Redis
- [1. Redis 技术文档整理](#1. Redis 技术文档整理)
-
- [1.1 Redis 特性介绍](#1.1 Redis 特性介绍)
-
- [1.1.1 核心特性](#1.1.1 核心特性)
- [1.1.2 应用场景](#1.1.2 应用场景)
- [1.2 环境搭建](#1.2 环境搭建)
-
- [1.2.1 Ubuntu 系统安装 Redis 5](#1.2.1 Ubuntu 系统安装 Redis 5)
- [1.2.2 配置文件](#1.2.2 配置文件)
- [1.2.3 客户端工具](#1.2.3 客户端工具)
- [1.2.4 注意事项](#1.2.4 注意事项)
- [1.3 通用命令](#1.3 通用命令)
-
- [1.3.1 基础操作命令](#1.3.1 基础操作命令)
- [1.3.2 全局命令](#1.3.2 全局命令)
- [1.3.3 Redis 过期策略](#1.3.3 Redis 过期策略)
-
- [1.3.3.1 定时器实现方案](#1.3.3.1 定时器实现方案)
- [1.4 数据结构](#1.4 数据结构)
-
- [1.4.1 类型查询命令](#1.4.1 类型查询命令)
- [1.4.2 核心数据结构(类比 C++ 容器)](#1.4.2 核心数据结构(类比 C++ 容器))
- [1.4.3 注意事项](#1.4.3 注意事项)
- [1.5 Redis 单线程模型](#1.5 Redis 单线程模型)
-
- [1.5.1 单线程高性能原因](#1.5.1 单线程高性能原因)
- [1.6 各数据结构详细命令](#1.6 各数据结构详细命令)
-
- [1.6.1 string 类型](#1.6.1 string 类型)
-
- [1.6.1.1 核心命令](#1.6.1.1 核心命令)
- [1.6.1.2 编码方式](#1.6.1.2 编码方式)
- [1.6.1.3 注意事项](#1.6.1.3 注意事项)
- [1.6.2 hash 类型](#1.6.2 hash 类型)
-
- [1.6.2.1 核心命令](#1.6.2.1 核心命令)
- [1.6.2.2 内部编码](#1.6.2.2 内部编码)
- [1.6.2.3 应用场景](#1.6.2.3 应用场景)
- [1.6.3 list 类型(双端队列)](#1.6.3 list 类型(双端队列))
-
- [1.6.3.1 核心命令](#1.6.3.1 核心命令)
- [1.6.3.2 内部编码](#1.6.3.2 内部编码)
- [1.6.4 set 类型(无序集合)](#1.6.4 set 类型(无序集合))
-
- [1.6.4.1 核心命令](#1.6.4.1 核心命令)
- [1.6.4.2 集合间操作](#1.6.4.2 集合间操作)
- [1.6.4.3 内部编码](#1.6.4.3 内部编码)
- [1.6.4.4 应用场景](#1.6.4.4 应用场景)
- [1.6.5 zset 类型(有序集合)](#1.6.5 zset 类型(有序集合))
-
- [1.6.5.1 核心命令](#1.6.5.1 核心命令)
- [1.6.5.2 内部编码](#1.6.5.2 内部编码)
- [1.7 渐进式遍历](#1.7 渐进式遍历)
-
- [1.7.1 返回格式](#1.7.1 返回格式)
- [1.7.2 注意事项](#1.7.2 注意事项)
- [1.8 数据库管理命令](#1.8 数据库管理命令)
-
- [1.8.1 核心命令](#1.8.1 核心命令)
- [1.9 Redis 客户端与 RESP 协议](#1.9 Redis 客户端与 RESP 协议)
-
- [1.9.1 客户端类型](#1.9.1 客户端类型)
- [1.9.2 RESP 协议(Redis Serialization Protocol)](#1.9.2 RESP 协议(Redis Serialization Protocol))
- [1.10 持久化](#1.10 持久化)
-
- [1.10.1 RDB(Redis DataBase):定期备份](#1.10.1 RDB(Redis DataBase):定期备份)
-
- [1.10.1.1 触发方式](#1.10.1.1 触发方式)
- [1.10.1.2 配置与存储细节](#1.10.1.2 配置与存储细节)
- [1.10.1.3 RDB 快照与内存的偏差问题](#1.10.1.3 RDB 快照与内存的偏差问题)
-
- [1.10.1.3.1 如何复现偏差](#1.10.1.3.1 如何复现偏差)
- [1.10.1.3.2 如何实现RDB文件损坏导致服务器无法启动](#1.10.1.3.2 如何实现RDB文件损坏导致服务器无法启动)
- [1.10.1.4 RDB 特点](#1.10.1.4 RDB 特点)
- [1.10.2 AOF(Append Only File):实时备份](#1.10.2 AOF(Append Only File):实时备份)
-
- [1.10.2.1 配置方式](#1.10.2.1 配置方式)
- [1.10.2.2 AOF 实时写入是否会导致Redis性能下降?](#1.10.2.2 AOF 实时写入是否会导致Redis性能下降?)
- [1.10.2.3 AOF 刷盘策略](#1.10.2.3 AOF 刷盘策略)
- [1.10.2.4 AOF 重写机制](#1.10.2.4 AOF 重写机制)
-
- [1.10.2.4.1 触发时机](#1.10.2.4.1 触发时机)
- [1.10.2.4.2 重写过程中的数据一致性保障](#1.10.2.4.2 重写过程中的数据一致性保障)
- [1.10.3 混合持久化](#1.10.3 混合持久化)
- [1.11 Redis 事务](#1.11 Redis 事务)
-
- [1.11.1 事务相关命令](#1.11.1 事务相关命令)
- [1.11.2 事务中的条件判定](#1.11.2 事务中的条件判定)
- [1.11.3 watch 命令的实现原理与应用](#1.11.3 watch 命令的实现原理与应用)
-
- [1.11.3.1 实现原理](#1.11.3.1 实现原理)
- [1.11.3.2 应用场景](#1.11.3.2 应用场景)
- [1.12 主从复制](#1.12 主从复制)
-
- [1.12.1 单个主机启动多个Redis实例](#1.12.1 单个主机启动多个Redis实例)
- [1.12.2 主从配置方式](#1.12.2 主从配置方式)
- [1.12.3 一主二从架构验证](#1.12.3 一主二从架构验证)
- [1.12.4 主从结构的修改与断开](#1.12.4 主从结构的修改与断开)
- [1.12.5 链式主从结构](#1.12.5 链式主从结构)
- [1.12.6 主从复制的关键配置](#1.12.6 主从复制的关键配置)
-
- [1.12.6.1 安全性配置](#1.12.6.1 安全性配置)
- [1.12.6.2 传输延迟配置](#1.12.6.2 传输延迟配置)
- [1.12.7 主从复制拓扑结构](#1.12.7 主从复制拓扑结构)
- [1.12.8 主从复制的核心机制](#1.12.8 主从复制的核心机制)
-
- [1.12.8.1 psync 命令:数据同步的核心](#1.12.8.1 psync 命令:数据同步的核心)
- [1.12.8.2 replicationid 与 offset](#1.12.8.2 replicationid 与 offset)
- [1.12.8.3 全量复制](#1.12.8.3 全量复制)
- [1.12.8.4 增量复制](#1.12.8.4 增量复制)
- [1.12.8.5 实时复制(增量复制)](#1.12.8.5 实时复制(增量复制))
- [1.12.8.6 无硬盘模式](#1.12.8.6 无硬盘模式)
- [1.12.8.7 runid 与 replid 的区别](#1.12.8.7 runid 与 replid 的区别)
- [1.12.9 从节点晋升为主节点的场景](#1.12.9 从节点晋升为主节点的场景)
- [1.12.10 主节点无法重启的权限问题](#1.12.10 主节点无法重启的权限问题)
- [1.13 哨兵机制(Sentinel)](#1.13 哨兵机制(Sentinel))
-
- [1.13.1 核心组件](#1.13.1 核心组件)
- [1.13.2 哨兵的核心功能](#1.13.2 哨兵的核心功能)
- [1.13.3 哨兵机制的核心价值](#1.13.3 哨兵机制的核心价值)
- [1.13.4 主从哨兵实战(Docker Compose 部署)](#1.13.4 主从哨兵实战(Docker Compose 部署))
-
- [1.13.4.1 数据节点配置(docker-compose.yml)](#1.13.4.1 数据节点配置(docker-compose.yml))
- [1.13.4.2 哨兵节点配置(sentinel.conf)](#1.13.4.2 哨兵节点配置(sentinel.conf))
- [1.13.4.3 哨兵节点部署(docker-compose-sentinel.yml)](#1.13.4.3 哨兵节点部署(docker-compose-sentinel.yml))
- [1.13.4.4 部署与验证](#1.13.4.4 部署与验证)
- [1.14 集群(Cluster)](#1.14 集群(Cluster))
-
- [1.14.1 数据分片方式](#1.14.1 数据分片方式)
-
- [1.14.1.1 哈希求余](#1.14.1.1 哈希求余)
- [1.14.1.2 一致性哈希算法](#1.14.1.2 一致性哈希算法)
- [1.14.1.3 哈希槽分区算法(Redis 采用)](#1.14.1.3 哈希槽分区算法(Redis 采用))
- [1.14.2 哈希槽分区的关键问题](#1.14.2 哈希槽分区的关键问题)
-
- [1.14.2.1 问题一:Redis集群最多支持16384个分片?](#1.14.2.1 问题一:Redis集群最多支持16384个分片?)
- [1.14.2.2 问题二:为何选择16384个槽位?](#1.14.2.2 问题二:为何选择16384个槽位?)
- [1.15 Redis 典型应用 - 缓存(Cache)](#1.15 Redis 典型应用 - 缓存(Cache))
-
- [1.15.1 缓存的核心价值](#1.15.1 缓存的核心价值)
- [1.15.2 热点数据存储策略](#1.15.2 热点数据存储策略)
-
- [1.15.2.1 定期生成](#1.15.2.1 定期生成)
- [1.15.2.2 实时生成](#1.15.2.2 实时生成)
- [1.15.3 内存淘汰策略](#1.15.3 内存淘汰策略)
- [1.15.4 缓存使用注意事项](#1.15.4 缓存使用注意事项)
-
- [1.15.4.1 缓存预热(Cache Preheating)](#1.15.4.1 缓存预热(Cache Preheating))
- [1.15.4.2 缓存穿透(Cache Penetration)](#1.15.4.2 缓存穿透(Cache Penetration))
- [1.15.4.3 缓存雪崩(Cache Avalanche)](#1.15.4.3 缓存雪崩(Cache Avalanche))
- [1.15.4.4 缓存击穿(Cache Breakdown)](#1.15.4.4 缓存击穿(Cache Breakdown))
- [1.16 分布式锁](#1.16 分布式锁)
-
- [1.16.1 Redis 单线程为何需要分布式锁?](#1.16.1 Redis 单线程为何需要分布式锁?)
- [1.16.2 Redis 事务+Lua脚本能否替代分布式锁?](#1.16.2 Redis 事务+Lua脚本能否替代分布式锁?)
- [1.16.3 必须使用分布式锁的场景](#1.16.3 必须使用分布式锁的场景)
- [1.16.4 Redis 分布式锁的实现方案](#1.16.4 Redis 分布式锁的实现方案)
-
- [1.16.4.1 原子加锁:`setnx` + 过期时间](#1.16.4.1 原子加锁:
setnx+ 过期时间) - [1.16.4.2 锁标识:避免误删锁](#1.16.4.2 锁标识:避免误删锁)
- [1.16.4.3 原子解锁:Lua脚本](#1.16.4.3 原子解锁:Lua脚本)
- [1.16.4.4 看门狗(Watch Dog):动态续约](#1.16.4.4 看门狗(Watch Dog):动态续约)
- [1.16.4.5 高可用:Redlock 算法](#1.16.4.5 高可用:Redlock 算法)
- [1.16.4.1 原子加锁:`setnx` + 过期时间](#1.16.4.1 原子加锁:
- [1.16.5 Redis 分布式锁的扩展类型](#1.16.5 Redis 分布式锁的扩展类型)
1. Redis 技术文档整理
1.1 Redis 特性介绍
Redis 是一款基于内存存储数据的开源中间件,核心用途为数据缓存。在单机架构下,直接通过定义变量存储数据的性能优于 Redis,其核心价值在分布式系统中得以充分发挥。
从数据组织方式来看,MySQL 是采用"表组织"的关系型数据库,而 Redis 则以"key-value 组织"为核心的非关系型数据库,二者在数据存储逻辑上存在本质区别。
1.1.1 核心特性
- Programmability(可编程性):支持 Lua 脚本(内嵌语言),可通过脚本实现复杂业务逻辑。
- Extensibility(可扩展性):支持 C/C++、Rust 扩展(本质为动态库),能够扩展 Redis 的数据结构与命令集。
- Persistence(持久化):提供数据持久化机制,确保内存数据可同步至硬盘,避免重启或宕机后数据丢失。
- Clustering(集群支持):支持集群部署,可通过水平横向扩展提升系统容量与并发处理能力。
- High availability(高可用性):通过冗余备份机制保障系统稳定运行,降低单点故障风险。
- 高性能优势 :
- 数据存储于内存,读写速度远超磁盘存储;
- 功能设计简洁,数据处理耗时低;
- 采用多路复用技术优化网络 IO;
- 单线程模型避免线程切换开销(Redis 性能瓶颈在于网络 IO,而非 CPU,多线程在 CPU 密集型任务中更具优势)。
1.1.2 应用场景
- 数据库(适用于高并发读写场景);
- 缓存/会话存储(减轻后端数据库压力);
- 消息队列(非核心应用场景,一般不推荐优先使用)。
1.2 环境搭建
1.2.1 Ubuntu 系统安装 Redis 5
bash
apt-get update
apt install redis
1.2.2 配置文件
Redis 配置文件路径:/etc/redis.conf
关键配置选项:
bind 0.0.0.0(允许外部访问)password_mode(密码认证相关配置)
1.2.3 客户端工具
- Redis 自带命令行客户端;
- 图形化客户端(如 Redis Desktop Manager 等);
- 基于 API 开发的自定义客户端。
1.2.4 注意事项
单机架构下,直接操作内存的速度远快于 Redis,无需经过网络 IO 开销,应避免"锤子思维"(盲目依赖 Redis 解决所有存储问题)。
1.3 通用命令
1.3.1 基础操作命令
get key:根据 key 获取对应的 value;set key value:存储 key-value 键值对(key 必须为字符串类型);flushall:删除所有数据库中的数据。
1.3.2 全局命令
keys pattern:根据 pattern 匹配获取所有符合条件的 key,时间复杂度 O(N),keys *命令因性能问题禁止在生产环境使用;exists key1 [key2 ...]:判定一个或多个 key 是否存在,返回存在的 key 个数(单一 key 时间复杂度 O(1),N 个 key 时间复杂度 O(N));del key1 [key2 ...]:删除一个或多个 key,时间复杂度(单一 key 时间复杂度 O(1),N 个 key 时间复杂度 O(N));expire key seconds/pexpire key milliseconds:为指定 key 设置过期时间,成功返回 1,失败返回 0,时间复杂度 O(1);ttl key/pttl key:查看 key 的剩余生存时间(time to live),返回值说明:
*=0:剩余过期时间(单位分别为秒/毫秒);
- -1:key 未关联过期时间;
- -2:key 不存在。
1.3.3 Redis 过期策略
- 惰性删除(懒汉模式):仅当 key 被访问时才检查其过期时间,过期则删除;
- 定期删除:定期、集中扫描部分设置了过期时间的键,删除已过期的键;
- 定时删除:为每个 key 配置独立定时器,到期触发删除。该策略未被采用,因大量定时器会消耗过多 CPU 资源,严重影响 Redis 主线程性能与响应时间。
1.3.3.1 定时器实现方案
- 基于优先级队列/堆:分配独立线程,每隔固定时间检查堆顶元素是否过期;若未过期,线程休眠至过期时间;若有新任务触发,可唤醒线程;
- 基于时间轮:将时间划分为固定间隔的扇区,每个扇区挂载任务链表(链表节点为待执行函数),线程遍历扇区时执行对应任务,扇区数量与间隔可根据业务调整。
1.4 数据结构
1.4.1 类型查询命令
type key:返回 key 对应的 value 的数据类型。
1.4.2 核心数据结构(类比 C++ 容器)
string:类比std::string,二进制安全,支持存储文本(含整数、字符串、JSON、XML 等结构化文本);list:类比std::deque,有序双端队列,元素插入顺序影响列表内容;hash:类比std::unordered_map,适用于存储对象类数据;set:类比std::set,无序集合,自动去重;zset:类比std::set + scores(权重),有序集合,按权重排序。
1.4.3 注意事项
上述类比基于时间复杂度与接口调用逻辑,Redis 内部编码实现复杂,不同版本、甚至同一类型存储不同数据时,底层物理结构可能存在差异,可通过 object encoding key 命令查看具体底层实现方式。
1.5 Redis 单线程模型
Redis 通过单个线程处理命令,但并非整个系统为单线程(网络 IO 处理采用多线程)。多线程接收客户端请求后,将请求放入队列,由主线程按从任务队列去除执行。
1.5.1 单线程高性能原因
- 数据存储于内存,读写无磁盘 IO 开销;
- 功能设计简洁,数据处理逻辑耗时低;
- 避免多线程切换带来的性能损耗;
- 采用多路复用技术优化网络 IO 处理。
1.6 各数据结构详细命令
1.6.1 string 类型
1.6.1.1 核心命令
set key value [ex seconds | px milliseconds] [nx | xx]:存储 key-value 键值对,ex/px指定过期时间,nx仅当 key 不存在时执行,xx仅当 key 存在时执行(set 命令会将 value 强制转为 string 类型);get key:获取 string 类型 key 对应的 value;mset key1 value1 key2 value2 ...:批量存储多个 key-value 键值对;mget key1 key2 ...:批量获取多个 key 对应的 value;del key1 [key2 ...]:删除一个或多个 key 及其对应的 value;incr key:对 key 存储的整数 value 加 1,key 不存在时自动创建并设为 0 后加 1,返回结果;incrby key n:对整数 value 加 n(n 可为负数,等效于减法);decr key:对整数 value 减 1;decrby key n:对整数 value 减 n(n 可为正数);incrbyfloat key n:对浮点数 value 加 n(n 可为负数);append key value:key 存在时在 value 末尾追加内容,不存在时等效于 set 命令,返回追加后 value 的字节长度;getrange key start end:获取 value 中 [start, end] 区间的子串(左闭右闭,下标从 0 开始,支持负数下标,如 -1 表示最后一个字符);setrange key offset value:从 offset 偏移量开始替换 value 内容,key 不存在时自动创建;strlen key:返回 value 的字节长度。
1.6.1.2 编码方式
Redis 会根据 value 特性自适应选择编码:
int:存储整数,对应 C++ 的long long;embstr:压缩字符串,适用于短字符串;raw:普通字符串,适用于长字符串。
1.6.1.3 注意事项
- string 类型是二进制安全的,客户端存储的编码格式与服务端返回格式一致;
- 若需存储中文且正常显示,可通过
redis-cli --raw启动客户端。
1.6.2 hash 类型
1.6.2.1 核心命令
hset key field1 value1 [field2 value2 ...]:为 hash 类型 key 存储一个或多个 field-value 键值对;hget key field:获取 hash 类型 key 中指定 field 对应的 value;hdel key field1 [field2 ...]:删除 hash 类型 key 中的一个或多个 field,返回删除成功的个数;hkeys key:获取 hash 类型 key 中所有的 field;hvals key:获取 hash 类型 key 中所有的 value;hgetall key:获取 hash 类型 key 中所有的 field 及其对应的 value(可能阻塞服务,大数据量建议使用 hscan);hmget key field1 [field2 ...]:批量获取 hash 类型 key 中多个 field 对应的 value;hscan key cursor [match pattern] [count count]:渐进式遍历 hash 类型数据,化整为零,避免阻塞;hlen key:获取 hash 类型 key 中 field 的个数;hsetnx key field value:仅当 field 不存在时存储 field-value 键值对;hincrby key field num:对 hash 类型 key 中 field 对应的整数 value 增减 num;hincrbyfloat key field num:对 hash 类型 key 中 field 对应的浮点数 value 增减 num。
1.6.2.2 内部编码
ziplist:当 field-value 对个数少且单个元素小时使用,压缩存储节省内存(时间换空间);hashtable:元素较多或较大时使用,查询效率更高。
1.6.2.3 应用场景
主要用于缓存对象类数据(如用户信息、商品详情等)。
1.6.3 list 类型(双端队列)
1.6.3.1 核心命令
lpush key element1 [element2 ...]:从列表头部插入一个或多个元素(如插入 1 2 3 4,实际顺序为 4 3 2 1);lrange key start stop:获取列表中 [start, stop] 区间的元素(左闭右闭,支持负数下标);lpushx key element1 [element2 ...]:仅当 key 存在时,从列表头部插入元素;rpush key element1 [element2 ...]:从列表尾部插入一个或多个元素(插入 1 2 3 4,实际顺序为 1 2 3 4);rpushx key element1 [element2 ...]:仅当 key 存在时,从列表尾部插入元素;lpop key [count]:从列表头部删除 count 个元素(count 可选,默认 1);rpop key [count]:从列表尾部删除 count 个元素(count 可选,默认 1);lindex key index:获取列表中第 index 个元素(时间复杂度 O(N),底层非顺序表实现,索引不合法返回 nil);linsert key <before | after> pivot element:在列表中 pivot 元素的前面或后面插入 element;llen key:获取列表的元素个数;lrem key count element:删除列表中指定 element(时间复杂度 O(N+M)):- count > 0:从左到右删除 count 个 element;
- count = 0:删除所有 element;
- count < 0:从右到左删除 |count| 个 element;
ltrim key start stop:保留列表中 [start, stop] 区间的元素,删除区间外的元素;lset key index element:修改列表中第 index 个元素的值;blpop key1 [key2 ...] timeout:阻塞式从列表头部弹出元素,多个 key 时按顺序尝试,超时时间为 timeout(秒),返回值格式为 [key, value];brpop key1 [key2 ...] timeout:阻塞式从列表尾部弹出元素,逻辑与 blpop 一致。
1.6.3.2 内部编码
- 历史版本:
ziplist(压缩列表)、linkedlist(双向链表); - 最新版本:
quicklist(双端链表,每个节点为一个压缩列表)。
1.6.4 set 类型(无序集合)
1.6.4.1 核心命令
sadd key member1 [member2 ...]:向集合中添加一个或多个成员,返回添加成功的个数(自动去重);smembers key:获取集合中所有成员;sismember key member:判断 member 是否为集合中的成员;spop key [count]:随机删除集合中的 count 个成员并返回;smove src dest member:将 src 集合中的 member 移动到 dest 集合(dest 中存在 member 时仍会删除 src 中的成员);srem key member1 [member2 ...]:删除集合中的一个或多个成员。
1.6.4.2 集合间操作
sinter key1 [key2 ...]:求多个集合的交集;sinterstore dest key1 [key2 ...]:求多个集合的交集并存储到 dest 集合;sunion key1 [key2 ...]:求多个集合的并集;sunionstore dest key1 [key2 ...]:求多个集合的并集并存储到 dest 集合;sdiff key1 [key2 ...]:求差集(返回属于 key1 但不属于其他集合的成员)。
1.6.4.3 内部编码
intset:集合中所有成员为整数且个数较少时使用;hashtable:其他场景下使用。
1.6.4.4 应用场景
- 用户标签、用户画像(如给用户添加"喜欢运动""关注科技"等标签);
- 共同好友计算、好友推荐(基于集合交集操作);
- PV(页面浏览量)、UV(独立访客数)统计。
1.6.5 zset 类型(有序集合)
1.6.5.1 核心命令
zadd key [NX | XX] [GT | LT] [CH] [INCR] score1 member1 [score2 member2 ...]:向有序集合中添加成员(score 为权重),返回新增成员个数(添加 CH 选项时返回新增+修改的个数):- NX:仅当 member 不存在时执行;
- XX:仅当 member 存在时执行;
- GT:仅当新 score 大于旧 score 时执行;
- LT:仅当新 score 小于旧 score 时执行;
- INCR:将 score 视为增量(如
zadd key incr 4 张飞表示张飞的 score 增加 4);
zrange key start stop [withscores]:按 score 升序获取 [start, stop] 区间的成员,添加 withscores 时返回成员及对应 score;zcard key:获取有序集合的成员个数;zcount key min max:返回 score 在 [min, max] 区间的成员个数(开区间可在 min/max 前加();zrevrange key start stop [withscores]:按 score 降序获取 [start, stop] 区间的成员;zrangebyscore key min max [withscores]:按 score 范围获取成员(已整合到 zrange 功能);zpopmax key [count]:删除并返回 score 最高的 count 个成员;bzpopmax key1 [key2 ...] timeout:阻塞式删除并返回 score 最高的成员;bzpopmin key1 [key2 ...] timeout:阻塞式删除并返回 score 最低的成员;zrank key member:获取成员按 score 升序的排名(下标从 0 开始);zrevrank key member:获取成员按 score 降序的排名;zscore key member:返回成员对应的 score;zrem key member1 [member2 ...]:删除有序集合中的一个或多个成员;zremrangebyrank key start stop:删除排名在 [start, stop] 区间的成员;zincrby key increment member:为成员的 score 增减 increment;zinterstore dest numkeys key1 key2 ...:求多个有序集合的交集并存储到 dest 集合(score 按默认规则聚合);zunionstore dest numkeys key1 key2 ...:求多个有序集合的并集并存储到 dest 集合(score 按默认规则聚合)。
1.6.5.2 内部编码
ziplist:成员个数较少时使用;skiplist(跳表):成员个数较多时使用,兼顾查询效率与插入效率。
1.7 渐进式遍历
bash
scan cursor [MATCH pattern] [COUNT count] [TYPE type]
cursor:遍历游标(初始为 0,返回游标为 0 时遍历结束);MATCH pattern:按模式匹配 key;COUNT count:提示服务器返回的 key 数量(非精确值);TYPE type:指定要遍历的数据类型。
1.7.1 返回格式
1) "cursor" # 下一次遍历的游标
2) 1) "key1"
2) "key2"
...
1.7.2 注意事项
渐进式遍历对服务器性能影响小,但如果遍历过程中有写操作,可能导致键的重复遍历或遗漏。
1.8 数据库管理命令
Redis 服务器默认创建 16 个数据库(编号 0-15),用户无法自定义创建或删除数据库。
1.8.1 核心命令
select index:切换到指定编号的数据库(默认使用 0 号数据库);dbsize:获取当前数据库中 key 的个数;flushdb [async | sync]:删除当前数据库中的所有 key(async 为异步,sync 为同步);flushall:删除所有数据库中的所有 key。
1.9 Redis 客户端与 RESP 协议
1.9.1 客户端类型
- Redis 自带命令行客户端;
- 图形化客户端;
- 基于 API 开发的自定义客户端(需遵循 RESP 协议)。
1.9.2 RESP 协议(Redis Serialization Protocol)
Redis 采用的应用层协议,具有以下优点:
- 简单易实现;
- 解析速度快;
- 肉眼可读。
客户端发送的命令以批量字符串数组形式传输,服务端根据命令类型返回不同格式的结果。
1.10 持久化
持久化的核心是将Redis内存中的数据同步至硬盘,查询仍从内存读取,其目的是确保Redis重启或主机重启后,数据能重新加载到内存中,因此某一时刻内存与磁盘会存储同一份数据,关键在于如何实现高效、可靠的写入。Redis提供两种持久化策略:RDB(Redis DataBase)和AOF(Append Only File),二者基于不同的设计取舍。
1.10.1 RDB(Redis DataBase):定期备份
RDB基于"定期备份"的设计思路,通过定期生成内存数据的快照实现持久化。
1.10.1.1 触发方式
触发方式分为手动触发和自动触发:
- 手动触发 :
save:阻塞当前Redis单线程,直至快照生成完成,生产环境不推荐使用;bgsave:创建子进程,通过写时拷贝(Copy-On-Write)机制生成快照,不阻塞主线程。
- 自动触发 :在配置文件
/etc/redis/redis.conf中通过save <seconds> <changes>配置(如save 900 1表示900秒内有1次数据变更即触发),支持配置多个触发条件。
1.10.1.2 配置与存储细节
- 配置文件相关选项:
dir:RDB文件存储目录(默认/var/lib/redis);dbfilename:RDB文件名(默认dump.rdb)。
- RDB是二进制文件,生成时会消耗CPU资源进行压缩,以节省存储空间;
- RDB文件仅存在一个,无论手动还是自动触发,都会先写入临时文件,IO完成后删除原RDB文件并将临时文件重命名,可通过inode编号查看文件是否更新;
- 请勿修改
dump.rdb文件(应设为只读),网络传输过程中可能因比特位翻转导致文件损坏。
1.10.1.3 RDB 快照与内存的偏差问题
RDB的定期快照特性导致快照与内存数据存在偏差,AOF机制正是为解决该问题而设计。
1.10.1.3.1 如何复现偏差
在不触发自动触发条件的前提下(或直接修改配置文件save ""关闭自动存储),避免使用shutdown或service redis-server restart命令重启服务器,此时快照未更新,内存中新增/修改的数据未同步至RDB,重启后会丢失,即可复现偏差。
1.10.1.3.2 如何实现RDB文件损坏导致服务器无法启动
- 修改
dump.rdb文件内容; - 通过
kill -9强制终止Redis服务器(正常退出会重写RDB文件,无法模拟损坏); - 启动Redis服务器,会因RDB文件损坏而崩溃;
- 查看
/var/log/redis目录下的日志文件(默认redis-server.log,日志过大时会自动压缩); - 可使用
/usr/bin/redis-check-rdb工具检测RDB文件问题。
1.10.1.4 RDB 特点
- 优点:适用于备份和全量复制场景,二进制文件加载速度快;
- 缺点:无法实现实时持久化,不同Redis版本间可能存在兼容性问题。
1.10.2 AOF(Append Only File):实时备份
AOF基于"实时备份"的设计思路,功能类似MySQL的binlog,但以文本文件形式存储,每执行一条写命令就记录一次。
1.10.2.1 配置方式
- 开启AOF:在配置文件中设置
appendonly yes; - 指定AOF文件名:
appendfilename <文件名>(如appendfilename appendonly.aof); - 注意:开启AOF后,服务器启动时不再读取RDB文件,但仍会生成RDB快照。
1.10.2.2 AOF 实时写入是否会导致Redis性能下降?
不会,原因如下:
- AOF并非直接将数据写入硬盘,而是先写入应用层缓冲区(aof_buf),减少IO次数。性能瓶颈不在于数据量,而在于硬盘写入次数;
- 缓冲区数据刷新到磁盘时为顺序读写,速度较快(磁轴无需多次寻址);
- 仍存在数据丢失风险:缓冲区数据仅存储在内存,若服务器宕机时数据未落地,会导致数据丢失。
1.10.2.3 AOF 刷盘策略
配置文件支持三种缓冲区刷新到磁盘的策略,平衡性能与可靠性:
always:每执行一条命令,立即调用fsync将缓冲区数据刷新到磁盘(数据零丢失,性能开销最大);everysec:命令写入缓冲区后,先通过write拷贝到内核层缓冲区,每秒由同步线程执行fsync(默认配置,平衡性能与可靠性);no:命令写入缓冲区后,通过write拷贝到内核层缓冲区,由操作系统自主控制刷新时机(性能最优,数据丢失风险最高)。
1.10.2.4 AOF 重写机制
随着服务器运行,AOF文件会持续增大,导致启动变慢,且存在命令冗余(如多次lpush可合并为一次)。Redis通过AOF重写机制压缩文件体积,基于当前内存数据重新生成精简的命令集,而非解析原AOF文件。
1.10.2.4.1 触发时机
- 手动触发 :执行
bgrewriteaof命令,fork子进程进行重写; - 自动触发 :通过配置文件两个选项控制:
auto-aof-rewrite-min-size:触发重写的最小AOF文件大小(默认64MB);auto-aof-rewrite-percentage:当前AOF文件相对上次重写后的增长比例(默认100%)。
1.10.2.4.2 重写过程中的数据一致性保障
RDB和AOF重写均会fork子进程,基于当前内存生成快照,此时父进程仍会接收请求,导致父子进程数据不一致。解决方案:
- 开启AOF后,父进程除了将命令写入
aof_buf(用于刷盘到旧AOF文件),还会写入aof_rewrite_buf缓冲区,专门存储写时拷贝后父子进程的差异数据; - 子进程重写完成临时AOF文件后,通过10号信号通知父进程;
- 父进程将
aof_rewrite_buf中的数据追加到临时文件,替换旧AOF文件,完成重写。
1.10.3 混合持久化
混合持久化是AOF的扩展策略,配置文件中设置aof-use-rdb-preamble yes即可启用:
- 触发AOF重写时,不再生成纯文本文件;
- 先生成内存数据的RDB二进制快照;
- 将重写期间的增量写命令以文本形式追加到临时RDB文件后;
- 最终生成的文件同时包含二进制快照和文本命令,兼顾加载速度与数据完整性。
1.11 Redis 事务
Redis事务是命令的打包与批处理,具有以下特性:
- 弱原子性:要么全部执行,要么全部不执行,但执行过程中若命令失败,Redis不支持回滚;
- 不保证一致性:若命令本身存在语法错误,事务队列会执行失败;
- 无隔离性:单线程模型下,事务执行期间不会被其他命令插入,无需额外隔离机制;
- 无持久性:事务的持久性依赖Redis的持久化策略,与事务本身无关。
1.11.1 事务相关命令
multi:开启事务,后续命令入队(不立即执行);exec:执行事务队列中的所有命令;discard:丢弃事务,清空命令队列;watch key1 [key2 ...]:监视一个或多个key,搭配事务使用;unwatch:取消所有key的监视。
1.11.2 事务中的条件判定
Redis事务不支持直接的条件判定(如if count > 0),需借助Lua脚本实现复杂逻辑,例如:
lua
multi
get count
if count>0
decr count
exec
上述逻辑需通过Lua脚本封装为原子操作。
1.11.3 watch 命令的实现原理与应用
1.11.3.1 实现原理
基于乐观锁(CAS,Compare And Swap)机制:
- 服务器收到客户端
watch请求时,为客户端分配版本号; - 客户端发送
exec命令后,服务器检查被监视key的版本号是否为最新; - 若版本号不一致(key被其他客户端修改),事务返回
nil,执行失败; - 底层实现:每个key维护1位标志位和客户端列表指针,key被修改时设置标志位,
exec时仅检查标志位。
1.11.3.2 应用场景
用于解决并发修改冲突,例如转账场景:多个同事同时向同一用户转账,若未加锁,可能导致重复转账。通过watch监视转账金额对应的key,确保事务执行时key未被其他客户端修改。
1.12 主从复制
主从复制的核心目的是解决单点问题,实现数据冗余、读写分离与故障恢复。主节点负责写操作,从节点负责读操作,从节点挂掉不影响系统运行(仅增加其他从节点负载),主节点挂掉会导致写操作失败。主从复制架构主要提升读操作并发量和高可用性,对写操作性能无实质提升。
1.12.1 单个主机启动多个Redis实例
Redis默认端口为6379,启动多个实例需指定不同配置文件,至少修改port参数:
bash
./redis-server slave1.conf
./redis-server slave2.conf
注意事项:通过root用户命令行启动时,从节点生成的AOF文件所有者(owner)和所属组(group)为root,而非Redis默认用户,可能导致权限问题。
1.12.2 主从配置方式
- 配置文件方式:在从节点配置文件中添加
slaveof [masterHost] [masterPort]; - 命令行参数方式:启动从节点时指定
--slaveof [masterHost] [masterPort]。
1.12.3 一主二从架构验证
- 启动一主二从后,通过
netstat -antp可看到三个监听套接字,以及两对服务器间的TCP连接; - 从节点默认只读,无法执行写操作;
- 通过
info replication命令查看主从配置信息:role:master/slave:标识节点角色;connected_slaves:2:主节点显示已连接的从节点数量;- 从节点信息:包含从节点IP、端口、状态(state)、偏移量(offset);
master_replid:主节点唯一标识;master_repl_offset:主节点已传播给从节点的命令字节位置。
1.12.4 主从结构的修改与断开
- 断开主从关系:从节点执行
slaveof no one,此时从节点晋升为主节点,可接收写操作; - 重新配置主从:从节点执行
slaveof [newMasterIP] [newMasterPort],建立新的主从关系。
1.12.5 链式主从结构
从节点可作为其他从节点的主节点(即"A服务器 <-- B服务器 <-- C服务器"),B服务器既是A的从节点,也是C的主节点,但B服务器仍为只读模式。
1.12.6 主从复制的关键配置
1.12.6.1 安全性配置
- 主节点设置
requirepass <password>启用密码校验; - 从节点需配置
masterauth <password>,否则无法与主节点同步数据(客户端需通过auth <password>鉴权)。
1.12.6.2 传输延迟配置
- TCP Nagle算法:开启后合并小报文,减少TCP报头数量,节省带宽,但增加传输延迟;关闭后减少延迟,增加带宽占用;
- Redis配置项
repl-disable-tcp-nodelay:控制主从同步通信时是否开启Nagle算法(yes表示关闭,no表示开启)。
1.12.7 主从复制拓扑结构
- 一主一从:结构简单,主节点可关闭AOF减少IO开销,但存在数据丢失风险,主节点宕机后重启需从从节点同步数据;
- 一主多从:提升读并发能力,但从节点数量增多会增加主节点带宽负载,需配备高性能网卡;
- 树状结构:从节点作为其他从节点的主节点,分散主节点带宽压力,但会增加数据同步延迟。
1.12.8 主从复制的核心机制
1.12.8.1 psync 命令:数据同步的核心
从节点通过psync命令与主节点同步数据,命令格式:psync [replicationid] [offset],其中:
replicationid:主节点生成的唯一标识,重启后会重新生成;offset:偏移量,主从节点均会记录,用于标识数据同步进度。
1.12.8.2 replicationid 与 offset
- replicationid :
- 主节点的
replicationid可通过从节点info replication查看(master_replid字段); master_replid2字段默认值为00000000000,一般不使用;- 若主从节点因网络抖动断开,从节点可能误判主节点宕机并晋升为主节点,生成新的
replicationid,重连后需重新同步。
- 主节点的
- offset :
- 主节点:记录已传播给从节点的命令字节位置(累计值);
- 从节点:记录已从主节点复制的最后一个命令字节位置;
- 从节点每秒向主节点上报自身偏移量;
replicationid和offset共同唯一标识一份数据集合,二者一致则表示主从节点数据完全同步。
1.12.8.3 全量复制
当从节点第一次连接主节点,或无法进行部分重同步时触发:
- 从节点发送
psync ? -1命令,请求全量同步; - 主节点返回
FULLRESYNC响应,携带自身replicationid和offset; - 主节点执行
bgsave生成RDB快照,同时将期间的写命令记录到复制缓冲区; - 主节点发送RDB快照至从节点,从节点加载快照;
- 主节点发送复制缓冲区中的增量命令,从节点执行后完成同步。
1.12.8.4 增量复制
全量复制的优化手段,适用于主从节点断开后重连的场景:
- 主从节点断开期间,主节点将写命令记录到复制缓冲区;
- 从节点重连后,发送
psync [replicationid] [offset]命令(携带上次同步的replicationid和offset); - 主节点验证
replicationid是否匹配,且offset是否在复制缓冲区范围内; - 若验证通过,主节点发送缓冲区中
offset之后的增量命令,从节点执行(返回CONTINUE响应); - 若验证失败(如
replicationid不匹配或offset超出缓冲区范围),则触发全量复制。
1.12.8.5 实时复制(增量复制)
主从节点完成全量同步后,主节点收到的新写命令会通过长连接实时同步至从节点:
- 长连接需保活机制,通过心跳包维持:
- 主节点:默认每10秒发送一次
ping包,从节点收到后返回pong; - 从节点:默认每秒向主节点上报自身
offset。
- 主节点:默认每10秒发送一次
1.12.8.6 无硬盘模式
主节点开启repl-diskless-sync yes后,bgsave生成的RDB快照不写入硬盘,直接通过网络发送至从节点,节省刷盘IO和网卡读取磁盘的IO开销。
1.12.8.7 runid 与 replid 的区别
runid:通过info server查看,是Redis服务端每次运行时随机生成的唯一标识,主要用于哨兵机制;replid:通过info replication查看,是主从架构中的主节点标识,主从结构下所有从节点的master_replid与主节点一致。
1.12.9 从节点晋升为主节点的场景
- 人工干预 :从节点执行
slaveof no one,主动断开主从关系,晋升为主节点; - 主节点宕机 :从节点不会自动晋升,执行
info replication会显示master_link_status:down,需通过哨兵机制实现自动晋升。
1.12.10 主节点无法重启的权限问题
- 若通过
systemctl start redis启动主节点(默认使用redis用户),而通过root用户命令行启动从节点,从节点生成的AOF/RDB文件所有者为root; - 若未修改多实例配置文件中的
dir或appendfilename,主节点宕机后重启时,因redis用户无root权限的文件读写权限,导致无法启动。
1.13 哨兵机制(Sentinel)
哨兵机制用于解决主从复制架构的主节点故障自动切换问题。在传统主从模式中,主节点故障后需人工切换主从关系,且客户端需重新配置连接,无法满足大规模应用的高可用需求。
1.13.1 核心组件
- 数据节点 :主节点(master)和从节点(slave),均为
redis-server进程,负责存储数据; - 哨兵节点 :独立的
redis-sentinel进程,不存储数据,仅负责监控数据节点、执行故障转移、通知客户端。
1.13.2 哨兵的核心功能
- 监控:哨兵节点与数据节点建立TCP长连接,通过定期发送心跳包检测节点存活状态;
- 自动故障转移:主节点故障时,自动从从节点中选举新主节点,重新配置其他从节点指向新主节点;
- 通知:将故障转移结果通知客户端,实现客户端无缝切换。
1.13.3 哨兵机制的核心价值
哨兵真正解决的是"主节点宕机且无法重启"的场景:
- 自动将从节点晋升为新主节点;
- 批量修改其他从节点的主节点配置;
- 通知客户端新主节点地址,避免客户端手动修改连接配置。
1.13.4 主从哨兵实战(Docker Compose 部署)
通过Docker Compose编排一主两从数据节点和三个哨兵节点,实现高可用部署。
1.13.4.1 数据节点配置(docker-compose.yml)
yaml
version: '3.8'
services:
master:
image: redis:5.0.9
container_name: redis-master
restart: always
command: redis-server --appendonly yes
ports:
- "6379:6379"
networks:
- redis-network
slave1:
image: redis:5.0.9
container_name: redis-slave1
restart: always
command: redis-server --appendonly yes --slaveof redis-master 6379
ports:
- "6380:6379"
depends_on:
- master
networks:
- redis-network
slave2:
image: redis:5.0.9
container_name: redis-slave2
restart: always
command: redis-server --appendonly yes --slaveof redis-master 6379
ports:
- "6381:6379"
depends_on:
- master
networks:
- redis-network
networks:
redis-network:
driver: bridge
1.13.4.2 哨兵节点配置(sentinel.conf)
三个哨兵节点分别使用sentinel1.conf、sentinel2.conf、sentinel3.conf(内容一致,启动后自动生成不同的配置信息):
yaml
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2 # 监控主节点,2个哨兵确认故障即触发转移
sentinel down-after-milliseconds redis-master 1000 # 主节点失联1秒判定为故障
1.13.4.3 哨兵节点部署(docker-compose-sentinel.yml)
yaml
version: '3.7'
services:
sentinel1:
image: 'redis:5.0.9'
container_name: redis-sentinel-1
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel1.conf:/etc/redis/sentinel.conf
ports:
- 26379:26379
sentinel2:
image: 'redis:5.0.9'
container_name: redis-sentinel-2
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel2.conf:/etc/redis/sentinel.conf
ports:
- 26380:26379
sentinel3:
image: 'redis:5.0.9'
container_name: redis-sentinel-3
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel3.conf:/etc/redis/sentinel.conf
ports:
- 26381:26379
networks:
default:
external:
name: redis-network # 与数据节点共用网络
1.13.4.4 部署与验证
-
启动数据节点:
docker-compose up -d; -
启动哨兵节点:
docker-compose -f docker-compose-sentinel.yml up -d; -
哨兵启动后,
sentinel.conf会自动新增以下内容(由CONFIG REWRITE生成):xml# Generated by CONFIG REWRITE // 哨兵启动后新增内容(三个哨兵的生成内容不同) -
模拟主节点故障:
docker stop redis-master; -
查看哨兵日志(以
sentinel1为例):bashredis-sentinel-1 | 1:X 18 Dec 2025 13:12:57.883 # +new-epoch 1 redis-sentinel-1 | 1:X 18 Dec 2025 13:12:57.885 # +vote-for-leader b2102005bd440bd4a61c4fe796121e2d1dbd4def 1 redis-sentinel-1 | 1:X 18 Dec 2025 13:12:58.976 # +odown master redis-master 172.18.0.2 6379 #quorum 3/2 redis-sentinel-1 | 1:X 18 Dec 2025 13:12:58.976 # Next failover delay: I will not start a failover before Thu Dec 18 13:18:58 2025 redis-sentinel-1 | 1:X 18 Dec 2025 13:12:59.054 # +config-update-from sentinel b2102005bd440bd4a61c4fe796121e2d1dbd4def 172.18.0.6 26379 @ redis-master 172.18.0.2 6379 redis-sentinel-1 | 1:X 18 Dec 2025 13:12:59.054 # +switch-master redis-master 172.18.0.2 6379 172.18.0.3 6379 redis-sentinel-1 | 1:X 18 Dec 2025 13:12:59.054 * +slave slave 172.18.0.4:6379 172.18.0.4 6379 @ redis-master 172.18.0.3 6379 redis-sentinel-1 | 1:X 18 Dec 2025 13:12:59.054 * +slave slave 172.18.0.2:6379 172.18.0.2 6379 @ redis-master 172.18.0.3 6379 redis-sentinel-1 | 1:X 18 Dec 2025 13:13:00.114 # +sdown slave 172.18.0.2:6379 172.18.0.2 6379 @ redis-master 172.18.0.3 6379 -
重启原主节点:
docker start redis-master,原主节点会自动成为新主节点的从节点。
1.14 集群(Cluster)
广义上,多个机器构成的分布式系统均可称为"集群"。Redis哨兵模式提升了系统可用性,但数据仍存储在单一主从组中,存在容量限制。Redis集群(Cluster)引入多组主从节点,每组存储数据全集的一部分(分片),实现水平扩展。
1.14.1 数据分片方式
数据分片的核心是将数据均匀分布到多个节点,Redis支持三种主流分片方式,最终采用哈希槽分区算法。
1.14.1.1 哈希求余
- 原理:假设有N个分片,通过
insert(key, hash(key) % N)将key映射到对应分片(哈希函数如MD5); - 缺点:扩容时需重新计算哈希值,导致大量数据迁移,开销极大,生产环境不可行。
1.14.1.2 一致性哈希算法
- 原理:将
0~2^32-1的数据空间映射到圆环上,数据按顺时针方向分布,分片节点也映射到圆环上; - 优点:扩容时仅需迁移部分数据(新增分片顺时针方向的相邻分片数据);
- 缺点:天然存在数据分布不均问题,需通过增加多个分片缓解,部署成本高。
1.14.1.3 哈希槽分区算法(Redis 采用)
- 核心原理:
- 预设16384个哈希槽(
2^14),每个key通过hash_slot = crc16(key) % 16384计算所属槽位; - 槽位均匀分配给多个分片(主节点),槽位可连续或非连续分配,仅要求数量均匀;
- 分片通过位图标识自身负责的槽位。
- 预设16384个哈希槽(
- 示例分配(N=3个分片):
- 0号分片:
[0, 5461](5462个槽位); - 1号分片:
[5462, 10923](5462个槽位); - 2号分片:
[10924, 16383](5460个槽位)。
- 0号分片:
- 扩容示例(新增1个分片,总4个):
- 0号分片:
[0, 4095](4096个槽位); - 1号分片:
[5462, 9557](4096个槽位); - 2号分片:
[10924, 15019](4096个槽位); - 3号分片:
[4096, 5461] + [9558, 10923] + [15020, 16383](4096个槽位)。
- 0号分片:
1.14.2 哈希槽分区的关键问题
1.14.2.1 问题一:Redis集群最多支持16384个分片?
- 槽位数≠分片数,分片数应远小于槽位数(假设key数量接近槽位数);
- 若分片数等于槽位数(16384个),会导致数据倾斜,且每个分片需搭配从节点,部署成本极高(2^15个服务器),实际不可行。
1.14.2.2 问题二:为何选择16384个槽位?
- 节点间需频繁发送心跳包,16384个槽位仅需2KB(16384位)的位图存储槽位信息,报文载荷小;
- 若选择65536个槽位,位图需8KB,增加网络传输开销。
1.15 Redis 典型应用 - 缓存(Cache)
缓存的核心逻辑是"用速度快的设备存储速度慢的设备的数据",Redis作为内存数据库,定位为"内存作为硬盘的缓存",基于"二八定律"(20%的热点数据支撑80%的访问)优化系统性能。
1.15.1 缓存的核心价值
解决MySQL等关系型数据库的并发压力问题:
- 硬件方案:引入数据库集群,扩展存储和计算能力;
- 软件方案:引入Redis缓存,将高频热点数据存储在内存,减少数据库访问。
1.15.2 热点数据存储策略
1.15.2.1 定期生成
- 流程:记录用户访问日志,通过分布式文件系统(HDFS)存储,使用Hadoop MapReduce框架统计高频热点数据,按天/月周期通过自动化脚本同步至Redis;
- 优点:实现简单、过程可控、便于排查问题;
- 缺点:实时性差,突发热点数据可能导致缓存穿透。
1.15.2.2 实时生成
- 流程:查询数据时先访问Redis,命中则返回;未命中则查询MySQL,返回数据的同时写入Redis;
- 优点:实时性强,无需提前统计;
- 缺点:Redis内存空间有限,需通过内存淘汰策略管理数据。
1.15.3 内存淘汰策略
当Redis内存不足时,根据配置文件maxmemory-policy选择淘汰策略:
volatile-lru:从设置过期时间的key中淘汰最近最少使用的;allkeys-lru:从所有key中淘汰最近最少使用的;volatile-lfu:Redis 4.0+,从设置过期时间的key中淘汰访问次数最少的;allkeys-lfu:Redis 4.0+,从所有key中淘汰访问次数最少的;volatile-random:从设置过期时间的key中随机淘汰;allkeys-random:从所有key中随机淘汰;volatile-ttl:从设置过期时间的key中淘汰剩余生存时间最短的(等效FIFO);noeviction:默认策略,内存不足时拒绝新写操作。
1.15.4 缓存使用注意事项
1.15.4.1 缓存预热(Cache Preheating)
- 问题:实时生成策略下,系统启动初期Redis无数据,所有请求直达MySQL,可能导致MySQL宕机;
- 解决方案:离线导入一批统计好的热点数据至Redis,缓解初期数据库压力。
1.15.4.2 缓存穿透(Cache Penetration)
- 定义:查询的key既不在Redis也不在MySQL,高频查询导致MySQL压力过大;
- 成因:业务设计缺少参数校验、数据误删、黑客恶意攻击;
- 解决方案:
- 不存在的key仍写入Redis,value设为非法值(如""),设置短期过期时间;
- 引入布隆过滤器,查询前先判定key是否存在(布隆过滤器存储所有合法key)。
1.15.4.3 缓存雪崩(Cache Avalanche)
- 定义:短时间内大量key过期或Redis集群宕机,缓存命中率骤降,请求直达MySQL;
- 成因:大量key集中过期、Redis集群故障;
- 解决方案:
- 加强集群高可用(哨兵/集群模式);
- key过期时间添加随机因子,避免集中过期;
- 核心业务key不设置过期时间。
1.15.4.4 缓存击穿(Cache Breakdown)
- 定义:热点key过期瞬间,大量请求直达MySQL,导致数据库瞬时压力过大;
- 解决方案:
- 热点key设置永不过期;
- 访问数据库时使用分布式锁,限制并发请求数;
- 服务降级:系统压力过大时,降低非核心服务质量,保证核心服务正常运行(如省电模式降低亮度)。
1.16 分布式锁
分布式锁用于解决分布式系统中多个节点访问公共资源的并发问题。Java的synchronized或C++的std::mutex仅适用于单进程,分布式场景需借助公共服务器(如Redis、MySQL、ZooKeeper)实现锁机制。
1.16.1 Redis 单线程为何需要分布式锁?
- 场景一:多个客户端执行多条命令(非原子操作):
- t1:客户端A执行
GET→ 返回10; - t2:客户端B执行
GET→ 返回10; - t3:客户端A执行
SET→ 设置为9; - t4:客户端B执行
SET→ 设置为9; - 问题:Redis串行处理命令,但客户端A的
GET和SET之间被客户端B的GET插入,导致数据不一致。
- t1:客户端A执行
- 场景二:多个客户端执行单条原子命令(如
redis.incr("counter")):无并发问题,无需锁。
1.16.2 Redis 事务+Lua脚本能否替代分布式锁?
不能,原因:
- 无法执行复杂业务逻辑(如调用外部服务);
- 脚本执行会阻塞Redis主线程,长脚本影响性能;
- 调试困难。
1.16.3 必须使用分布式锁的场景
- 涉及多个数据源(Redis + MySQL + MQ);
- 需要调用外部服务;
- 业务逻辑复杂且耗时;
- 需要严格的数据一致性。
1.16.4 Redis 分布式锁的实现方案
1.16.4.1 原子加锁:setnx + 过期时间
- 核心命令:
set key value nx ex seconds(nx:key不存在时设置;ex:指定过期时间); - 作用:避免锁遗漏释放(过期时间自动释放锁),单个命令保证原子性(避免拆分
setnx和expire)。
1.16.4.2 锁标识:避免误删锁
- 问题:客户端A加锁后,客户端B可能误删该锁;
- 解决方案:
- 给每个客户端分配唯一标识(如UUID);
- 加锁时将value设为客户端标识(如
set lock:ticket UUID nx ex 10); - 解锁时先验证value是否为自身标识,再删除。
1.16.4.3 原子解锁:Lua脚本
-
问题:"查询标识→判断→删除"为非原子操作,可能导致误删;
-
解决方案:通过Lua脚本实现原子操作:
luaif redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
1.16.4.4 看门狗(Watch Dog):动态续约
- 问题:过期时间设置过短导致业务未执行完锁释放,设置过长导致并发度下降;
- 解决方案:动态续约:
- 初始设置较短过期时间(如1秒);
- 客户端启动独立线程(看门狗),在锁过期前300ms自动续约(如重新设置过期时间为1秒);
- 直至业务执行完成,手动释放锁;
- 解决业务未执行完、并发度低、服务器崩溃导致锁无法释放的问题。
1.16.4.5 高可用:Redlock 算法
- 问题:单个Redis节点作为锁服务器存在单点故障;
- 解决方案:Redlock 算法(冗余):
- 部署多组独立Redis实例(无主从关系);
- 客户端按顺序向每个实例发送
set nx ex命令加锁; - 若成功加锁的实例数超过总数的一半(如3/5个实例),则视为加锁成功;
- 解锁时需向所有实例发送解锁命令。
1.16.5 Redis 分布式锁的扩展类型
除基础互斥锁外,还可实现:
- 读写锁:区分读操作和写操作,支持多线程读、单线程写;
- 公平锁:按请求顺序分配锁;
- 可重入锁:同一客户端可多次加锁,需记录加锁次数,解锁时次数减为0才释放。