redis 作为缓存 问题

https://www.bilibili.com/video/BV1xf4y1278g?p=1
https://blog.csdn.net/qq_36324113/article/details/90727355


*缓存雪崩:
【原因】
1. 热点数据有时效性,设定有效期,redis自动清空,假如热点ttl为1周,1周后,全部失效,数据全部去访问数据库,导致数据库卡顿或者宕机,称之为缓存雪崩。
2. redis服务器宕机。
【解决】
1. 数据的生命周期设置随机值。
2. 使用redis 集群[分为副本集群、切片机群]、多机缓存。
3. 多个redis 备机连接。

 


*缓存击穿:
一般不用去解决。一般没有所有连接都去请求这条被击穿数据。
特殊情况:如秒杀。。。

缓存击穿是缓存穿透的特殊表现形式。
【解决】
使用分布式锁


*缓存穿透:
不存在非法数据,不在数据库中,当然redis也不存在这样的数据,导致请求直接到了数据库,这就形成缓存穿透。

【解决】
1. 取数据库中查询的所有的数据放入缓存层,即使不存在,也加入进去。
2. 判断请求数据的合法性,比如请求的key 都为uuid。
3. 使用【布隆过滤器】:过滤器中标识数据库中所有数据的ID号, 数据太大,可能会有效率问题,通过布隆算法,降低错误。
4. 互斥的分布式锁,场景,多个黑客控制的客户端并发请求且请求一致。

 

【redis集群中的hash一致性的算法及作用】
1. 解决的问题,当集群中有机器发生增删,不至于发生大量的数据移动问题、拓展,容错。
缺点:数据倾斜,解决,虚拟节点问题。

数据倾斜:
1. 存储数据倾斜-- 某台机器存储了大量的数据
2. 框架的数据倾斜 -- 某台机器计算了大量的数据
虚拟节点的综合在 1000-2000个节点之间,这样基本均衡。

Redis-Cluster集群【分治、分片的,解决容量,压力,瓶颈的问题】使用了一致性 hash算法。
Redis 官方提供了 redis-trib.rb 工具方便我们快速搭建集群。
https://blog.csdn.net/qq_36324113/article/details/90727355
http://redis.cn/commands/cluster-slots.html
https://blog.csdn.net/zjuwzp/article/details/96692805
https://www.jianshu.com/p/813a79ddf932


【布隆过滤器】
使用场景,双11快递单号查询。

【实现】
1. 将所有数据通过hash生成值,该值作为bit数组的下标,同时bit数组的值设为1.
2. 需要请求的数据,通过该hash去找数组中的值,如果值为1,说明该数据在数据库中【可能】存在,如果为0,则说明一定不存在。
3. 布隆过滤器有一定的错误率的,跟hash碰撞相关,降低容错率的方法,使用3~5个hash函数,bit数组设置的足够大。

【代码】https://www.cnblogs.com/li-ning/p/9489958.html
bitmap.h --> 可使用C++ bitset 来替换

#ifndef _BIT_MAP_H
#define _BIT_MAP_H

#include<iostream>
#include<vector>
using namespace std;

/*
*一个数据32位,40亿个整数,每个整数需用一位表示,40亿位就完事
*/

class BitMap
{
public:
BitMap()
:_size(0)
{}

BitMap(size_t size)
:_size(0)
{
_array.resize((size>>5)+1); //多少个数据,一个数据占32位,加一是至少一个数据
}

bool Set(size_t num)
{
size_t index = num >> 5; //计算在哪个数据上
size_t n = num % 32;

if (_array[index] & (1 << (31 - n))) //移位问题
{
cout << "有数据" << endl;
return false;
}
else
{
size_t a = 1 << (31 - n);
_array[index] |= a;
++_size;
return true;
}
}

bool ReSet(size_t num) //删除一个数 之后重置
{
size_t index = num >> 5;
size_t n = num % 32;

if (_array[index] & (1 << (31 - n))) //数存在 删除
{
_array[index] &= (~(1 << (31 - n)));
--_size;
return true;
}
else
{
return false; //不存在这个数
}
}

private:
vector<size_t> _array; //数组
size_t _size; //位图中数据个数
};

#endif


void Test()
{
BitMap bm(65);

for (int i = 0; i < 32; ++i)
{
bm.Set(i);
}

bm.ReSet(0);
}

HashFun.h
#pragma once
template<class T> //各类哈希函数
size_t BKDRHash(const char *str)
{
register size_t hash = 0; // register 关键字请求让编译器将变量a直接放入寄存器里面
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch;
}
return hash;
}

template<class T>
size_t SDBMHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
}
return hash;
}

