为啥 redis 单线程模型也能效率这么高?
-
纯内存操作。
-
核心是基于非阻塞的 IO 多路复用机制。
-
C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
-
单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
redis 过期策略
定期删除:redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
惰性删除:获取时redis检查该key是否过期,如果过期则删除,并给你返回空。
内存淘汰机制:
-
no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错
-
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据进行淘汰
-
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据进行淘汰
-
volatile-random:从已设置过期时间的数据集中选择任意数据进行淘汰
-
volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据进行淘汰
-
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(最常用)
-
allkeys-random:从数据集中选择任意数据进行淘汰
-
allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
redis 持久化
RDB:redis周期性将其中的数据快照进行持久化
优缺点:
-
RDB由主线程fork一个子线程,对redis本身的影响很小,可以让 redis 保持高性能
2. 相对于 AOF 持久化机制来说,基于 RDB 数据文件来重启和恢复 redis ,更加快速 3. 如果redis 故障恢复,想尽可能少的丢失数据,那么 RDB 没有 AOF 好
AOF:对每条写入命令作为日志,以追加(append-only)的形式写入日志文件,redis 重启时通过回放 日志中的指令来重新构建整个数据集
优缺点:
1. AOF 日志文件以追加模式写入性能非常高,而且文件不容易破损。AOF 日志文件通过非常易于理解的方式进行记录,即使文件尾部破损,也很容易修复。
2. 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
3. 一般 AOF 会每隔 1 秒,通过一个后台线程执行一次`fsync`操作,最多丢失 1 秒钟的数据
缓存失效
-
缓存穿透:大量无效请求过来直接打到db上 解决方案:a. 缓存空值 b. 布隆过滤器
-
缓存击穿:缓存过期时,大量同类型正常请求打到db上 解决方案:a. db查询前加互斥锁 b. redis降级集群错开过期时间
-
缓存雪崩:缓存服务挂了,大量不同类型正常请求打到db上 解决方案:a. 建立缓存集群 b. 本地缓存+限流&降级 c. 缓存持久化机制,服务正常后能尽快恢复缓存数据
缓存数据库不一致
-
先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
解决:先删除缓存再更新数据库,如果删除缓存失败,此时请求读缓存为旧数据,数据库没有更新也是旧数据。如果更新数据库失败,则数据库依然是旧数据,而请求读缓存为空再读数据库也是旧数据。
-
先删除了缓存,然后要去修改数据库时还没来得及。一个请求读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更完成了,导致数据库和缓存中的数据不一样了
解决:a. 将修改数据库和读取缓存空需要更新当成事件放入FIFO队列,然后一个线程串行取事件处理,这样可以保证缓存空读取数据库一定是更新完成的
b. 先删除缓存,再更新数据库,再删除缓存。整个步骤作为事务,如果第三步失败直接回滚数据库更新操作,并将更新事件存入队列,由异步线程进行执行。此时缓存和数据库均为旧数据。
redis并发竞争
多个客户端同时去set同一个key,解决方案:
-
乐观锁。适用于对修改顺序没有要求的场景,不要在分片集群中使用。利用redis的watch机制回滚事务,保证修改过程中key没有变动过
-
分布式锁。适用于分布式环境,在业务层操作 redis 前先申请一个分布式锁,拿到锁的才能操作。
-
时间戳。适用于有序场景,在写入的值中带上时间戳,写入前先比较已有的value中的时间戳是否晚于自己的时间戳,如果晚就不写。
-
消息队列,适用于并发量很大的场景,通过消息队列实现数据的串行化处理。