术语定义
高可用 (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;
工作流程:
- 哨兵持续监控主从节点的健康状态
- 当主节点不可用时,哨兵会进行投票选举一个哨兵作为领导者
- 领导者哨兵选择一个从节点提升为新的主节点
- 通知其他从节点更新复制目标为新的主节点
- 通知客户端主节点已更改
集群模式 (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 开源项目
- Redis 官方博客
- 《Redis开发与运维》- 付磊, 张益军著
- 《Redis设计与实现》- 黄健宏著
进阶学习主题
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脚本是更好的选择。