在Ubuntu 22.0.4 配置高可用Redis集群

1. 配置Redis集群

在Redis集群中,3台节点的架构通常采用“一主二从+哨兵(Sentinel)”模式(或纯主从模式,哨兵用于自动故障转移),这是中小规模场景下平衡高可用、性能与成本的经典方案。以下从工作原理核心优势以及安装方法三个方面详细说明:

一、3台Redis集群(一主二从+哨兵)工作原理

3台节点的角色分工明确:1个主节点(Master) 负责写操作与数据同步,2个从节点(Slave) 负责读操作与数据备份,同时搭配哨兵集群(通常3个哨兵,可与Redis节点混部) 实现故障自动检测与转移。整体流程可拆解为4个核心机制:

1. 角色分工:主从节点的核心职责

角色 数量 核心职责
主节点(Master) 1 1. 接收所有写请求(SET、DEL、HSET等)并执行;
2. 记录写操作到“复制积压缓冲区”,供从节点同步;
3. 不主动处理读请求(客户端可配置读主,但通常不推荐)。
从节点(Slave) 2 1. 接收主节点同步的全量/增量数据,保持与主节点数据一致;
2. 接收所有读请求(GET、HGET等),分担主节点读压力;
3. 主节点故障时,作为“候选主节点”等待晋升。
哨兵(Sentinel) 3 1. 监控主/从节点的存活状态(通过“心跳检测”);
2. 主节点故障时,通过“投票选举”从从节点中选新主;
3. 通知所有从节点切换到新主,更新客户端连接信息。

2. 数据同步:主从节点的数据一致性保障

Redis通过“全量同步+增量同步” 机制,确保从节点与主节点数据实时一致,流程如下:

(1)全量同步:从节点首次连接主节点时触发

  1. 从节点向主节点发送 PSYNC ? -1 命令,表明“首次同步,需要全量数据”;
  2. 主节点收到请求后,执行 BGSAVE 命令生成RDB快照文件(后台执行,不阻塞写操作),同时将快照生成期间的写命令记录到“复制积压缓冲区”;
  3. 主节点将RDB文件发送给从节点,从节点接收后清空本地数据,加载RDB文件;
  4. 主节点将“复制积压缓冲区”中记录的写命令发送给从节点,从节点执行这些命令,最终与主节点数据一致。

(2)增量同步:全量同步后持续触发

  1. 主节点每执行一次写命令,会将命令写入“复制积压缓冲区”,并记录自己的复制偏移量(类似“数据版本号”);
  2. 从节点每接收并执行一次主节点的命令,也会记录自己的复制偏移量,并定期向主节点发送 PSYNC 偏移量 命令,表明“已同步到该偏移量,需要后续命令”;
  3. 主节点对比自身偏移量与从节点偏移量,将“复制积压缓冲区”中从节点未同步的命令发送给从节点,实现“增量更新”。

关键细节:“复制积压缓冲区”是一个固定大小的环形缓冲区(默认1MB),用于避免从节点短暂断连后重新连接时需要再次全量同步,降低资源开销。

3. 故障转移:主节点故障时的自动恢复

当主节点因宕机、网络中断等原因不可用时,哨兵集群会自动触发故障转移,确保服务不中断,流程如下:

  1. 故障检测
    所有哨兵通过“定时发送PING命令”监控主/从节点。若主节点连续多次(可配置,默认30秒)未回复,哨兵标记其为“主观下线(SDOWN)”;
    随后哨兵之间通过“互相通信”确认:若超过半数哨兵(3个哨兵需≥2个)认为主节点下线,则标记为“客观下线(ODOWN)”,触发故障转移。

  2. 选举新主节点
    哨兵集群通过“Raft算法”从2个从节点中选举新主,选举标准优先级为:

    • 从节点的“复制偏移量”(越大越好,确保数据最新);
    • 从节点的“运行ID”(越小越好,作为备选)。
  3. 切换与通知

    1. 哨兵向“当选的从节点”发送 SLAVEOF NO ONE 命令,使其晋升为新主节点;
    2. 哨兵向剩余的从节点发送 SLAVEOF 新主节点IP:端口 命令,让它们从新主节点同步数据;
    3. 哨兵更新“集群信息”,并通知所有客户端(如通过配置中心、客户端订阅哨兵消息):后续写请求指向新主节点。

