Redis核心特性与应用全解析

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.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(高可用性):通过冗余备份机制保障系统稳定运行,降低单点故障风险。
  • 高性能优势
    1. 数据存储于内存,读写速度远超磁盘存储;
    2. 功能设计简洁,数据处理耗时低;
    3. 采用多路复用技术优化网络 IO;
    4. 单线程模型避免线程切换开销(Redis 性能瓶颈在于网络 IO,而非 CPU,多线程在 CPU 密集型任务中更具优势)。

1.1.2 应用场景

  1. 数据库(适用于高并发读写场景);
  2. 缓存/会话存储(减轻后端数据库压力);
  3. 消息队列(非核心应用场景,一般不推荐优先使用)。

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 客户端工具

  1. Redis 自带命令行客户端;
  2. 图形化客户端(如 Redis Desktop Manager 等);
  3. 基于 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 过期策略

  1. 惰性删除(懒汉模式):仅当 key 被访问时才检查其过期时间,过期则删除;
  2. 定期删除:定期、集中扫描部分设置了过期时间的键,删除已过期的键;
  3. 定时删除:为每个 key 配置独立定时器,到期触发删除。该策略未被采用,因大量定时器会消耗过多 CPU 资源,严重影响 Redis 主线程性能与响应时间。
1.3.3.1 定时器实现方案
  1. 基于优先级队列/堆:分配独立线程,每隔固定时间检查堆顶元素是否过期;若未过期,线程休眠至过期时间;若有新任务触发,可唤醒线程;
  2. 基于时间轮:将时间划分为固定间隔的扇区,每个扇区挂载任务链表(链表节点为待执行函数),线程遍历扇区时执行对应任务,扇区数量与间隔可根据业务调整。

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 单线程高性能原因

  1. 数据存储于内存,读写无磁盘 IO 开销;
  2. 功能设计简洁,数据处理逻辑耗时低;
  3. 避免多线程切换带来的性能损耗;
  4. 采用多路复用技术优化网络 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 应用场景
  1. 用户标签、用户画像(如给用户添加"喜欢运动""关注科技"等标签);
  2. 共同好友计算、好友推荐(基于集合交集操作);
  3. 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 客户端类型

  1. Redis 自带命令行客户端;
  2. 图形化客户端;
  3. 基于 API 开发的自定义客户端(需遵循 RESP 协议)。

1.9.2 RESP 协议(Redis Serialization Protocol)

Redis 采用的应用层协议,具有以下优点:

  1. 简单易实现;
  2. 解析速度快;
  3. 肉眼可读。

客户端发送的命令以批量字符串数组形式传输,服务端根据命令类型返回不同格式的结果。


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 ""关闭自动存储),避免使用shutdownservice redis-server restart命令重启服务器,此时快照未更新,内存中新增/修改的数据未同步至RDB,重启后会丢失,即可复现偏差。

1.10.1.3.2 如何实现RDB文件损坏导致服务器无法启动
  1. 修改dump.rdb文件内容;
  2. 通过kill -9强制终止Redis服务器(正常退出会重写RDB文件,无法模拟损坏);
  3. 启动Redis服务器,会因RDB文件损坏而崩溃;
  4. 查看/var/log/redis目录下的日志文件(默认redis-server.log,日志过大时会自动压缩);
  5. 可使用/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性能下降?

不会,原因如下:

  1. AOF并非直接将数据写入硬盘,而是先写入应用层缓冲区(aof_buf),减少IO次数。性能瓶颈不在于数据量,而在于硬盘写入次数;
  2. 缓冲区数据刷新到磁盘时为顺序读写,速度较快(磁轴无需多次寻址);
  3. 仍存在数据丢失风险:缓冲区数据仅存储在内存,若服务器宕机时数据未落地,会导致数据丢失。
1.10.2.3 AOF 刷盘策略

