Redis
约 2115 字大约 7 分钟
2024-10-23
一、数据结构与底层原理
1. Redis 的五大基础数据类型是?其底层数据结构是?
| 数据类型 | 典型业务场景 | 底层数据结构 |
|---|---|---|
| String | 验证码、计数器(点赞/库存)、分布式锁 | 简单动态字符串(SDS) |
| Hash | 对象数据存储(如用户信息、购物车) | listpack(Redis 7.0 替换了 ziplist)或 hashtable |
| List | 简单的消息队列、时间轴(Timeline)展示 | quicklist(结合了双向链表和压缩列表的优势) |
| Set | 标签系统、抽奖去重、交并集计算(共同好友) | intset(整数集合)或 hashtable |
| ZSet | 排行榜、延时队列、滑动窗口限流 | listpack 或 hashtable + 跳跃表(Skip List) |
2. Redis为什么这么快?为什么单线程依然能保持极高的读写性能?
- 内存主导:Redis 是内存式数据库,内存访问比硬盘访问至少快1000倍。
- 线程模型:核心工作线程是单线程,避免了多线程频繁的上下文切换和锁竞争带来的性能消耗。
- 网络模型:Redis 使用了 IO 多路复用(IO multiplexing),单个线程可以高效地处理并发的网络连接请求。
- 数据结构:Redis 使用了简单而高效的数据结构(跳跃表等)
3. 什么是 BigKey 和 HotKey ?在生产环境中如何排查和处理?
BigKey
BigKey 指 Value 的数据量过大,读写时会导致网络拥塞;因为 Redis 核心是单线程,会严重阻塞主线程。
解决:业务层面进行数据拆分(大集合拆小集合);使用 unlink 命令异步删除,避免阻塞。
HotKey
HotKey 指极短时间内被超高频访问的 Key,请求集中打在单个 Redis 节点,导致单节点 CPU 和网卡被打满,甚至引发宕机。
解决:引入本地缓存(如 Caffeine/Guava)提前拦截;对 HotKey 增加随机后缀,打散分布到不同的 Redis 节点上。
4. Redis 和 memcached 的区别?
Redis 是远程服务,独立部署;memcached 集成在应用中,是本地服务。
5. Redis 的持久化机制有哪些?各自有什么优缺点?
Redis 有 RDB(内存快照)机制,每隔一段时间,Redis 就会快照内存里的所有数据,存成一个文件。这种方式恢复速度极快,备份文件体积小,但容易丢失增量数据。
同时,Redis 也有 AOF(追加日志)机制,每次执行写入命令时,Redis 都会做日志记录。恢复时逐条回放日志中的写命令。这种方式备份体积随时间膨胀较快(需通过 AOF Rewrite 机制压缩重写)。
实际生产环境中,通常会将 RDB 和 AOF 结合起来使用。
二、缓存异常与一致性
1. 什么是缓存雪崩、缓存穿透和缓存击穿,以及对应的解决方案。
- 缓存穿透:数据根本不存在(查不到)。
- 缓存击穿:一个极度热门的数据刚好过期(单点突破)。
- 缓存雪崩:一大批数据同时过期,或 Redis 宕机(全面崩溃)。
参考:缓存的三大经典问题
2. 布隆过滤器是怎么解决缓存穿透的?它的底层工作原理是什么?
布隆过滤器(Bloom Filter)可以理解为一个不太精确的 Set,当它说某个值存在,这个值不一定存在,当它说不存在,那就肯定不存在。
Redis 在 4.0 支持布隆过滤器插件。
Redis的布隆过滤器底层是一个位数组 + 几个 hash 函数。添加元素时,这几个 hash 函数都会对该值计算一个 hash 值,并对数组长度取模得到一个下标(每个hash函数算到的下标都不一样)。获取元素时,需要该值对这些 hash 函数 hash 后的下标都为 1,才能判定该值存在。
布隆过滤器可以用来不太准确地判断一个值是否存在,以及准确地判断一个值不存在,例如新闻客户端的推送(以及推送过的就放进布隆过滤器,存在了就不重复推送了)。在 Hbase 等 NoSQL 里用来过滤掉大量不存在的 row 请求,再去磁盘查询。邮箱系统中用来判断垃圾邮件。互联网系统中防范穿透攻击。
3. 如何保证数据库与 Redis 缓存的“双写一致性”?
业界主流的实践是 旁路缓存模式(Cache Aside Pattern)。即面对写操作,通用的做法是删除缓存,而不是更新缓存。因为部分缓存值可能是经过复杂计算得出的,频繁更新代价较高,且并发更新时容易引发数据覆盖问题。
为什么是“删缓存”而不是“更新缓存”?
- 这是一种懒加载,提高性能,将刷新缓存的计算延迟到了下一次读请求;
- 避免了高并发环境下,两个线程在执行“更新数据库+更新缓存”导致的线程安全问题。
先操作数据库,还是先操作缓存?
业界主流是先更新数据库,再删缓存。虽然极低概率下会发生不一致,但仍然是首选基准方案。
如果一致性要求极高,考虑基于 Binlog 的异步同步。例如,用中间件(如 Canal)监听 Binlog 变更,解析出具体的数据,然后将这些变更推送到 MQ 或直接异步投递给专门的缓存更新服务,由该服务负责删除或更新 Redis 里的数据。
如果缓存一致性要求不高,更简单的方案是,定时任务定期刷新缓存。
如果删除缓存失败了怎么办?
引入重试机制。删除缓存失败时,将这条消息发送到 MQ 进行补偿。保证最终一致性。
三、高可用与持久化机制
1. Redis 的持久化机制有哪些?对比优缺点和使用场景
Redis 有 RDB(内存快照)机制,每隔一段时间,Redis 就会快照内存里的所有数据,存成一个文件。这种方式恢复速度极快,备份文件体积小,但容易丢失增量数据。
同时,Redis 也有 AOF(追加日志)机制,每次执行写入命令时,Redis 都会做日志记录。恢复时逐条回放日志中的写命令。这种方式备份体积随时间膨胀较快(需通过 AOF Rewrite 机制压缩重写)。
实际生产环境中,通常会将 RDB 和 AOF 结合起来使用。
2. Redis 的键过期删除策略和内存淘汰策略分别有哪些?
过期策略(针对的是设置了有效期的 Key)
Redis 采用 定期 + 惰性 组合:
- 惰性删除: 只有当访问某个 Key 时,才去检查它是否过期,过期则删除。优点是省 CPU,缺点是废内存(长期不访问的过期键会驻留)。
- 定期删除: Redis 默认每秒执行 10 次后台任务,随机抽取部分设置了过期时间的 Key 进行检查并删除。这是一种 CPU 和内存消耗的折中方案。
淘汰策略(内存满了怎么办)
- noeviction: 默认策略,不淘汰任何数据,直接拒绝所有写操作并报错。
- LRU 系列(基于访问时间): 优先淘汰最近最少使用(LRU) 的 Key。
- LFU 系列(基于访问频率): 优先淘汰最不经常使用(LFU) 的 Key(相比 LRU,LFU 能更好地保留真正的 HotKey )
- TTL 系列: 优先淘汰剩余存活时间(TTL)最短的 Key。
3. Redis 主从同步的机制和全量/增量复制过程是怎么样的?
全量复制(初次同步或严重脱节):
- 从节点向主节点发送 psync 命令。
- 主节点执行 bgsave 生成 RDB 文件,并发给从节点。从节点清空本地数据并加载 RDB。
- 主节点将生成 RDB 期间产生的新写命令(存放在 replication buffer 中)发送给从节点执行。
增量复制(网络断开重连):
- 主节点维护了一个环形缓冲区(repl_backlog_buffer),记录最近的写命令和偏移量(offset)。
- 从节点重连后,汇报自己的 offset。如果该 offset 还在主节点的环形缓冲区内,主节点只需把缺失的那部分命令发过去。
- 如果断开太久,offset 已经被覆盖,则只能退化为 全量复制。
4. 哨兵模式(Sentinel)和集群模式(Cluster)的区别是什么?
哨兵模式(Sentinel):
- 核心定位: 解决 主从复制(Replication) 无法自动故障转移的问题(即高可用 HA)。
- 工作原理: 独立的哨兵集群监控 Redis 节点。如果主节点宕机,哨兵通过类似 Raft 算法的投票机制,选举出一个从节点晋升为新主节点,并通知客户端更新连接。
- 局限性: 只有主节点能处理写请求,无法解决单节点内存容量和写并发的瓶颈。
集群模式(Cluster):
- 核心定位: 同时解决高可用(自动故障转移)和水平扩容(分布式分片存储)问题。
- 工作原理: 采用 哈希槽(Hash Slot) 机制(共 16384 个槽位)。数据根据 Key 的哈希值分配到不同的槽,槽位平均分布在多个主节点上。每个主节点都有自己的从节点。
- 故障转移: 去中心化设计,各主节点通过 Gossip 协议互相 PING。如果某个主节点挂了,集群内其他主节点会协作将其从节点升级为主节点。
四、高级应用与并发处理
如何使用 Redis 实现高可用的分布式锁?如何避免锁超时和误释放问题?
核心是使用 NX 参数保证互斥(键不存在时才能设置成功) 和 PX 参数设置过期时间以防止宕机导致的死锁。
SET key value NX PX milliseconds超时误释放问题
如果 A 执行超时,B 持有锁,待超时的 A 执行完毕时,A 会将 B 的锁误释放掉。
建议将 Value 设置为客户端的唯一标识(如 UUID + 线程 ID)。释放锁时,必须使用 Lua 脚本保证“判断标识是否匹配”和“删除锁”这两个操作的原子性。
超时业务未执行完问题
如果锁超时,业务未执行完。主流使用 Redisson 客户端,它内部提供了一个 看门狗(Watch Dog) 。只要客户端持有锁,看门狗会每隔一段时间(默认 10 秒)自动将锁的超时时间重新延长,直到业务执行完毕后由客户端显式释放。
Redis 事务的工作机制是什么?支持回滚吗?
开启事务后,命令会被放入一个队列中,直到调用 EXEC 才会依次执行。执行期间不会被其他客户端的命令插队。
Redis 的事务不保证强原子性,运行时错误不支持回滚。原因是回滚机制与 Redis 追求极致高效的设计初衷相悖。
