目录
缓存穿透
缓存穿透是指查询的数据在缓存中不存在,需要到数据库中查询,导致缓存没有起到作用。
解决办法
1. 缓存空值
对于不存在的key,在缓存中存储value为空,以后来的查询仍然去缓存中查询。
2. 布隆过滤器
对于每一次查询,把key放入布隆过滤器进行过滤,如果布隆过滤器判断不存在,就直接返回空,不再去数据库请求。
布隆过滤器特点:当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
布隆过滤器原理:
- 初始化
向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度 进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就 完成了 add 操作。 - 查询
向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位 置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个key 不存在。如果都是 1,这并不能说明这个 key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组 比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。 这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景, 代码维护较为 复杂, 但是缓存空间占用很少。
注意
布隆过滤器里面的数据不能删除,只能添加,如果数据有更新,需要重建。
缓存击穿
大量的缓存同时失效,导致大量的请求打到数据库。
解决办法
设置缓存过期时间设置一个时间范围内的随机值,避免同时大量过期。
缓存雪崩
缓存雪崩指的是缓存层支撑不住或宕掉后, 流量会像奔逃的野牛一样, 打向后端存储层。
解决方法
服务降级,限流,避免缓存失效以后引发连锁失效。
热点缓存重建优化
正常情况下,采用缓存+失效时间,可以正常更新数据。如果出现下列情况:
- 热点key,查询请求量大
- 缓存重建耗时长
就会出现在缓存失效是大量的线程重建缓存,大量的请求打到数据库。
解决方法
缓存重建串行化,利用互斥锁来解决。
缓存与数据库双写不一致
并发场景下,更新缓存和更新数据库出现不同步。
解决方法
- 并发不高情况下,本来很少发生,加上过期时间,失效自动更新,问题不大
- 某些高并发情况下也能容忍
- 读写锁排队处理,读写、写写加锁,读读相当于无锁。
- 引入中间件,阿里也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加 了系统的复杂度。
这些都是读多写少场景下,如果写操作多,缓存要保持高一致性,更新会很频繁。使用缓存不合适。
redis开发规范
键值设计
键设计
- 可读可维护
- 简洁
- 不要包含特殊字符
值设计
- 避免bigkey,就是一个value不要存储大量数据
bigkey会导致查询变慢,传输变慢,删除变慢,redis单线程,拖垮redis性能。 - 选择合适的数据类型,节省内存
- 设置过期时间
命令规范
- 【推荐】 O(N)命令关注N的数量 例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有 遍历的需求可以使用hscan、sscan、zscan代替。
- 【推荐】:禁用命令 禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的 方式渐进式处理。
- 【推荐】合理使用select redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还 是单线程处理,会有干扰。
- 【推荐】使用批量操作提高效率
原生命令:例如mget、mset。
非原生命令:可以使用pipeline提高效率。
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
注意两者不同:
1. 原生是原子操作,pipeline是非原子操作。
2. pipeline可以打包不同的命令,原生做不到
3. pipeline需要客户端和服务端同时支持。
- 【建议】Redis事务功能较弱,不建议过多使用,可以用lua替代
客户端规范
- 避免多个应用使用一个Redis实例
- 使用连接池
必要时进行连接池预热。