为什么使用缓存
提升读写性能
redis为什么快
redis的hash是如何实现的,rehash
参考:https://blog.csdn.net/zxm342698145/article/details/80888266
redis数据库使用字典作为低层实现,对数据库的增删改查操作也是构建在对字典的操作之上,redis的字典使用hash表作为低层实现;
redis为了时间效率和空间效率最大化,hash表的设计与普通的哈希表设计不同;
普通hash表设计:当hash表满员(大于阈值(当前数组的长度乘以加载因子的值))时会进行rehash,即调整空间大小,并拷贝原来的数据,这里rehash就是效率的关键
redis的hash表设计: 采用渐进式rehash来调整空间大小,就是把拷贝工作平摊到后续操作中,即对节点操作,再次插入、查找、删除、修改时都会拷贝
实现redis的rehash过程,一个hash结构必须有以下字段:
两个hash表,一个表拷贝到另一个表的容器;
一个标识rehashidx来表明是否在rehash中;
何时启动rehash?
当hash结构的第一个hash表ht[0]达到扩容条件就可以启动了。此时重新调整并分配新的空间,将hash结构的第二个hash表ht[1]指向这个空间。
rehash过程
1、通过rehashidx索引找到要搬迁节点的位置,如果为空,则向后跳
2、计算要搬移节点的hash值,得出要插入到新hash表的位置
3、写入到新节点中,如果节点是链式的,则要搬移后面所有链表中的节点
4、更新hash表的计数
需要注意的是因为在渐进式rehash的过程中,字典同时会使用ht[0],ht[1]两个hash表。所以在这个过程中,删除,查找,更新等操作会在这两个hash表上进行。例如要在字典里查找一个key的话,会先在ht[0]里面进行查找,如果没有找到的话,就会继续到ht[1]里面进行查找。(兼容)
另外,在渐进式rehash执行期间,新添加到字典的key-val一律会被保存到ht[1]里面,而ht[0]不再进行任何添加操作,这一措施保证了ht[0]包含的key-val对数量只减不增,并随着rehash操作的执行而最终变成空表。
redis的rehash和hashmap中的rehash有什么不同
redis的rehash,是将ht[0]的数据搬到ht[1] (ht[1]是ht[2]两倍),扩容过程中新加入的元素直接往ht[1]里面添加
hashmap的resize,创建一个新的数组,将旧的搬到新的数组中
redis集群的主从数据同步
redis集群怎样做到高可用
redis的持久化方式
RDB
RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
为什么不用redis做专门的持久化数据库存储
如何使用 Redis 实现分布式锁
Redis LRU 算法
Redis 的线程模型
Redis 有哪几种数据“淘汰”策略
Redis 有几种数据“过期”策略
为什么 Redis 单线程模型也能效率这么高
Redis 是单线程的,如何提高多核 CPU 的利用率
可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个 CPU ,你可以考虑一下分区。
缓存穿透
缓存穿透,是指查询一个一定不存在的数据,由于缓存是不命中时被动写,并且处于容错考虑,如果从 DB 查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,失去了缓存的意义。
1)方案一,缓存空对象。
当从 DB 查询数据为空,我们仍然将这个空结果进行缓存,具体的值需要使用特殊的标识,能和真正缓存的数据区分开。另外,需要设置较短的过期时间,一般建议不要超过 5 分钟。
2)方案二,BloomFilter 布隆过滤器。
在缓存服务的基础上,构建 BloomFilter 数据结构,在 BloomFilter 中存储对应的 KEY 是否存在,如果存在,说明该 KEY 对应的值不为空。那么整个逻辑的如下:
1、根据 KEY 查询【BloomFilter 缓存】。如果不存在对应的值,直接返回;如果存在,继续向下执行。【后续的流程,就是标准的流程】
2、根据 KEY 查询在【数据缓存】的值。如果存在值,直接返回;如果不存在值,继续向下执行。
3、查询 DB 对应的值,如果存在,则更新到缓存,并返回该值。
缓存雪崩
缓存雪崩,是指缓存由于某些原因无法提供服务( 例如,缓存挂掉了 ),所有请求全部达到 DB 中,导致 DB 负荷大增,最终挂掉的情况。
预防和解决缓存雪崩的问题,可以从以下多个方面进行共同着手。
1)缓存高可用
通过搭建缓存的高可用,避免缓存挂掉导致无法提供服务的情况,从而降低出现缓存雪崩的情况。
假设我们使用 Redis 作为缓存,则可以使用 Redis Sentinel 或 Redis Cluster 实现高可用。
2)本地缓存
如果使用本地缓存时,即使分布式缓存挂了,也可以将 DB 查询到的结果缓存到本地,避免后续请求全部到达 DB 中。
当然,引入本地缓存也会有相应的问题,例如说:
本地缓存的实时性怎么保证?
方案一,可以引入消息队列。在数据更新时,发布数据更新的消息;而进程中有相应的消费者消费该消息,从而更新本地缓存。
方案二,设置较短的过期时间,请求时从 DB 重新拉取。
方案三,手动过期。
每个进程可能会本地缓存相同的数据,导致数据浪费?方案一,需要配置本地缓存的过期策略和缓存数量上限。
3)请求 DB 限流
通过限制 DB 的每秒请求数,避免把 DB 也打挂了。这样至少能有两个好处:
可能有一部分用户,还可以使用,系统还没死透。未来缓存服务恢复后,系统立即就已经恢复,无需再处理 DB 也挂掉的情况。
当然,被限流的请求,我们最好也要有相应的处理,走【服务降级】,提供一些默认的值,或者友情提示,甚至空白的值也行。
缓存热点 key 重建优化(缓存击穿)
热点key造成的问题:
当前 key 是一个热点 key( 例如一个热门的娱乐新闻),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存 ( 如下图),造成后端负载加大,甚至可能会让应用崩溃。
作为一个并发量较大的应用,在使用缓存时有三个目标:
第一,加快用户访问速度,提高用户体验。
第二,降低后端负载,减少潜在的风险,保证系统平稳。
第三,保证数据“尽可能”及时更新。
1)互斥锁 (mutex key)
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可
2)永远不过期
缓存上从不设置过期时间,功能上将过期时间存在 KEY 对应的 VALUE 里。流程如下:
1、获取缓存。通过 VALUE 的过期时间,判断是否过期。如果未过期,则直接返回;如果已过期,继续往下执行。
2、通过一个后台的异步线程进行缓存的构建,也就是“手动”过期。通过后台的异步线程,保证有且只有一个线程去查询 DB。
3、同时,虽然 VALUE 已经过期,还是直接返回。通过这样的方式,保证服务的可用性,虽然损失了一定的时效性。
redis单机并发