中级

Redis高可用与集群技术详解

保障数据可靠性与系统扩展性的核心技术

术语定义

高可用 (High Availability)

指系统在面对各种故障时能够持续提供服务的能力,通常通过冗余设计和自动故障转移实现。在Redis中,主要通过哨兵模式Redis Sentinel是Redis的高可用解决方案,能够监控主节点状态并在主节点故障时自动进行故障转移。集群模式Redis Cluster是Redis的分布式解决方案,提供数据分片、自动故障转移和线性扩展能力。实现。

核心概念

  • 故障转移 (Failover):当主节点不可用时,自动选举并提升一个从节点为新的主节点
  • 数据分片 (Sharding):将数据分散存储在多个节点上,提高系统容量和性能
  • 一致性哈希 (Consistent Hashing):一种数据分布算法,在节点变化时最小化数据迁移
  • 主从复制 (Master-Slave Replication):数据从主节点复制到从节点,提供数据冗余

实现细节与优化

哨兵模式 (Sentinel)

sentinel.conf 配置示例
# 监控主节点,名称为mymaster,IP为127.0.0.1,端口为6379,当2个哨兵认为主节点故障时才进行故障转移
sentinel monitor mymaster 127.0.0.1 6379 2

# 哨兵认为主节点下线所需的毫秒数
sentinel down-after-milliseconds mymaster 5000

# 故障转移过程中,同时进行同步的从节点数量
sentinel parallel-syncs mymaster 1

# 故障转移超时时间
sentinel failover-timeout mymaster 180000
%%{init: {'theme': 'neutral', 'themeVariables': { 'fontSize': '14px', 'fontFamily': 'Noto Sans SC, Inter, sans-serif' }}}%% graph TD A[哨兵1] -->|监控| B[主节点] C[哨兵2] -->|监控| B D[哨兵3] -->|监控| B B -->|复制| E[从节点1] B -->|复制| F[从节点2] A -.->|监控| E A -.->|监控| F C -.->|监控| E C -.->|监控| F D -.->|监控| E D -.->|监控| F A <-->|通信| C A <-->|通信| D C <-->|通信| D classDef sentinel fill:#f87171,stroke:#dc2626,color:white; classDef master fill:#818cf8,stroke:#4f46e5,color:white; classDef slave fill:#a3e635,stroke:#65a30d,color:black; class A,C,D sentinel; class B master; class E,F slave;

工作流程:

  1. 哨兵持续监控主从节点的健康状态
  2. 当主节点不可用时,哨兵会进行投票选举一个哨兵作为领导者
  3. 领导者哨兵选择一个从节点提升为新的主节点
  4. 通知其他从节点更新复制目标为新的主节点
  5. 通知客户端主节点已更改

集群模式 (Cluster)

redis.conf 集群配置示例
# 启用集群模式
cluster-enabled yes

# 集群配置文件(自动生成)
cluster-config-file nodes-6379.conf

# 节点超时时间
cluster-node-timeout 15000

# 集群节点IP
bind 0.0.0.0

# 启用集群总线通信的端口
cluster-announce-port 6379
cluster-announce-bus-port 16379
%%{init: {'theme': 'neutral', 'themeVariables': { 'fontSize': '14px', 'fontFamily': 'Noto Sans SC, Inter, sans-serif' }}}%% graph TD subgraph "分片0: 0-5460" A[主节点0] -->|复制| B[从节点0] end subgraph "分片1: 5461-10922" C[主节点1] -->|复制| D[从节点1] end subgraph "分片2: 10923-16383" E[主节点2] -->|复制| F[从节点2] end A <-->|集群总线| C A <-->|集群总线| E C <-->|集群总线| E classDef shard0 fill:#f87171,stroke:#dc2626,color:white; classDef shard1 fill:#818cf8,stroke:#4f46e5,color:white; classDef shard2 fill:#a3e635,stroke:#65a30d,color:black; class A,B shard0; class C,D shard1; class E,F shard2;

数据分片原理:

  • Redis集群使用哈希槽Redis集群使用16384个哈希槽来分配数据,每个键根据CRC16算法计算出一个槽位,然后映射到对应的节点。(Hash Slot)进行数据分片
  • 共有16384个哈希槽,平均分配给各个主节点
  • 每个键通过CRC16(key) mod 16384计算出所属的槽位
  • 集群支持在线水平扩展,通过迁移槽位实现

事务与Lua脚本

Redis事务示例
MULTI
SET account:1:balance 300
SET account:2:balance 700
EXEC
Lua脚本实现分布式锁续期
-- 分布式锁续期脚本
-- KEYS[1]: 锁的键名
-- ARGV[1]: 锁的值(通常是唯一标识符)
-- ARGV[2]: 新的过期时间(秒)

if redis.call('get', KEYS[1]) == ARGV[1] then
  return redis.call('expire', KEYS[1], ARGV[2])
else
  return 0
end

应用场景

哨兵模式应用场景

  • 中小型应用需要高可用但数据量不大
  • 对数据一致性要求较高的场景
  • 读多写少的业务场景
  • 实例:电商网站的商品缓存、会话存储
Java客户端连接哨兵示例
Set sentinels = new HashSet<>();
sentinels.add("127.0.0.1:26379");
sentinels.add("127.0.0.1:26380");
sentinels.add("127.0.0.1:26381");

JedisSentinelPool pool = new JedisSentinelPool(
    "mymaster", sentinels, config);

