redis 的面试点总结
redis自学总结,这并不是标准答案,需要你自己思考
- 1. redis的读写一致性
- 1.1 先删除缓存,再更新数据库
- 解决方案
- 1.2 先更新数据库再更新缓存(这中操作比较推荐)
- 1.3 删除缓存失败的情况怎么解决?(删除重试)
- 2 redis 击穿、雪崩、穿透
- 缓存击穿(场景对应一个或者几个热点key)
- 缓存击穿的3个解决方案
- 缓存雪崩
- 缓存雪崩情景1的解决方案:
- 缓存雪崩场景2的解决方案:
- 穿透
- 穿透的解决方案:
- 3. 一个亿的用户实时积分排行榜怎么实现(zset并分桶)
- 4. 连续(购物/游戏/点赞等场景)登录记录并进行奖励的实现
- 5. 共同好友问题
- 6. redis集群扩容的时候是否还能提供正常的读写
- 7. redission 续锁失败了应该怎么办
- 8. 如何进行redis性能调优
- 9. 大key中的问题
- 大key的一般定义(经验值)
- 大key的影响
- 大key的产生
- 快速找出大key
- 优化大key
- 10. redis的多线程问题
- 11. 热点key的处理方案
- 预先知道热点key
- 不能预先知道的热key
- 12. redis中的key过期了会立即删除吗?
- redis中如果没有设置过期时间,key可能会被删除吗?
- 13. 系统时钟对于redis的影响
- 14. 由于网络抖动引起redis主备频繁切换
- 15. 3个节点的redis集群,如果一个节点挂了会不会脑裂?如果不脑裂怎么选主
- 主观下线到客观下线
1. redis的读写一致性
数据的不一致主要是出现在数据更新的阶段,如果是读数据的场景不会出现数据一致问题我们处理的时候一般不会去更新缓存,而是直接删除缓存,这样可以简化流程,等待下一次查的时候直接查数据库,写入缓存
1.1 先删除缓存,再更新数据库
有2个线程,线程1先删除缓存,后更新数据库,假如更新数据库的时候有网络延迟.线程2在线程1删除以后更新数据库之前查询数据,此时缓存里面没有数据,线程2开始去数据库查询数据,查询完成以后更新redis
线程2更新完成以后,线程1更新数据完成,如果有过期时间的话,在过期时间内其他线程查询到额数据就一直是老数据,这就导致了不一致。如果没有过期时间就得等到下一次数据更新的时候才会获取到新数据,这期间查询的数据都是老数据。
解决方案
先删除缓存更新数据库,更新完数据库再进行二次删除缓存,这时候可以认为在极端时间内有数据不一致(这个过程叫双删)。如果极短的不一致也不可接受就需要加锁让数据强一致,但是这样对性能的损失非常大
需要注意的点是延迟双删: 双删场景下,第二次删除一般会加个几十到200毫秒的sleep,主要是为了解决线程2更新完数据的操作比线程1删除redis的速度慢,如果这种情况下双删就没有意义,redis里仍然是旧数据
1.2 先更新数据库再更新缓存(这中操作比较推荐)
如果是先操作数据库的话,那就是更新完数据量,再删缓存。这时候是从更新数据库到删除数据量这段时间内读取到的数据都是老数据。如果出现删除缓存失败的情况跟上面的逻辑是一致的。也需要进行重试逻辑
1.3 删除缓存失败的情况怎么解决?(删除重试)
删除重试: 上面的解决方案仍然不能保证删除没有问题,比如第二次删除的时候失败了,对于第二次删除失败的问题我们的解决方案是,执行异步删除重试的逻辑;删除错误的事件发给MQ
删除重试怎么解耦合?使用阿里云的canal进行解耦合
主要用途基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费实现方式主要是基于业务的trigger获取增量变更
canal的实现原理:
mysql:
- MySQL master 将数据变更写入二进制日志(binlog,可以通过show binlog events进行查看)
- MySQL slave 将master的bin log events 拷贝到他的中继日志中
- MySQL 重放relay log 中的事件,将数据变更反映到自己的数据
canal工作原理:
- canal 模拟MySQL slave的交互协议,伪装自己为MySQL slave,向master发送dump 协议
- mysql master收到dump请求,开始推送bin log给MySQL slave,即canal
- canal 解析bin log对象
另外,canal的客户端也非常丰富(go,Java、php)
2 redis 击穿、雪崩、穿透
缓存击穿(场景对应一个或者几个热点key)
在高并发的场景下,当一个key非常热点,在不停的扛着大并发,当这个key在瞬间失效的情况下,持续的大并发就会穿透缓存。
缓存击穿的3个解决方案
- 如果能预先知道这个热点的key就可以给他一个足够的过期时间,比如爆卖三天就把他的时间设置大于3天
- 如果不能预先判断,可以添加一个这样的逻辑,比如一段时间内,get的次数超过预设的阈值就可以把他过期时间加长
- 如果从如果不使用上面的方案也可以在get的时候如果是nil,就加一个同步锁,其中一个线程先去数据库里查询数据,其他的线程都等待去捞数据的线程去更新缓存
需要注意的是:这里加锁的情况是从缓存中获取不到才加锁,只要缓存命中率高大部分情况下都不不会走加锁的逻辑
缓存雪崩
雪崩的场景可以简单理解成2个点
- 过期时间集中过期失效
- redis挂掉导致查询缓存失败
缓存雪崩情景1的解决方案:
设置一个随机数的过期时间,比如5ms-10m取一个随机数
缓存雪崩场景2的解决方案:
高可用(集群方案、异地灾备方案)
穿透
穿透的情况我们一般指的是查询大量/多次不存在的key
穿透的解决方案:
- 参数校验(,比如参数大于多少,或者小于多少,这个需要根据场景来设置一个合理的list)
- 缓存空对象,缓存空对象一定要设置一个过期时间
- 使用布隆过滤器,这个方案我理解就是方案一个扩展。如果你不了解布隆过滤器,可以把他理解成以一个黑名单或者白名单
如果你数据量比较少完全可以使用黑白名单的方式简单实现,你查的这个key如果不在我的list里我直接拒绝你的查询
3. 一个亿的用户实时积分排行榜怎么实现(zset并分桶)
主要的点有2个:
- 数据量比较大
- 实时性要求比较高
使用zset这时候肯定会出现另外一个问题,就是大key的问题,这时候就需要使用分桶额方式,比如top100、top1000、top100000。
4. 连续(购物/游戏/点赞等场景)登录记录并进行奖励的实现
这种场景放在数据库里可以实现,但是实现的方式不合适。我们可以使用redis提供的bitmap进行实现。
在redis的场景里bitmap的实现是由string类型来进行实现的,string类型最大可以占用512M内存,那么他就可以存储的数量就是
512M = 1024 byte * 1024 * 512 *8 = 42 亿个状态
5. 共同好友问题
用户A的还有和用户B的好友进行取并集。在redis l里面有个对象是SINTERSTORE可以实现,返回一个新的集合
这里推荐几个方案
- neo4j+redis(首推,neo4j就是专门做这种图谱的数据库)
- MySQL + redis
- habase + hadoop + redis
6. redis集群扩容的时候是否还能提供正常的读写
结论是不影响
原因是:
- 当新的节点通过meet命令加入集群的时候没有槽位,也不处理任何的查询业务
- 然后把槽位进行均分,然后客户端给被迁移的节点发送migrating事件,此时被迁移的节点变成迁出中状态(有槽位从这个节点上迁出去)
- 客户端给迁入节点发送import指令,迁入节点变成迁入状态
- 状态修改完成,开始迁slot
- 源节点执行migrating指令,迁移对应的数据到目标节点上
- 这时候如果有客户端查询刚才迁移的数据会遇到两种情况
- 当前key还在源节点上,说明数据还没有迁移到目标节点上,源节点返回数据
- 当key已经重新迁移了,已经不在原节点上了,这时候源节点会返回一个ask重定向,客户端发起sking指令到目标节点。目标节点返回ok事件,然后客户端发起查询事件
- slot 迁移完成以后新节点向集群发送通知,新节点关于这个迁移的slot开始接收所有的请求,不需要进行ask的重定向阶段
7. redission 续锁失败了应该怎么办
- 什么是redission锁续期
- 当redission客户端成功向redis的某个key加锁成功之后会激活一个后台线程,后台线程的功能是每隔一段时间(超时时间/3)就去检查是否还持有锁,如果持有锁就将所得超时时间设置为default值。
- 续锁失败会带来什么问题
- 锁到期释放问题,可能被其他线程或节点获得,导致资源出现并非访问问题
- 如何应对续锁失败?
- 根据业务的执行时间调整默认的锁超时时间(默认30s)
- 代码中增加容错机制,如果因为续锁失败无法完成操作时,通过补偿就行修正
- 监控redission锁的状态和TTL及时发现续锁失败的问题
8. 如何进行redis性能调优
- redis 提供了慢日志记录功能,可以使用这个功能进行查询哪些命令执行的耗时时间比较长进行调优
- 避免使用比较复杂的命令进行查询O(n)复杂度的查询尽量避免(如果一个get/delete的执行时间比较长,大概率是大key)
- 查看过期策略是不是懒加载,这样大量key过期的话会在后台线程中处理,不会阻塞主线程的执行
- 硬件层面的优化,网络和存储磁盘是否达到了瓶颈
9. 大key中的问题
大key的一般定义(经验值)
- 对于大key redis没有一个显示的定义,只是一个通用术语,用来描述那些在存储和性能方面可能引起问题的键。常见的有字符串,列表哈希表或集合
- key 本身的数据量过大:一个string类型的key他的值有1Mb
- key中成员数量过多:一个zset类型的key成员数量超过10k
key 中成员的数据量过大:一个hash类型的key成员数量虽然只有1k,但是这1k成员的总的value为100Mb
大key的影响
- 让redis的内存占用过高:占用内存较多,可能引起redis内存不足,影响其他键的存储和访问
- 性能下降:对于大key的操作比如读写删除都会消耗更多的CPU和内存资源,从而也会阻塞其他key的读写
- 网络阻塞:每次访问大key都会产生较大的网络开销,如果是一个热key可能造成带宽被打满的问题,也会导致其他的请求延迟甚至超时,从而影响整个系统的吞吐量
- 持久化问题:大key会让数据备份和持久化变得困难和更长的恢复时间
- 数据倾斜:其他的key在节点上分布不均匀,影响负载均衡和集群的性能问题
大key的产生
- 大型的数据结构存储(上面说过)
- 缓存的滥用:将带昂的数据作为单个键的值进行缓存,而不是进行拆分存储
- 数据导入:从外部数据源进行导入数据时,数据量较大且以单个键进行存储
快速找出大key
- 使用redis的scan命令(不会阻塞redis实例的情况下进行遍历,它也可以有效的便利所有的key,跟keys 相比不会阻塞主线程)
- redis 的命令行参数加上 bigkeys参数
redis-cli -h 127.0.0.1 -p 6379 -bigkeys - redis rdb tools,通过分析rdb文件扫描redis中的大key <
rdb -commond memory -bytes 1024 -largest 3 dump.rdb>
优化大key
- 拆分成多个小key
- 优化数据结构:使用string类型的时候使用压缩算法或者使用hash类型存储
- 设置合理的超时时间,避免大key一直堆积
- 使用unlink命令异步删除大key,避免阻塞redis实例
10. redis的多线程问题
redis 6.0中引入了多线程主要原因是为了提高其网络处理的效率,尤其是在网络IO上,实际的命令执行仍然是单线程,主要是解决了大量小请求的场景,提高了IO瓶颈,带宽的利用率,降低请求延迟,充分利用服务器的多核
11. 热点key的处理方案
预先知道热点key
- 将一个key分摊成多个子key,让其分布在不同的节点上(比如商品库存有1000个,目前有个3节点的redis集群,key1分330个在节点A,key2分330个在节点B上,key3分340个在节点C上)
- 限流:防止过多的请求进入(根据实际的压测结果)
go 相关的有标准库的rate 包,
uber-go/ratelimit
不能预先知道的热key
- 添加监控和报警,当一段时间内get次数足够到阈值的时候进行报价,人工介入处理
12. redis中的key过期了会立即删除吗?
不会。
redis采用了一种惰性删除和定期删除相结合的方式来处理过期的key
- 惰性删除:当客户端访问一个键的时候,redis会先检查这个键是否已经过期,如果过期了就会在被访问时删除,这就意味着即使一个键过期了也不会立即删除,而是下次被访问的时候被删除
- 定期删除:redis会周期的随机抽取一部分设置了过期时间的key进行检查,并删除过期的key。
有惰性删除为什么还需要定期删除?如果只有惰性删除的话,如果有大量key不被访问他会一直占用内存空间,导致redis节点内存不足
redis中如果没有设置过期时间,key可能会被删除吗?
可能会的。
在此前的内存淘汰策略下,如果是当redis已使用内存超过maxmemory限定时,会主动触发清理策略,其中有3中策略会对所有的key做处理
- allkeys-random:从所有的键值对随机取一个删除数据
- allkeys-lru:最近最少使用(多数情况下使用)
- allkeys-lfu:最近访问次数较少的key(热点key较多的时候使用)
13. 系统时钟对于redis的影响
几台resis服务器如果系统时钟有不一致,可能会让大量的key过期被清理,进而导致缓存雪崩。
14. 由于网络抖动引起redis主备频繁切换
可以在redis节点层面通过修改cluster-node-timeout来调整超时时间
15. 3个节点的redis集群,如果一个节点挂了会不会脑裂?如果不脑裂怎么选主
主观下线到客观下线
- 主观下线(PFail):集群中每个节点会通过 “Gossip 协议” 定期向其他节点发送 “心跳包”(包含自身状态、已发现的故障节点信息)。当节点 B(或 C)长时间(超过cluster-node-timeout配置,默认 15 秒)没收到主节点 A 的心跳包时,会将 A 标记为 “主观下线”(即 “我认为 A 挂了”),并将这个信息通过 Gossip 协议同步给其他节点(如 B 告诉 C “我觉得 A 挂了”)。
- 客观下线(Fail):当节点 C 收到 B 发送的 “A 主观下线” 信息后,会检查自己是否也无法联系 A。若 C 也无法联系 A,会统计 “认为 A 下线的节点数量”—— 当支持 “A 下线” 的节点数达到 “法定票数”(3 节点集群中为 2 票,B 和 C 各 1 票)时,集群会将 A 正式标记为 “客观下线”(即 “大家一致认为 A 挂了”),并触发选主流程。
- 优先级规则:① 从节点的slave-priority配置(数值越小优先级越高,默认 100,0 表示无资格);② 从节点与原主节点 A 的 “数据同步进度”(同步越完整,优先级越高);③ 从节点的 ID(ID 越小,优先级越高,用于打破平局)。
以上都是个人总结,由于个人能力有限理解的未必全部正确,麻烦大佬指正!
感谢感谢
