缓存服务架构
缓存穿透
缓存穿透指查询一个根本不存在的数据导致缓存层和储存层都无法命中, 缓存穿透导致每次查询都要去存储层查询,失去了缓存层保护后端存储层的意义;
可能造成缓存穿透的两个原因:
1. 自身代码或者数据原因
2. 恶意攻击 或者爬虫软件
缓存穿透的解决方案:
1.空对象缓存,当查询穿透到存储层,查到为null时,将这个对象缓存起来并设置过期时间;以下是伪代码
String gek(String key){
//从缓存中获取
cache=cache.get(key);
if(cache==null){
//从数据库获取
storage=storage.get(key);
//设置到缓存
cache.set(key, storageValue);
if(storage==null){
//设置过期时间
cache.expire(key, 60 * 5);
}
}
}
2.使用布隆过滤器
对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不 存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可 能不存在;当它说不存在时,那就肯定不存在。
布隆过滤器就是一个超大的bitmap 和几个不一样的hash函数;
当添加一个key时,布隆过滤器会分别使用hash函数得到hash值,再与bitmap的长度做取模运算得到一个数组下标,将下标位置标记为1;
向布隆过滤器去查询时,也是执行添加的逻辑,找到下标所在位置,如果几个位置都为1 则认为这个key存在,如果有一个为0则不存在;
布隆过滤器在bitmap比较拥挤时概率较低,因为在计算key时可能产生hash碰撞;
使用布隆过滤器的几点注意:
1.使用前需要把所有需要过滤的key添加到布隆过滤器;
2.当完成布隆过滤器的初始化后,布隆过滤器将无法修改,只能销毁重新初始化;
3.布隆过滤器采用的bitmap, 占用的内存空间并不算大;可以根据转换公式计算 8bit=1byte 1024byte=1KB 1024Kb=1MB
缓存击穿
由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大 甚至挂掉,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同时间.
缓存雪崩
由于缓存层承载着大量请求,有效的保护了存储层;当缓存层因为某种原因不能提供服务导致流浪都到达存储层,可能造成存储层宕机继而引发整个服务崩溃;
解决方案:
1.提高缓存层的高可用性,可使用redis哨兵或者redisCluster
2.在web层增加限流组件,比如sentinel 或者 histrix ,流量过大时使用降级的方式引导用户
缓存与数据库双写不一致
在高并发场景下,同时操作数据库和缓存可能造成不一致;
解决方案:
1.对于并发几率很小的数据,几乎可以不考虑这个问题;可以在缓存数据上增加过期时间;
2.对于并发高,但是可以容忍范围内的数据不一致情况 也可以采用过期时间的方案;
3.对于无法容忍的不一致性情况下,可以使用redis读写锁来控制,使写操作串行化;
4.也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。
bigkey
bigkey主要针对value; 比如一个String类型的value不应过大;一个hash,set,list 一个key的value元素不要过多;切合实际场景来调整;
bigkey可能引发的问题:
1.当使用了getall 等获取全部数据的函数时可能由于数据量过大导致redis阻塞
2.加入一个value的数据为1MB,client每秒请求1000次;那么会占用带宽每秒就是 1000MB,导致带宽占满,网络拥塞;