Redis(或其他缓存系统)
要求:了解redis工作模型、redis持久化、redis过期淘汰机制、redis分布式集群的常见形式、分布式锁、缓存击穿、缓存雪崩、缓存一致性问题
本文回答以下问题,文内可能有遗漏、错误或表达不够清晰的地方。
① redis性能为什么高?单线程的redis如何利用多核cpu机器?
② redis的缓存淘汰策略?
③ redis如何持久化数据?
④ redis有哪几种数据结构?
⑤ redis集群有哪几种形式?
⑥ 有海量key和value都比较小的数据,在redis中如何存储才更省内存?
⑦ 如何保证redis和DB中的数据一致性?
⑧ 如何解决缓存穿透和缓存雪崩?
⑨ 如何用redis实现分布式锁?
① redis性能为什么高?单线程的redis如何利用多核cpu机器?
- redis为什么性能高、速度快?
Redis是纯内存操作,数据读写在内存中,异步持久化到磁盘;
基于非阻塞的I/O多路复用机制;
Redis是单线程模型,避免了上下文切换;
高效的数据结构,Redis中的数据结构是专门进行设计的;
resp协议(redis客户端和服务端通信协议) 文本协议
- 如何利用多核cpu机器?
如果你确实需要充分使用多核cpu的能力,那么需要在单台服务器上运行多个redis实例(主从部署/集群化部署),并将每个redis实例和cpu内核进行绑定。可以参考:https://redis.io/topics/faq
Redis 6.0首次引入了多线程,有两个原因:
1)充分利用CPU多核,6.0之前主线程只能利用一个核。
2)多线程任务可以分摊Redis同步IO读写负荷。
② redis的缓存淘汰策略?
Redis 如何管理内存?
过期键删除
,内存和CPU资源都是宝贵的,Redis通过定期删除设定合理的执行时长和执行频率,配合惰性删除兜底的方式,来达到CPU时间占用和内存浪费之间的平衡。
数据淘汰
,如果key生产的太快,定期删除操作跟不上新生产的速率,而这些key又很少被访问无法触发惰性删除,是否会把内存撑爆?回答是不会,因为redis有数据淘汰策略:
Redis采用的默认键内存释放策略
是noeviction-不删除,达到最大内存时,如需更多内存(存入数据),则操作报错;默认的过期键删除策略
则是惰性删除+定期删除的方案;
③ redis如何持久化数据?
RDB内存快照、AOF及混合持久化
RDB:达到触发条件进行内存快照(save 60 1000:满足“60秒内有至少有1000个键被改动”这一条件时,自动保存一次数据集,Redis默认将内存数据库快照保存在名字为 dump.rdb 的二进制文件中);有2种手动生成快照文件方式:save同步阻塞;bgsave异步非阻塞
AOF:将修改的每一条指令记录到文件 appendonly.aof 中(开启appendonly yes,先写入oscache每隔一段时间fsync到磁盘);重放增量日志:AOF 文件里可能有太多没用指令,所以 AOF 会定期根据内存的最新数据生成 AOF 文件重放增量日志 (定期自动重写,重写时把内存数据以命令方式存储)
混合:aof‐use‐rdb‐preamble yes(用 RDB 恢复内存状态可能会丢失大量数据,而 AOF 重放性能慢很多,4.0引入了新的持久化选项混合持久化,必须先开启aof),在Redis重启的时候,可以先加载RDB的内容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放,重启效率大幅得到提升。
④ redis有哪几种数据结构?
在 Redis 中,常用的 5 种数据类型和应用场景如下:
String: 缓存、分布式锁、计数器、微信文章阅读量等。
List: 链表、队列、微博关注人时间轴列表、评论列表、好友列表、微信公众号消息推送等。
Hash: 用户信息、Hash 表、电商购物车等。
Set: 去重、赞、踩、共同好友等。
Zset: 访问量排行榜、点击量排行榜、当日热搜、七日热搜等
举例:
- 微信公众号消息推送
list:key-value,key是用户id、value是消息列表;10min前的消息在前,1h前的消息在后
- 当日热搜、七日热搜(zset每个元素带可重复的分值)
zset:key-value,key是时间点、value是点击量-新闻id;hotNews20230101,10000-xxx事件
- 微信文章阅读量
string:key-value,key是文章id、value文章阅读量;文章A,阅读量10w+
- 微博微信点赞、踩
set:key-value,key是朋友圈消息id、value是点赞的用户id;xxx消息-甲乙丙点赞,可统计点赞总数
- 微博微信共同好友
set:key-value,a好友/关注为seta、b好友/关注为setb
ab共同关注求seta setb交集、给a推荐好友求seta setb差集(大于阈值)
6.电商购物车
hash:key-field-value,key是用户id、field是商品id、value商品数量;A-显示器-100台
⑤ redis集群有哪几种形式?
Redis支持三种集群方案
主从复制模式:能实现读写分离,但是不能自动故障转移;
Sentinel(哨兵)模式:基于主从复制模式,能实现自动故障转移,达到高可用,但与主从复制模式一样,不能在线扩容,容量受限于单机的配置;
Cluster模式:无中心化架构,实现分布式存储,可进行线性扩展,也能高可用,但对于像批量操作、事务操作等的支持性不够好
⑥ 有海量key和value都比较小的数据,在redis中如何存储才更省内存?
直接以 key-value 形式进行存储,数据量特别庞大,如何存储才更省内存?
尽量用 int 作为 key、用 hash 中的 zipList
ziplist可以用来存放字符串或者整数,其存储数据的特点是:比较小的整数或比较短的字符串。当list键里包含的元素较少、并且每个元素要么是小整数要么是长度较小的字符串时,redis将会用ziplist作为list键的底层实现。同理hash和zset在这种场景下也会使用ziplist。
zipList要比hashTable占用少的多的空间,当hash 键值对的键和值的字符串长度大于 64 个字节或者键值对数量大于 512 个改用 hashtable 的编码方式
其他特殊数据结构:
- 布隆过滤器
特点:某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在
- HyperLogLog
一种基数(即集合中元素的个数)算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等;适用场景:统计网站的UV数据
⑦ 如何保证redis和DB中的数据一致性?
参考文章:保证Redis缓存与数据库的一致性?
提高缓存利用率:
写请求不仅写数据库,还要写缓存
缓存中的数据,都设置失效时间
读请求先读缓存,如果缓存里不存在,则读数据库,然后写入缓存
并发下同时操作数据库与缓存会存在数据不一致性问题,通常有以下几种解决方式:
更新数据库后,更新缓存;
更新缓存后,更新数据库;
删除缓存后,更新数据库;
更新数据库后,删除缓存;(推荐)
● 更新数据库后,更新缓存 / 更新缓存后,更新数据库
- 线程安全角度: 后面一步的更新操作如果延时了(发生GC),无论是哪种,最后数据都会不一致
- 业务场景角度: 如果业务是写多读少的场景,可能数据压根没读过,缓存就频繁更新,浪费性能;如果写入缓存的值是经过复杂计算的值,就会浪费性能
● 删除缓存后,更新数据库
● 更新数据库后,删除缓存
数据库读写分离,读操作的速度远快于写操作的,发生概率低,可以采用异步延时删除。
无论是更新缓存还是删除缓存,只要第二步发生失败,那么就会导致数据库和缓存不一致。可以利用消息队列进行删除的补偿,但会增加维护成本,同时对业务代码造成大量的侵入。
更简单的方案:订阅数据库的 binlog 日志,对缓存进行操作
⑧ 如何解决缓存穿透和缓存雪崩?
● 缓存穿透
现象:访问根本不存在的数据,缓存层和存储层都不存在
原因:业务代码问题、恶意攻击、爬虫访问
解决:接口校验、缓存空对象、布隆过滤器
● 缓击击穿
现象:缓存中没有但数据库中有的数据
,某一个热点 key 的缓存时间到期,同时有大量的请求打进来直达数据库,可能会造成数据库瞬间压力过大甚至挂掉
解决:热点数据不过期、加互斥锁只允许一个线程计算;接口限流熔断降级
● 缓存雪崩
现象:大批量缓存同一时间失效,可能导致大量请求同时直达数据库,可能会造成数据库瞬间压力过大甚至挂掉
解决:热点数据不过期、加互斥锁只允许一个线程计算、缓存数据过期时间设置为一个时间段内的随机值
⑨ 如何用redis实现分布式锁?
Redisson分布式锁原理:
- 加锁原理
底层逻辑:通过一段类似 setnx + expire 操作的Lua脚本(hset)
特点:支持可重入,第一个客户端请求加锁,加锁成功/锁重入返回null,第二个客户端再来请求加锁,加锁失败,返回锁的剩余生存时间ttl。
- 锁互斥机制
当锁正在被占用时,等待获取锁的进程并不是通过一个 while(true) 死循环去获取锁,而是利用了 Redis 的发布订阅机制,订阅锁释放消息,通过信号量阻塞自己ttl时间来唤醒,有效的解决了无效的锁申请浪费资源的问题。
- 锁续期机制
leaseTime 必须是 -1 才会开启 Watch Dog 机制,一旦设了时间RedissonLock就会认为你需要自己控制锁时间,而放弃执行续锁逻辑。客户端加锁的锁 key 默认生存时间为 30 秒
,看门狗续命机制是一个后台定时任务线程,获取锁成功之后,每隔 10 秒 检查一下
,如果客户端还持有锁 key,那么就会不断的延长锁 key 的生存时间。
- 释放锁机制
锁释放,发布释放锁的消息,此时被信号量阻塞的等待队列中的一个线程就可以继续尝试获取锁了。
参考链接:
Redis套路,一网打尽
Redis 核心篇:唯快不破的秘密
Redis 的缓存淘汰机制(Eviction)
选择合适Redis数据结构,减少80%的内存占用
网友评论