template<class T>
size_t RSHash(const char * str)
{
size_t hash = 0;
size_t magic = 63689;
while (size_t ch = (size_t)*str++)
{
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}


template<class T>
size_t APHash(const char *str)
{
register size_t hash = 0;
size_t ch;
for (long i = 0; ch = (size_t)*str++; i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}


template<class T>
size_t JSHash(const char* str)
{
if (!*str)
{
return 0;
}
size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
Bloom_Filter.h
#pragma once

#include"BitMap.h"
#include"HashFun.h"

template<class T>
struct __HashFun1 //5种哈希函数对应的仿函数
{
size_t operator()(const T& key)
{
return BKDRHash<T>(key.c_str());
}
};

template<class T>
struct __HashFun2
{
size_t operator()(const T& key)
{
return SDBMHash<T>(key.c_str());
}
};

template<class T>
struct __HashFun3
{
size_t operator()(const T& key)
{
return RSHash<T>(key.c_str());
}
};

template<class T>
struct __HashFun4
{
size_t operator()(const T& key)
{
return APHash<T>(key.c_str());
}
};

template<class T>
struct __HashFun5
{
size_t operator()(const T& key)
{
return JSHash<T>(key.c_str());
}
};


template<class K = string,
class HashFun1 = __HashFun1<K>,
class HashFun2 = __HashFun2<K>,
class HashFun3 = __HashFun3<K>,
class HashFun4 = __HashFun4<K>,
class HashFun5 = __HashFun5<K>>
class Bloom_Filter
{
public:
Bloom_Filter(size_t size)
:_capacity(size)
{
_bitmap._array.resize((size >> 5) + 1);
}

void _Set(const K& key)
{
_bitmap.Set(HashFun1()(key) % _capacity);
_bitmap.Set(HashFun2()(key) % _capacity);
_bitmap.Set(HashFun3()(key) % _capacity);
_bitmap.Set(HashFun4()(key) % _capacity);
_bitmap.Set(HashFun5()(key) % _capacity);
}

// 如果k个位置有一个为0,则肯定不在集合中
// 如果k个位置全部为1,则可能在集合中
bool _IsIn(const K& key)
{
if (!_bitmap.Test(HashFun1()(key) % _capacity))
return false;
if (!_bitmap.Test(HashFun1()(key) % _capacity))
return false;
if (!_bitmap.Test(HashFun1()(key) % _capacity))
return false;
if (!_bitmap.Test(HashFun1()(key) % _capacity))
return false;
if (!_bitmap.Test(HashFun1()(key) % _capacity))
return false;
return true;
}
private:
BitMap _bitmap;
size_t _capacity;
};

拓展:https://www.cnblogs.com/magisk/p/8809922.html
biset使用
bitset<4> bitset1;  //无参构造,长度为4,默认每一位为0
bitset<8> bitset2(12);  //长度为8,二进制保存,前面用0补充

string s = "100101";
bitset<10> bitset3(s);  //长度为10,前面用0补充

char s2[] = "10101";
bitset<13> bitset4(s2);  //长度为13,前面用0补充

cout << bitset1 << endl;  //0000
cout << bitset2 << endl;  //00001100
cout << bitset3 << endl;  //0000100101
cout << bitset4 << endl;  //0000000010101


C++11 中使用FNVHash
常用hash函数:
https://blog.csdn.net/sky_j123/article/details/38121825?utm_source=blogxgwz9
https://blog.csdn.net/u014800748/article/details/47397383

 

 

 

【分布式锁】
1. 两大类分布式锁:轮询锁poll【效率低,他要经过IO,效率低】,如: redis mysql,;时间通知锁(event方式),如,zookeeper,etcd。
redis:使用setnx 命令
Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值
SET key value [EX seconds] [PX milliseconds] [NX|XX]
使用订阅发布模式,实现event ,不需要频繁在poll。
发布订阅+超时时间轮询,可以减少IO轮询。
持锁线程在规定的时间内未执行完,需要watch线程对redis锁进行时间增持时间。

redis 集群: 主从复制集群【同步不精准】、cluster模式(分治分片模式)。
分布式要在同一个物理的数据中心完成。
分布式锁,只用一台redis 即可,该redis 只做分布式锁,不做其他用途,因为服务器基本性能好,不可能会挂。
redlock 抢夺多态redis 上的锁,抢到过半,就可以取得锁,如果不是,多台机器再次重试。
{放大了不好的一面,出现以上各种问题}

// event事件通知后续锁的变化,轮询向外的过程。

 

REDIS集群脑裂以及解决方案https://www.cnblogs.com/ysd139856/p/12896600.html

 关于reids集群会由于网络等原因出现脑裂的情况,所谓的集群脑裂就是,由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old master降为salve,这时再从新master同步数据,这会导致大量数据丢失。

   解决方案:

    redis中有两个配置参数:

    (旧版本)

      min-slaves-to-write 3

      min-slaves-max-lag 10

    (新版本)

      min-replicas-to-write 3

      min-replicas-max-lag 10

  第一个参数表示最少的salve节点为3个,第二个参数表示数据复制和同步的延迟不能超过10秒

    配置了这两个参数:如果发生脑裂:原master会在客户端写入操作的时候拒绝请求。这样可以避免大量数据丢失。

 

posted @ 2020-11-30 11:18  雪域蓝心  阅读(94)  评论(0编辑  收藏  举报