配置文件支持三种缓冲区刷新到磁盘的策略,平衡性能与可靠性:

  1. always:每执行一条命令,立即调用fsync将缓冲区数据刷新到磁盘(数据零丢失,性能开销最大);
  2. everysec:命令写入缓冲区后,先通过write拷贝到内核层缓冲区,每秒由同步线程执行fsync(默认配置,平衡性能与可靠性);
  3. 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 主从配置方式

  1. 配置文件方式:在从节点配置文件中添加slaveof [masterHost] [masterPort]
  2. 命令行参数方式:启动从节点时指定--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 主从复制拓扑结构

  1. 一主一从:结构简单,主节点可关闭AOF减少IO开销,但存在数据丢失风险,主节点宕机后重启需从从节点同步数据;
  2. 一主多从:提升读并发能力,但从节点数量增多会增加主节点带宽负载,需配备高性能网卡;
  3. 树状结构:从节点作为其他从节点的主节点,分散主节点带宽压力,但会增加数据同步延迟。

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
    • 主节点:记录已传播给从节点的命令字节位置(累计值);
    • 从节点:记录已从主节点复制的最后一个命令字节位置;
    • 从节点每秒向主节点上报自身偏移量;
    • replicationidoffset共同唯一标识一份数据集合,二者一致则表示主从节点数据完全同步。
1.12.8.3 全量复制

当从节点第一次连接主节点,或无法进行部分重同步时触发:

  1. 从节点发送psync ? -1命令,请求全量同步;
  2. 主节点返回FULLRESYNC响应,携带自身replicationidoffset
  3. 主节点执行bgsave生成RDB快照,同时将期间的写命令记录到复制缓冲区;
  4. 主节点发送RDB快照至从节点,从节点加载快照;
  5. 主节点发送复制缓冲区中的增量命令,从节点执行后完成同步。
1.12.8.4 增量复制

全量复制的优化手段,适用于主从节点断开后重连的场景:

  1. 主从节点断开期间,主节点将写命令记录到复制缓冲区;
  2. 从节点重连后,发送psync [replicationid] [offset]命令(携带上次同步的replicationidoffset);
  3. 主节点验证replicationid是否匹配,且offset是否在复制缓冲区范围内;
  4. 若验证通过,主节点发送缓冲区中offset之后的增量命令,从节点执行(返回CONTINUE响应);
  5. 若验证失败(如replicationid不匹配或offset超出缓冲区范围),则触发全量复制。
1.12.8.5 实时复制(增量复制)

主从节点完成全量同步后,主节点收到的新写命令会通过长连接实时同步至从节点:

  • 长连接需保活机制,通过心跳包维持:
    • 主节点:默认每10秒发送一次ping包,从节点收到后返回pong
    • 从节点:默认每秒向主节点上报自身offset
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 从节点晋升为主节点的场景

  1. 人工干预 :从节点执行slaveof no one,主动断开主从关系,晋升为主节点;
  2. 主节点宕机 :从节点不会自动晋升,执行info replication会显示master_link_status:down,需通过哨兵机制实现自动晋升。

1.12.10 主节点无法重启的权限问题

  • 若通过systemctl start redis启动主节点(默认使用redis用户),而通过root用户命令行启动从节点,从节点生成的AOF/RDB文件所有者为root;
  • 若未修改多实例配置文件中的dirappendfilename,主节点宕机后重启时,因redis用户无root权限的文件读写权限,导致无法启动。

1.13 哨兵机制(Sentinel)

哨兵机制用于解决主从复制架构的主节点故障自动切换问题。在传统主从模式中,主节点故障后需人工切换主从关系,且客户端需重新配置连接,无法满足大规模应用的高可用需求。

1.13.1 核心组件

  • 数据节点 :主节点(master)和从节点(slave),均为redis-server进程,负责存储数据;
  • 哨兵节点 :独立的redis-sentinel进程,不存储数据,仅负责监控数据节点、执行故障转移、通知客户端。

