Redis—跳跃表、缓存一致性、雪崩、穿透、击穿
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Redis—跳跃表、缓存一致性、雪崩、穿透、击穿,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含5392字,纯文字阅读大概需要8分钟。
内容图文
1.跳跃表
1.跳跃表——本质是解决查找问题
-
跳跃表(skiplist)是一种随机化的数据结构,是一种可以与平衡树媲美的层次化链表结构——查找、删除、添加等操作都可以在对数期望时间下完成,以下是一个典型的跳跃表例子:
-
有序列表 zset 的数据结构,它类似于 Java 中的 SortedSet 和 HashMap 的结合体,一方面它是一个 set 保证了内部 value 的唯一性,另一方面又可以给每个 value 赋予一个排序的权重值 score,来达到 排序 的目的。
-
有序列表 zset的内部实现就依赖了一种叫做 「跳跃列表」 的数据结构。
2.为什么使用跳跃表?
- 1.要快速随机增删,故排除数组结构
- 2.要排序,且要考虑高并发性能,故排除红黑树、平衡树(因为他们需要rebalance实现再平衡)
- 性能考虑: 在高并发的情况下,树形结构需要执行一些类似于 rebalance 这样的可能涉及整棵树的操作,相对来说跳跃表的变化只涉及局部 (下面详细说);
- 实现考虑: 在复杂度与红黑树相同的情况下,跳跃表实现起来更简单,看起来也更加直观;
3.跳表的实现——随机出层数
skiplist 为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是 为每个节点随机出一个层数(level)。
- 比如,一个节点随机出的层数是 3,那么就把它链入到第 1 层到第 3 层这三层链表中。为了表达清楚,下图展示了如何通过一步步的插入操作从而形成一个 skiplist 的过程:
- 优点:每一个节点的层数(level)是随机出来的,而且新插入一个节点并不会影响到其他节点的层数,因此,插入操作只需要修改节点前后的指针,而不需要对多个节点都进行调整,这就降低了插入操作的复杂度。
为什么跳表默认允许最大32层?
直观上期望的目标是 :
- 50% 的概率被分配到 Level 1;
- 25% 的概率被分配到 Level 2,
- 12.5% 的概率被分配到 Level 3;
以此类推…有 2-63 的概率被分配到最顶层,因为这里每一层的晋升率都是 50%。
- Redis 跳跃表默认允许最大的层数是 32,被源码中 ZSKIPLIST_MAXLEVEL 定义,当 Level[0] 有 2^64 个元素时,才能达到 32 层,所以定义 32 完全够用了。
2.缓存一致性
2种方案
- 1.先删缓存,再更新数据库
- 2.先更新数据库,再删除缓存
为什么是删除,而不是更新缓存?
1.先删除缓存,再更新数据库
先删除缓存,数据库还没有更新成功,此时如果读取缓存,缓存不存在,去数据库中读取到的是旧值,缓存不一致发生。
解决方案:延时双删
-
延时双删的方案的思路是,先删除1次缓存,为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再sleep一段时间,然后再次删除缓存。
-
sleep的时间:要对业务读写缓存的时间做出评估,sleep时间大于读写缓存的时间即可。
流程如下:
- 1.线程1删除缓存,然后去更新数据库。
- 2.线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取,这时候由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存。
- 3.线程1,根据估算的时间,sleep,由于sleep的时间大于线程2读数据+写缓存的时间,所以缓存被再次删除。
- 4.如果还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值。
2.先更新数据库,再删除缓存
- 这个就更明显的问题了,更新数据库成功,如果删除缓存失败或者还没有来得及删除缓存,那么,其他线程从缓存中读取到的就是旧值,还是会发生缓存不一致。
解决方案1:消息队列(有缺陷)
先更新数据库,成功后往消息队列发消息,消费到消息后再删除缓存,借助消息队列的重试机制来实现,达到最终一致性的效果。
这个解决方案其实问题更多。
- 1.引入消息中间件之后,问题更复杂了,怎么保证消息不丢失?
- 2.就算更新数据库和删除缓存都没有发生问题,消息的延迟也会带来短暂的不一致性,不过这个延迟相对来说还是可以接受的
解决方案2:进阶版消息队列
-
可以借助监听binlog的消息队列来做删除缓存的操作。这样做的好处是,不用你自己引入,侵入到你的业务代码中,中间件帮你做了解耦,同时,中间件的这个东西本身就保证了高可用。
-
当然,这样消息延迟的问题依然存在,但是相比单纯引入消息队列的做法更好一点。
-
而且,如果并发不是特别高的话,这种做法的实时性和一致性都还算可以接受的。
解决方案3:设置缓存过期时间(一致性要求不高时)
每次放入缓存的时候,设置一个过期时间,比如5分钟,以后的操作只修改数据库,不操作缓存,等待缓存超时后从数据库重新读取。如果对一致性要求不是很高的情况,可以采用这种方案。
- 但是,如果数据更新的特别频繁,不一致性的问题就很大了。
3.为什么是删除缓存,而不是更新缓存?
-
以先更新数据库,再删除缓存来举例:
-
如果是更新的话,那就是先更新数据库,再更新缓存。
举个例子:如果数据库1小时内更新了1000次,那么缓存也要更新1000次,但是这个缓存可能在1小时内只被读取了1次,那么这1000次的缓存更新有必要吗?
反过来,如果是删除的话,就算数据库更新了1000次,那么也只是做了1次缓存删除,只有当缓存真正被读取的时候才去数据库加载。
3.穿透、击穿、雪崩
1.雪崩(key同时大面积失效)
1.雪崩是什么?
同一时间缓存大面积失效,那一瞬间Redis跟没有一样,那这个数量级别的请求直接打到数据库几乎是灾难性的,
2.怎么办?
- 1.批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效。
- 2.如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题
- 3.设置热点数据永远不过期,有更新操作就更新缓存就好了(确保缓存一致性)。
- 4.限流、降级
2.穿透(根本查不到)
1.穿透是什么?
缓存穿透:是指缓存和数据库中都没有的数据,而用户不断发起请求。
- 1.数据不合法:我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据
- 2.数据合法但不存在:或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
2.穿透怎么办?
1.做参数合法性校验,把不合法的请求过滤掉
2.缓存空对象,把找不到value的key,主动写为null或错误这样的值,并且把该缓存的有效期设置的短一些。
3.布隆过滤器(位图+hash算法):利用高效数据结构和算法,快速判断这个key是否存在与数据库中,不存在就return,存在就查数据库,并更新缓存。
3.击穿(打了个洞)
1.击穿是什么?和雪崩哪里相似?
- 缓存击穿:指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这一个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
- 缓存雪崩:因为大面积的key缓存失效,打崩了DB。
2.怎么解决?
1.设置热点数据永远不过期。
2.加互斥锁。(1个获取,其他等待)
4.总结
内容总结
以上是互联网集市为您收集整理的Redis—跳跃表、缓存一致性、雪崩、穿透、击穿全部内容,希望文章能够帮你解决Redis—跳跃表、缓存一致性、雪崩、穿透、击穿所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。