4. 读写分离:流量分发的核心逻辑

客户端通过“配置规则”实现读写分离,避免主节点因读请求过载:

  • 写请求:所有SET、DEL、HSET等写操作强制路由到主节点(确保数据唯一写入,避免分布式一致性问题);
  • 读请求:所有GET、HGET、LRANGE等读操作路由到2个从节点(可通过“轮询”“随机”“权重”等策略分发,分担主节点读压力)。

二、3台Redis集群的核心优势

3台“一主二从+哨兵”架构的优势,本质是解决了“单点故障”“性能瓶颈”“数据可靠性”三大核心问题,同时兼顾成本与易用性:

1. 高可用性(HA):无单点故障,服务不中断

  • 主节点故障时,哨兵可在10-30秒内自动完成故障转移,无需人工干预,避免因主节点宕机导致服务不可用;
  • 2个从节点互为备份,即使其中1个从节点故障,另1个从节点仍可提供读服务与数据备份,进一步降低故障风险。

    对比“单节点Redis”:单节点宕机后服务直接中断,恢复依赖人工重启,可用性极低。

2. 性能优化:读写分离,支撑更高并发

  • 读性能提升:2个从节点可分担90%以上的读请求(如电商商品详情、用户信息查询等读多写少场景),主节点仅专注于写操作,避免“读请求阻塞写请求”;
  • 吞吐量提升:假设单节点Redis读QPS为1万,2个从节点可将读QPS提升至2-2.5万(需排除网络瓶颈),整体集群吞吐量显著高于单节点。

3. 数据可靠性:多重备份,降低数据丢失风险

  • 从节点备份:2个从节点实时同步主节点数据,相当于“双份热备份”,即使主节点因硬件故障(如磁盘损坏)丢失数据,也可从从节点恢复;
  • RDB/AOF增强:主节点可关闭AOF(避免写性能损耗),从节点开启AOF(实时日志),既保证写性能,又通过从节点AOF确保数据不丢失(AOF比RDB更实时)。

    对比“单节点Redis”:单节点若磁盘损坏,数据直接丢失,仅能通过历史RDB备份恢复,存在数据增量丢失风险。

4. 部署成本低,易用性高

  • 资源成本:仅需3台服务器(或容器),可实现“高可用+读写分离”,相比Redis Cluster(需至少6台节点:3主3从)成本更低,适合中小规模业务;
  • 配置简单:主从复制与哨兵的配置均通过Redis原生参数实现(如 slaveof 配置从节点,sentinel monitor 配置哨兵),无需复杂的分布式协调逻辑;
  • 兼容性好:客户端无需适配特殊协议,只需支持“读写分离路由”(如Java的Jedis、Spring Data Redis均原生支持),迁移成本低。

5. 可扩展性:灵活应对业务增长

  • 读扩展:若后续读请求进一步增加,可直接新增从节点(如从2个扩至3个),无需修改主节点配置,快速提升读吞吐量;
  • 平滑升级:主节点需要扩容硬件时,可先将其中一个从节点升级,再通过故障转移将其设为主节点,实现“无感知升级”。

三、适用场景

3台Redis集群(一主二从+哨兵)并非万能,更适合以下场景:

  • 读多写少的业务(如商品详情、用户画像、缓存热点数据);
  • 对可用性要求较高(需99.9%以上),但并发量未达到“超大规模”(如QPS≤5万)的中小业务;
  • 成本敏感,无法承担Redis Cluster(6+节点)的资源开销。

1.1 安装Redis

三台服务器均需执行以下命令:

1
apt install redis && apt install redis-sentinel

服务器信息:

  • 192.168.40.100:2核2G Ubuntu 22.4
  • 192.168.40.101:2核2G Ubuntu 22.4
  • 192.168.40.102:4核4G Ubuntu 22.4

1.2 Master节点配置

编辑Master节点配置文件:

1
vim /etc/redis/master/redis.conf

插入以下配置内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
port 26379
daemonize yes
pidfile /var/run/redis-sentinel.pid
logfile "/var/redis/sentinel/sentinel.log"
dir "/var/redis/sentinel"
# 需要两台服务器协商后切换
sentinel monitor mymaster 192.168.40.102 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 180000
# 连接密码
sentinel auth-pass mymaster d7a47843428816aa17e654f27f0527cd7785f8182f072aecc1c62#########
sentinel deny-scripts-reconfig yes
sentinel parallel-syncs mymaster 1
sentinel resolve-hostnames no
sentinel announce-hostnames no