1.13.2 哨兵的核心功能

  1. 监控:哨兵节点与数据节点建立TCP长连接,通过定期发送心跳包检测节点存活状态;
  2. 自动故障转移:主节点故障时,自动从从节点中选举新主节点,重新配置其他从节点指向新主节点;
  3. 通知:将故障转移结果通知客户端,实现客户端无缝切换。

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.confsentinel2.confsentinel3.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 部署与验证
  1. 启动数据节点:docker-compose up -d

  2. 启动哨兵节点:docker-compose -f docker-compose-sentinel.yml up -d

  3. 哨兵启动后,sentinel.conf会自动新增以下内容(由CONFIG REWRITE生成):

    xml 复制代码
    # Generated by CONFIG REWRITE
    // 哨兵启动后新增内容(三个哨兵的生成内容不同)
  4. 模拟主节点故障:docker stop redis-master

  5. 查看哨兵日志(以sentinel1为例):

    bash 复制代码
    redis-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
  6. 重启原主节点: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计算所属槽位;
    • 槽位均匀分配给多个分片(主节点),槽位可连续或非连续分配,仅要求数量均匀;
    • 分片通过位图标识自身负责的槽位。
  • 示例分配(N=3个分片):
    • 0号分片:[0, 5461](5462个槽位);
    • 1号分片:[5462, 10923](5462个槽位);
    • 2号分片:[10924, 16383](5460个槽位)。
  • 扩容示例(新增1个分片,总4个):
    • 0号分片:[0, 4095](4096个槽位);
    • 1号分片:[5462, 9557](4096个槽位);
    • 2号分片:[10924, 15019](4096个槽位);
    • 3号分片:[4096, 5461] + [9558, 10923] + [15020, 16383](4096个槽位)。

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的GETSET之间被客户端B的GET插入,导致数据不一致。
  • 场景二:多个客户端执行单条原子命令(如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 secondsnx:key不存在时设置;ex:指定过期时间);
  • 作用:避免锁遗漏释放(过期时间自动释放锁),单个命令保证原子性(避免拆分setnxexpire)。
1.16.4.2 锁标识:避免误删锁
  • 问题:客户端A加锁后,客户端B可能误删该锁;
  • 解决方案:
    • 给每个客户端分配唯一标识(如UUID);
    • 加锁时将value设为客户端标识(如set lock:ticket UUID nx ex 10);
    • 解锁时先验证value是否为自身标识,再删除。
1.16.4.3 原子解锁:Lua脚本
  • 问题:"查询标识→判断→删除"为非原子操作,可能导致误删;

  • 解决方案:通过Lua脚本实现原子操作:

    lua 复制代码
    if 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 分布式锁的扩展类型

除基础互斥锁外,还可实现:

  1. 读写锁:区分读操作和写操作,支持多线程读、单线程写;
  2. 公平锁:按请求顺序分配锁;
  3. 可重入锁:同一客户端可多次加锁,需记录加锁次数,解锁时次数减为0才释放。
相关推荐
hgz07102 小时前
Redis多实例部署与主从架构
redis
长安第一美人10 小时前
C 语言可变参数(...)实战:从 logger_print 到通用日志函数
c语言·开发语言·嵌入式硬件·日志·工业应用开发
Larry_Yanan10 小时前
Qt多进程(一)进程间通信概括
开发语言·c++·qt·学习
superman超哥10 小时前
仓颉语言中基本数据类型的深度剖析与工程实践
c语言·开发语言·python·算法·仓颉
不爱吃糖的程序媛10 小时前
Ascend C开发工具包(asc-devkit)技术解读
c语言·开发语言
bu_shuo10 小时前
MATLAB奔溃记录
开发语言·matlab
SAP小崔说事儿10 小时前
在数据库中将字符串拆分成表单(SQL和HANA版本)
java·数据库·sql·sap·hana·字符串拆分·无锡sap
J ..11 小时前
C++ 多线程编程基础与 std::thread 使用
c++
你的冰西瓜11 小时前
C++标准模板库(STL)全面解析
开发语言·c++·stl