简略总结#
- 认识 Redis
- 内存数据库
- 常用于缓存、消息队列、分布式锁
- 原子性操作
- Redis 值的数据结构(五种常见)
- String 字符串:缓存对象、常规计数、分布式锁、共享 session 信息等
- List 列表(链表):消息队列等
- Hash 哈希(无序散列表):缓存对象、购物车等
- Set 集合(字符串的无序集合):聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等
- Zset 有序集合:排序场景,比如排行榜、电话和姓名排序等
- …(还有 4 种,先不写了)
- Redis 线程模型
- 单线程是指:
- [接收客户端请求> 解析 > 数据读写 > 发送数据给客户端] 由主线程完成。
- redis 程序并不是单线程
- 6.0 前后
- 前 - 单线程:可维护高,简单,无锁问题
- 后 - 多线程:采用多个 I/O 线程处理网络请求(因为网络 I/O 性能瓶颈)
- 总结:执行命令还是单线程,引入多线程为了提供网络 I/O 处理性能
- 单线程是指:
- Redis 持久化
- AOF 日志:执行命令后,将命令写入 AOF 文件,重启时加载该文件
- 策略:
- Always:执行完命令后同步写入
- Everysec:先将命令写入内核缓冲区,然后每秒将缓冲区的内容写入磁盘
- NO:由操作系统控制何时将缓冲区的内容写进磁盘
- 过大后:触发 AOF 重写机制
- 缺点:文件过大,加载时间长
- 策略:
- RDB 快照:将某一时刻的内存数据(频率可控),以二进制的方式写入磁盘
- 方式:
- save 命令:通过主线程生成,会阻塞线程
- bgsave 命令:通过创建子进程生成,不会阻塞线程
- 缺点:数据丢失风险高
- 方式:
- 混合:4.0 新增,解决 AOF 恢复慢,RDB 数据丢失风险高,集合两者优点
- AOF 日志:执行命令后,将命令写入 AOF 文件,重启时加载该文件
- Redis 集群
- 方式:
- 主从复制:主同步到从(首次全量, 后续增量),主写从读
- 哨兵模式:在主从复制的基础上,增加哨兵监控主、从、哨兵的健康状态,自动故障转移
- 切片集群:将数据分布到不同的服务器,降低单点依赖,提高读写性能
- 集群脑裂:网络分区(不同网络下)导致双主
- 解决:
- min-slaves-to-write x,限制至少 x 个从连接到主,低于时主禁写
- min-slaves-max-lag x,限制主从数据同步的延迟小于 x 秒,超过时主禁写
- 解决:
- 方式:
- Redis 过期删除与内存淘汰
- 过期删除:对比过期字典,未过期返回数据,已过期删除
- 删除策略:
- 惰性删除:访问 key 时检测是否过期,是则删除 key 返回 null
- 定期删除:定时随机取一定数量的 key 检查,删除其中过期的
- 流程:抽取 key → 检查是否过期,并删除 → 过期数量超过 25% 时,重复之前步骤 → 小于 25% ,等待下一轮检查
- 持久化时过期键处理:
- AOF:
- 对过期键添加删除指令到 AOF 文件
- 加载时过期键不会写入 AOF
- RDB:
- 生成 RDB 时忽略过期键
- 加载时主过期不加载,从全部加载
- AOF:
- 主从过期键处理:从依赖主(不扫描过期键),主检查删除过期键后同步到从
- 内存满载:达到阈值触发内存淘汰机制,配置 maxmemory
- 内存淘汰机制策略:共八种,归类为 [不进行淘汰] 和 [进行淘汰] 两类
- 不进行淘汰:超过阈值,直接返回错误
- 进行淘汰:分为 [在设置了过期时间的数据中] 和 [所有数据] 内进行淘汰两类,最好的是设置了过期时间且使用率最低的键
- Redis 缓存设计
- 雪崩:为了数据一致性,对缓存的数据设置过期时间,过期后从数据库请求并重新更新缓存,当大量键过期,请求直接到 DB 导致宕机。或 Redis 宕机。
- 解决:
- 过期:
- 打散过期时间
- 互斥锁:同一时间仅一个业务线程更新缓存
- 双 key(主设过期,备永久)
- 宕机:
- 服务熔断
- 请求限流
- 高可用
- 过期:
- 解决:
- 击穿:热点数据过期,请求直接到 DB(雪崩的子集情况)导致宕机
- 解决:
- 互斥锁:同一时间仅一个业务线程更新缓存
- 不设置过期时间
- 解决:
- 穿透:数据不在缓存中,也不在 DB 中,一般是业务误操作和恶意攻击两种
- 解决:
- 限制非法请求
- 缓存默认值
- 使用布隆过滤器判断数据是否存在,避免查 DB
- 解决:
- 动态缓存热点数据:通过数据最新访问时间排序,留下 top 数据
- 常见缓存更新策略:
- Cache Aside 旁路缓存(实际开发时使用,最常用,适合读多写少):应用程序直接与 [数据库、缓存] 交互,负责维护缓存
- 写策略:先更新 DB,再删除 Cache(不能改变顺序,否则导致数据不一致,删除是懒加载思想)
- 读策略:
- 有缓存,直接返回数据
- 没缓存,读 DB,写入 Cache,返回数据
- Read/Write Through 读穿 / 写穿
- Write Back 写回:有数据丢失风险
- Cache Aside 旁路缓存(实际开发时使用,最常用,适合读多写少):应用程序直接与 [数据库、缓存] 交互,负责维护缓存
- 保证 Cache 和 DB 数据一致性:更新数据库 + 更新缓存(使用分布式锁,加上较短的过期时间)
- 雪崩:为了数据一致性,对缓存的数据设置过期时间,过期后从数据库请求并重新更新缓存,当大量键过期,请求直接到 DB 导致宕机。或 Redis 宕机。
- Redis 实战
- 延迟执行队列:使用 ZSet(有序集合),其中 Score 属性存储延迟执行的时间,再利用 zrangebysocre 查询并对比时间找到待处理的
- Redis 大 key:指 key 的 value 很大
- 条件:
- String 类型的值大于 10 KB
- Hash、List、Set、ZSet 类型的元素超过 5k
- 影响:
- 客户端超时阻塞:redis 单线程执行命令,操作大 key 耗时,阻塞线程
- 引发网络堵塞:数据 * 访问量 = 产生大流量
- 阻塞工作线程:del 大 key,阻塞工作线程
- 内存分布不均:集群模型在 slot 分片均匀时,出现数据和查询倾斜情况,部分有大 key 的节点占用内存多,QPS 较大
- 排查:
redis-cli --bigkeys
- 注意:
- 在从节点执行,因为会阻塞线程
- 无从时在低峰时间执行,可使用
-i
控制扫描间隔,降低性能影响
- 注意:
SCAN
:先用 SCAN 扫描,再用 TYPE 获取 key 的类型- String 类型:使用 STRLEN 获取长度
- 集合类型:
- 平均大小 * 数量
MEMORY USAGE
查询键占用空间(Redis 4.0+)
- https://github.com/sripathikrishnan/redis-rdb-tools:解析 RDB 文件
- 删除:直接删除会释放大量内存,同时会造成空闲内存块链表操作时间增加,从而 Redis 主线程阻塞,致使各种请求超时,最终 Redis 连接耗尽,产生各种异常
- 方法:
- 分批次
- 大 Hash:使用
hscan
每次获取 100 个字段,再用hdel
one by one 删除 - 大 Set:使用
sscan
每次扫描 100 个元素,再用srem
one by one 删除 - 大 ZSet:使用
zremrangebyrank
每次删除 top 100 个元素
- 大 Hash:使用
- 异步(Redis 4.0+):用
unlink
代替del
- 方式:
- 主动调用
unlink
命令 - 配置参数,满足条件时异步删除
- 主动调用
- 方式:
- 分批次
- 方法:
- 条件:
- Redis 管道(Pipeline):是客户端而非服务器提供的一种批处理技术,一次处理多个 Redis 命令,解决多个命令执行时的网络等待
- Redis 事务不支持回滚:不支持事务运行时错误的事务回滚,只有
DISCARD
放弃事务执行- 不支持原因:
- 作者认为生产环境很少出错,没必要开发
- 事务回滚较复杂与 Redis 简单高效设计不符
- 不支持原因:
- 分布式锁:在分布式环境下并发控制的一种机制,用于控制资源在同一时刻只能被一个应用使用
-
原理:
SET
命令的NX
参数可以实现 [key 不存在才插入]- key 不存在:显示插入成功,表示加锁成功
- key 存在:显示插入失败,表示加锁失败
-
加锁条件:
- 加锁是对锁变量进行 [读取、检查和设置] 三个操作,需要原子操完成,所以使用
SET
+NX
- 锁变量需要设置过期时间,使用
EX/PX
选项,单位为毫秒 - 锁变量的值使用客户端的唯一标识区分来源
# 满足三条件的命令: SET lock_key unique_value NX PX 10000 - lock_key: key 值 - unique_value: 客户端唯一标识 - NX: lock_key 不存在时才进行设置 - PX 10000: 设置 lock_key 过期时间为 10 秒
- 加锁是对锁变量进行 [读取、检查和设置] 三个操作,需要原子操完成,所以使用
-
解锁:有两个操作,需要 Lua 脚本保证原子性,即先比较值是否相等,再删除键
// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
-
优点:
- 性能高
- 实现方便
- 避免单点故障(Redis 集群部署)
-
缺点:
- 超时时间不好设置:过长影响性能,过短失去锁保护
- Redis 主从复制模式中数据是异步复制的,可能导致分布式锁的不可靠性。
-
集群下的可靠性:官方设计了一个分布式锁算法 Redlock(红锁)。基于多个 Redis 节点的分布式锁,官方推荐至少 5 个孤立的主节点
- 原理:让客户端和多个独立的 Redis 节点依次请求申请加锁,和半数以上的节点加锁成功,就认为客户端加锁成功
- 过程(加锁成功 = 超半数成功获取锁 & 总耗时 < 锁有效时间):
- 客户端获取当前时间(t1)
- 客户端按顺序依次向 N 个节点执行加锁
- 客户端从超过半数(大于等于 N/2+1)的节点上成功获取到了锁,就再次获取当前时间(t2),然后计算耗时(t2-t1),t2-t1 < 锁的过期时间,认为加锁成功
-