文章目录
- Redis 基础
- Redis 是什么,有哪些特点
- 为什么要使用 Redis 而不仅仅依赖 MySQL
- Redis 是单线程吗
- Redis 单线程为什么还这么快
- Redis 数据类型和数据结构
- 五种基本数据结构及应用场景
- 其他数据类型
- Redis 底层数据结构
- Redis 持久化
- 数据不丢失的实现
- AOF 日志
- RDB 快照
- 混合持久化
- AOF 和 RDB 的选择
- AOF 重写的执行过程
- RDB 快照的执行过程
- 混合持久化的执行过程
- Redis 功能和高可用性
- 主从复制
- 全量复制
- 增量复制
- 全量复制使用 RDB 的原因
- 哨兵模式
- 切片集群
- 集群脑裂
- 过期删除
- 内存淘汰策略
- LRU 和 LFU 算法
- Redis缓存
- 缓存雪崩
- 应对策略
- 缓存击穿
- 缓存穿透
- 常见原因及处理方法
- 保证数据库和缓存的一致性
- 写策略
- 读策略
- 保证删除缓存操作一定能成功
- 重试机制
- 订阅BINLog
Redis 基础
Redis 是什么,有哪些特点
Redis 是一个开源的基于内存的数据库,具有以下特点:
- 基于内存:读写速度非常快,适用于缓存、高性能应用场景。
- 持久性:支持将数据持久化到磁盘,防止数据丢失。
- 多数据结构:支持字符串、哈希表、列表、集合、有序集合等。
- 原子性操作:保证操作的原子性,确保数据一致性。
- 分布式特性:提供多种集群方案,提高可扩展性和可用性。
为什么要使用 Redis 而不仅仅依赖 MySQL
- 高性能:读写速度快,适用于频繁读写的数据。
- 高并发:高效处理并发请求,适用于快速响应的场景。
- 丰富的数据结构:处理特定类型数据和实现特定功能更灵活。
- 减轻数据库压力:缓存常用数据,减轻 MySQL 等数据库的压力。
Redis 是单线程吗
Redis 的网络请求模块是单线程的,但其他模块使用多线程,以提高性能。
Redis 单线程为什么还这么快
- 基于内存存储:内存访问速度快。
- 非阻塞单线程:避免多线程竞争和同步开销。
- 高效的数据结构:如 STRING、LIST、HASH 等。
- I/O 多路复用:同时监听多个 Socket,实现高效并发处理。
Redis 数据类型和数据结构
五种基本数据结构及应用场景
- String(字符串):缓存对象、计数、分布式锁。底层的数据结构实现主要是SDS(简单动态字符串)
- List(列表):消息队列。底层数据结构是双向链表或者压缩列表
- Set(集合):点赞、共同关注、抽奖活动。底层由哈希表或整数集合(都是整数且元素个数小于 512个)实现
- Hash(哈希):缓存对象、购物车。底层由哈希表或压缩列表(哈希类型元素个数小于512个,所有值小于 64 字节)实现
- Zset(有序集合):排行榜。底层数据结构压缩列表或跳表
其他数据类型
- BitMap:签到统计、用户登陆态判断。
- HyperLogLog:基数统计,如网页面 UV 计数。不准确。优点在输入元素的 数量或者体积非常非常大时,所需的内存空间总是固定的、并且很小
- GEO:存储地理位置信息。
- Stream:消息队列。自动生成全局唯一ID,支持以消费组形式消费数据
Redis 底层数据结构
- SDS(简单动态字符串):动态扩容,支持字符串和二进制数据。
- 双端链表:快速插入和删除。无法很好的利用CPU缓存,内存开销比较大
- 压缩列表:压缩列表是一种紧凑的、可变⻓度,由连续内存块组成的顺序型数据结构。在内存使用效率上比较高
- 哈希表:快速查询,采用了拉链法解决哈希冲突。
- 整数集合:高效存储整数值。二进制表示
- 跳表:多层有序链表,查找复杂度 O(logN)。
- quicklist:双向链表+压缩列表的组合。quicklist 就是一个链表,而链表中的每个元素又是一个压缩列 表
- listpack:只记录当前节点的长度,避免连锁更新问题。
Redis 持久化
数据不丢失的实现
Redis 通过以下三种方式实现数据持久化,将数据存储到磁盘,确保在重启时可以恢复数据:
- AOF 日志:记录每个写操作命令,以日志形式存储。
- RDB 快照:定期将内存数据快照保存为二进制文件。
- 混合持久化:结合 AOF 和 RDB 的优点,提供更快的恢复速度和较少的数据丢失。
AOF 日志
- 机制:先执行命令,把数据写入内存,记录每个写操作命令,重启时重放日志恢复数据。
- 优点:避免记录错误命令,不阻塞当前写操作。
- 写回策略:
- Always:每次写操作后立即写回磁盘,最安全,但性能较低。
- Everysec:每秒写回一次,折中方案,性能和数据安全性较好。
- No:由操作系统控制写回时机,性能最高,但数据安全性较低。
- 重写机制:扫描数据中所有的键值对数据,然后为每一个键值对生成一条写操作命令,接着将该命令写入到新的 AOF 文件,重写完成后,就替换掉现有的 AOF 日志。重写的过程是由后台子进程完成的,这样可以使得主进程可以继续正常处理命令。
- 触发时机:
- 手动执行
bgrewriteaof
命令。 - 主从复制完成。
- AOF 重写被设置为待调度执行。
- AOF 文件大小比例超出阈值,以及 AOF 文件的大小绝对值超出阈值。
- 手动执行
- 触发时机:
RDB 快照
- 机制:记录某一时刻的内存数据快照,以二进制方式保存到磁盘。
- 优点:数据恢复速度快,文件体积小。
- 缺点:频率太低会丢失数据,频率太高会影响性能。
- 生成方式:
- save:在主线程执行,可能阻塞 Redis 的其他操作。
- bgsave:在子进程执行,避免阻塞主线程,适合大规模数据保存。
混合持久化
- 机制:结合 AOF 和 RDB,先将内存数据以 RDB 方式写入 AOF 文件,然后将增量命令以 AOF 方式写入。文件前半部分是 RDB 格式的全量数据(加载速度快),后半部分是 AOF 格式的增量数据(数据丢失更少)。
- 优点:快速恢复数据,降低数据丢失风险,结合了 RDB 的快速加载和 AOF 的持久性优势。
- 缺点:AOF 文件可读性变差,兼容性较差(Redis 4.0 之前版本不支持)。
AOF 和 RDB 的选择
- AOF 优点:持久性更好,丢失数据较少,但恢复速度较慢。
- RDB 优点:数据恢复速度快,但快照频率难以掌控。
- 混合持久化:综合两者优点,适合需要快速恢复数据且不希望数据丢失的场景。
AOF 重写的执行过程
- 启动重写:手动或触发条件满足时,Redis 启动 AOF 重写。
- 生成新文件:子进程遍历所有数据,生成新的 AOF 文件。
- 处理增量数据:在生成新文件过程中,增量数据被记录到缓冲区。
- 替换旧文件:新 AOF 文件生成完毕后,增量数据写入新文件,替换旧 AOF 文件。
RDB 快照的执行过程
- save 命令:主线程执行,阻塞其他操作。
- bgsave 命令:子进程执行,避免主线程阻塞。
- 文件生成:快照生成的文件包含当前内存中的所有数据。
- 文件加载:重启时加载 RDB 文件恢复数据。
混合持久化的执行过程
- 启动重写:AOF 重写过程中,子进程将内存数据以 RDB 方式写入 AOF 文件。
- 记录增量数据:主线程处理的操作命令记录到重写缓冲区,以 AOF 方式写入新文件。
- 文件替换:新文件生成后,通知主进程将其替换旧文件。新文件前半部分为 RDB 格式数据,后半部分为 AOF 格式数据。
- 快速恢复:重启时,先加载 RDB 部分,快速恢复大部分数据,再加载 AOF 部分,确保数据一致性。
Redis 功能和高可用性
主从复制
Redis 通过主从复制模式实现数据的高可用性,保证数据副本的一致性。主从复制模式下,读写操作分离:
- 读操作:主库和从库都可以接收。
- 写操作:先在主库执行,然后同步到从库。
全量复制
- 建立连接:从库与主库建立连接,主库确认后开始同步。
- 主库发送数据:主库发送当前数据的 RDB 快照及增量写命令。
- 从库接收数据:从库接收 RDB 文件和增量写命令,更新数据状态。
增量复制
从库发生宕机,重新连接后数据的同步操作(增量复制)
- 从库宕机重连:从库发生宕机,主库会把断连期间收到的写操作命令,写到repl_backlog_buffer中;从库重新连接主库后,发送 psync 命令并传递当前的复制偏移量slave_repl_offset。
- 数据同步:主库根据从库的复制偏移量,决定进行增量复制还是全量复制(从库相差 > repl_backlog_buffer)。
全量复制使用 RDB 的原因
- 压缩数据:RDB 文件内容是经过压缩的二进制,文件较小。
- 性能影响小:AOF 文件需要选择文件刷盘的策略,选择不当会影响性能,而 RDB 文件只在备份和同步时生成。
哨兵模式
Redis 的哨兵模式用于监控主从服务器,提供主从节点故障转移的功能,实现自动故障转移和高可用性。哨兵的功能包括:
- 监控:定期检查主从服务器状态。
- 通知:发现问题时通知相关人员。
- 故障迁移:自动主从切换。
- 配置管理:统一管理主从地址。
切片集群
当缓存数据量大到一台服务器无法承载时,使用 Redis 切片集群将数据分布在多个服务器上,降低系统对单主机点的依赖,提高读写性能。切片集群的工作原理:
- 数据分片:将数据集划分为 16384 个槽,每个节点管理部分槽的数据。
- 节点间通信:节点通过 gossip 协议互相通信,保持一致性。
- 数据分布:根据键的 CRC16 哈希值确定数据属于哪个槽,并存储到相应节点。
- 故障检测:哨兵机制检测节点状态,重新分配槽并选举新主节点。
- 客户端路由:客户端根据键的哈希值确定目标节点,直接与负责数据的节点通信。
- 数据复制:每个槽有一个主节点和若干个从节点,数据写入主节点后异步复制到从节点。
集群脑裂
脑裂是指在网络故障时,主节点的网络突然发生了问题与所有的从节点都失联,但与客户端正常通信,这些数据被主节点缓存到了缓冲区里。哨兵 也发现主节点失联了,就会在从节点中选举出一个leader作为主节点,导致出现多个主节点。解决方法:
- 主节点写保护:主节点在发现从节点断开或通信超时数量少于阈值时,禁止写操作。
- 从节点配置:设置主节点需要从节点的 ACK 消息数量和延迟限制,确保数据一致性。
过期删除
Redis 支持为键设置过期时间,自动删除过期键值对,采用以下删除策略:
- 定时删除:设置定时事件,到时间自动删除键,占用 CPU 时间多。
- 惰性删除:不主动删除,访问键时检查其是否过期,节省 CPU 时间,但可能浪费内存。
- 定期删除:每隔一段时间随机抽取部分键进行检查和删除,均衡 CPU 和内存使用。
Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡
内存淘汰策略
当内存不足时,Redis 会根据配置的淘汰策略删除一些键以释放内存。常见策略:
- NoEviction:不进行数据淘汰,内存不足时返回错误。
- VolatileTTL:优先淘汰更早过期的键。
- VolatileLRU:对带过期时间的键使用 LRU 策略,其他键使用 NoEviction 策略。
- VolatileRandom:对带过期时间的键随机淘汰,其他键使用 NoEviction 策略。
- VolatileLFU:对带过期时间的键使用 LFU 策略,其他键使用 NoEviction 策略。
- AllKeysLRU:根据最近最少使用原则淘汰最久未使用的键。
- AllKeysRandom:随机选择一个键进行淘汰。
- AllKeysLFU:根据最少频繁使用原则淘汰最少使用的键。
可以使用config get maxmemory-policy
命令,来查看当前 Redis 的内存淘汰策略
LRU 和 LFU 算法
- LRU(Least Recently Used):淘汰最近最少使用的对象。
- 实现:随机取若干值,淘汰最久未使用的对象。
- LFU(Least Frequently Used):淘汰使用频率最低的对象。
- 实现:维护使用计数,淘汰使用计数最低的对象。
区别:
- LRU:关注最近访问情况,认为最近访问的对象更可能被再次访问。
- LFU:关注使用频率,认为使用频率低的对象未来访问概率低。
Redis缓存
由于用户请求频繁访问数据库,直接访问数据库可能导致数据库崩溃,因此常常使用Redis作为数据库的缓存层。
缓存雪崩
缓存雪崩:某个时间点,缓存中的大量数据同时失效,大量请求涌向数据库,导致数据库压力剧增。原因可能是缓存过期时间设置相近或Redis故障。
应对策略
-
大量数据同时过期
- 均匀设置过期时间:加随机数,避免大量数据同一时间失效。
- 使用互斥锁:确保同一时间只有一个请求构建缓存。
- 后台线程定时更新缓存:让缓存设置“永久有效”。
-
Redis故障宕机
- 服务熔断或请求限流:减少直接对数据库的压力。
- 构建Redis高可靠集群:通过主从节点切换确保服务连续。
缓存击穿
缓存击穿:热点数据过期时,大量并发请求查询该数据,导致直接访问数据库,增加数据库负载。可以通过互斥锁或后台更新缓存来解决。
缓存穿透
缓存穿透:请求的数据既不在缓存中,也不在数据库中,导致所有请求直接访问数据库,增加负载。
常见原因及处理方法
- 业务误操作或恶意攻击。
- 对非法请求做限制。
- 缓存中设置空值或默认值。
- 使用布隆过滤器判断数据是否存在。
保证数据库和缓存的一致性
Cache Aside策略:
写策略
- 更新数据库后删除缓存。
读策略
- 缓存命中返回数据;未命中从数据库读取数据后写入缓存并返回。
为了高缓存命中率,可以采用更新数据库加更新缓存的方法,但需解决并发导致的一致性问题,例如加分布式锁或设置较短的缓存过期时间。
保证删除缓存操作一定能成功
重试机制
引入消息队列,应用删除失败从消息队列重新读取数据再进行删除,重试多次未成功则报错。
订阅BINLog
订阅binlog日志,准确删除缓存中的数据。通过模拟MySQL从节点获取binlog,解析后删除缓存数据。