try (Jedis jedis = pool.getResource()) {
    jedis.set("key", "value");
}

集群模式应用场景

  • 大型应用需要横向扩展存储容量
  • 需要处理高并发读写的场景
  • 数据量大且持续增长的业务
  • 实例:社交媒体的用户数据、游戏排行榜
Java客户端连接集群示例
Set nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7001));
nodes.add(new HostAndPort("127.0.0.1", 7002));
nodes.add(new HostAndPort("127.0.0.1", 7003));

JedisCluster cluster = new JedisCluster(nodes);
cluster.set("key", "value");

比较分析

特性 哨兵模式 (Sentinel) 集群模式 (Cluster)
数据分片 ❌ 不支持 ✅ 支持(哈希槽)
水平扩展 ❌ 有限(只能通过读写分离) ✅ 支持动态扩缩容
故障转移 ✅ 自动(由哨兵完成) ✅ 自动(由集群节点完成)
部署复杂度 中等 较高
多键操作 ✅ 支持 ❌ 有限(仅同槽位的键)
事务支持 ✅ 完全支持 ❌ 有限(仅同槽位的键)
内存使用 较少 较多(集群元数据)
适用数据量 小至中等 中等至大型

内存管理与性能优化

内存淘汰策略

  • noeviction:不淘汰,内存满时返回错误
  • allkeys-lru:淘汰最近最少使用的键
  • volatile-lru:淘汰设置了过期时间的键中最近最少使用的
  • allkeys-lfu:淘汰使用频率最低的键
  • volatile-lfu:淘汰设置了过期时间的键中使用频率最低的
  • allkeys-random:随机淘汰键
  • volatile-random:随机淘汰设置了过期时间的键
  • volatile-ttl:淘汰即将过期的键

大Key和热Key问题

大Key:单个键占用大量内存,可能导致:

  • 内存使用不均衡
  • 网络带宽占用高
  • 阻塞Redis主线程

热Key:频繁访问的键,可能导致:

  • 单节点负载过高
  • 网络流量不均衡
  • 影响整体性能

性能优化技巧

Pipeline批量操作示例
// 使用Pipeline批量操作
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
    pipeline.set("key" + i, "value" + i);
}
// 一次性发送所有命令并获取结果
List<Object> results = pipeline.syncAndReturnAll();

总结

mindmap
  root((Redis高可用与集群))
    哨兵模式
      监控主从节点
      自动故障转移
      客户端通知
      适合中小规模应用
    集群模式
      数据分片
      哈希槽分配
      动态扩缩容
      适合大规模应用
    内存管理
      淘汰策略
      大Key优化
      热Key处理
    性能优化
      Pipeline批量操作
      Lua脚本
      慢查询分析
                    

常见问题与解决方案

Q: 哨兵模式和集群模式如何选择?

A: 如果数据量不大但需要高可用,选择哨兵;如果数据量大且需要横向扩展,选择集群。

Q: 集群模式下为什么不支持跨槽事务?

A: 因为Redis集群采用分布式架构,不同槽位的数据可能在不同节点上,无法保证分布式事务的原子性。

Q: 如何处理大Key问题?

A: 将大Key拆分为多个小Key,如将大Hash拆分为多个小Hash,或使用分段存储策略。

Q: 如何解决热Key问题?

A: 使用本地缓存、增加读取副本、使用分布式缓存或对热Key进行复制到多个槽位。

参考资料

官方文档

推荐学习资源

进阶学习主题

Redis Stream

Redis 5.0引入的数据类型,可用于消息队列和事件流处理

Redis Module

通过模块扩展Redis功能,如RedisJSON、RediSearch等

Redis ACL

Redis 6.0引入的访问控制列表,提供更细粒度的权限控制

高级主题

慢查询分析与优化

慢查询配置与分析
# 配置慢查询日志
# 执行时间超过多少微秒的命令会被记录
CONFIG SET slowlog-log-slower-than 10000

# 慢查询日志最多保存多少条
CONFIG SET slowlog-max-len 128

# 查看慢查询日志
SLOWLOG GET [n]

# 获取慢查询日志长度
SLOWLOG LEN

# 重置慢查询日志
SLOWLOG RESET

慢查询优化策略:

  • 避免使用O(N)复杂度的命令处理大量数据,如KEYS、LRANGE、SMEMBERS等
  • 使用SCAN系列命令代替KEYS命令
  • 合理设置数据过期时间,避免实例中存在过多数据
  • 使用Pipeline批量处理命令,减少网络往返时间

Redis事务的ACID特性

Redis事务提供了一种将多个命令打包,然后一次性、按顺序地执行的机制。但与传统数据库的事务相比,Redis事务的ACID特性有所弱化:

Redis事务支持的特性

  • 原子性(Atomicity):部分支持,所有命令要么全部执行,要么全不执行,但命令执行错误不会导致回滚
  • 隔离性(Isolation):完全支持,事务执行期间不会被其他命令打断

Redis事务不支持的特性

  • 一致性(Consistency):不保证,语法错误会导致整个事务失败,运行时错误不会影响其他命令执行
  • 持久性(Durability):取决于持久化配置,不是事务本身的特性

使用Lua脚本替代事务

Lua脚本提供了比Redis事务更强的原子性保证,因为脚本作为一个整体执行,且执行过程中不会被其他命令打断。对于需要条件判断和复杂逻辑的场景,Lua脚本是更好的选择。