1.3 两台Slave节点配置(配置相同)

编辑Slave节点配置文件:

1
vim /etc/redis/slave/redis.conf

配置内容如下:

1
2
3
4
5
6
7
8
9
10
11
bind 0.0.0.0
port 6379
daemonize yes
pidfile /var/run/redis_6379.pid
logfile "/var/redis/slave/redis.log"
dbfilename "dump.rdb"
dir "/var/redis/slave"
requirepass "d7a47843428816aa17e654f27f0527cd7785f818"
masterauth "d7a47843428816aa17e654f27f0527cd7785f818"
# 同时指向Master节点
slaveof 192.168.40.102 6379

1.4 配置redis-sentinel监控Redis运行状态

三台服务器均需配置哨兵,编辑配置文件:

1
vim /etc/redis/sentinel/sentinel.conf

配置内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
port 26379
daemonize yes
pidfile /var/run/redis-sentinel.pid
logfile "/var/redis/sentinel/sentinel.log"
dir "/var/redis/sentinel"
sentinel monitor mymaster 175.178.15.146 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 180000
sentinel auth-pass mymaster d7a47843428816aa17e654f27f0527cd7785f8182f072aecc1c62c4cfd316810

# 添加关键参数
sentinel deny-scripts-reconfig yes
sentinel parallel-syncs mymaster 1
sentinel resolve-hostnames no
sentinel announce-hostnames no

创建日志文件

三台服务器共同执行以下命令:

1
mkdir /var/redis/sentinel && touch /var/redis/sentinel/sentinel.log

重启服务

配置完成后,重启Redis及Redis-sentinel:

1
systemctl restart redis && systemctl restart redis-sentinel

2. 配置ThinkPHP 8

2.1 编辑缓存配置文件

修改app/config/cache.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------

return [
'default' => 'redis',
'stores' => [
'redis' => [
'type' => 'app\cache\PredisSentinel', // 指向自定义驱动
'prefix' => 'tp:',
],
],
];

2.2 创建Redis驱动

安装依赖

在项目根目录执行以下命令安装Predis驱动:

1
composer require predis/predis

创建自定义驱动文件

app目录下创建cache目录,新建PredisSentinel.php文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php

namespace app\cache;

use think\cache\driver\Redis as BaseRedis;
use Predis\Client;
use Predis\Connection\ConnectionException;
use think\facade\Log;

class PredisSentinel extends BaseRedis
{
protected $handler = null;

public function handler()
{
if (is_null($this->handler)) {
$sentinels = [
'tcp://127.0.0.1:26379', // 102服务使用回环地址,减少带宽占用
'tcp://192.168.100:26379',
'tcp://192.168.101:26379',
];

$options = [
'replication' => 'sentinel',
'service' => 'mymaster',
'parameters' => [
'password' => 'd7a47843428816aa17e654f27f0527cd7785f8182f072aecc1c62c4cfd316810',
'database' => 0,
'timeout' => 1.5,
],
'retry_limit' => 5,
'sentinel_timeout' => 0.5,
'retry_interval' => 100,
];

try {
$this->handler = new Client($sentinels, $options);
$this->handler->connect();

// 验证连接状态
if (!$this->handler->isConnected()) {
throw new ConnectionException("Predis connected but not active");
}

} catch (ConnectionException $e) {
Log::error("Redis哨兵连接失败: " . $e->getMessage());

try {
$fallback = new Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'password' => 'd7a47843428816aa17e654f27f0527cd7785f8182f072aecc1c62c4cfd316810',
'database' => 0
]);

$fallback->connect();
$this->handler = $fallback;
Log::warning("使用Redis主节点直连作为后备方案");
} catch (ConnectionException $e2) {
Log::error("Redis主节点直连失败: " . $e2->getMessage());
throw new \RuntimeException("所有Redis连接尝试均失败");
}
}
}

return $this->handler;
}
}

至此,配置完成。可通过关闭任意节点的Redis服务验证集群容错能力,系统应能继续正常工作。