- 本文使用的redis版本是 6.2.6
- 本文分两部分
- 第一部分是安装Redis单机版
- 第二部分是哨兵介绍和实操部署演示
一、安装redis并使用systemd管理进程
手动编译并安装 Redis 6.2.6
下载 Redis 6.2 源代码
wget http://download.redis.io/releases/redis-6.2.6.tar.gz
解压和编译
tar xzf redis-6.2.6.tar.gz
cd redis-6.2.6
make
sudo make install
使用systemd管理 Redis 服务
创建 Redis 服务文件:
sudo vim /etc/systemd/system/redis.service
[Unit]
Description=Redis In-Memory Data Store
After=network.target
[Service]
ExecStart=/home/hzz/redis/redis-6.2.6/src/redis-server /home/hzz/redis/redis-6.2.6/redis.conf
ExecStop=/home/hzz/redis/redis-6.2.6/src/redis-cli shutdown
Restart=always
User=hzz
Group=hzz
[Install]
WantedBy=multi-user.target
启动并设置 Redis 为开机自启
sudo systemctl daemon-reload
sudo systemctl start redis
sudo systemctl enable redis
检测状态以及redis版本
sudo systemctl status redis
检测版本已及连通性:
redis-cli -h localhost 6379
INFO SERVER
在另外两台机器也是一样的操作,现在我的三台虚拟机都安装好了redis (版本6.2.6):
- 192.168.122.136(vm06)
- 192.168.122.137(vm07)
- 192.168.122.138(vm08)
均已经安装好单机的redis,接下来我将其改造为redis集群(哨兵模式)
二、部署哨兵集群
搭建 Redis 哨兵模式需要至少三个 Redis(我现在正好3台) 实例来保证高可用性,推荐在三个不同的虚拟机上分别部署 Redis 和 Redis Sentinel进程。以下是具体步骤:
1.配置 Redis 主从复制
首先,选择一个 Redis 实例作为主节点(Master),其他作为从节点(Slave)。
配置主节点
在主节点( 192.168.122.136)的 Redis 配置文件中(我的redis.conf配置文件在 /home/hzz/redis/redis-6.2.6/redis.conf),修改为以下配置:
# 允许外部访问 Redis
bind 0.0.0.0
# 关闭保护模式(允许外部访问)
protected-mode no
# Redis 监听端口,默认 6379
port 6379
之后启动 Redis 主节点:
sudo systemctl start redis
配置从节点
在另外两个从节点(假设为 192.168.122.137 和 192.168.122.138)的 Redis 配置文件中添加如下配置,使它们作为从节点连接到主节点:
命令:sudo vim /home/hzz/redis/redis-6.2.6/redis.conf
bind 0.0.0.0
protected-mode no
port 6379
# 指定主节点 IP 和端口
replicaof 192.168.122.136 6379
两个从节点都配置好后,分别启动这俩 Redis 从节点(192.168.122.137和 192.168.122.138):
sudo systemctl start redis
使用redis-cli -p 6379 INFO replication查看当前节点以及主节点情况:
可以看到 136是redis主节点, 137和138分别是从节点。
ok到这里redis主动复制旧搭建好了,但是一般我们还可以配置哨兵对其进行监控,让其在发生故障时能够重新选主,保证redis的高可用。
所谓哨兵,听这个词就知道,肯定是有进程去监听redis ,然后当期发生宕机时 去做一些事情(也就是故障恢复)。下边我们对redis哨兵进行个简介。
2.Redis 哨兵模式简介
Redis 哨兵模式(Redis Sentinel)是一种高可用性解决方案,用于监控和管理 Redis 实例,以确保其在节点故障时能够自动恢复。哨兵模式通过提供故障检测、通知、自动故障转移和配置管理等功能,帮助用户构建可靠的 Redis 集群。
相关功能
- 监控:
- 哨兵会定期检查 Redis 主节点和从节点的状态,确保它们正常运行。通过发送 ping 请求,哨兵能够快速发现节点故障。
- 自动故障转移:
- 当主节点出现故障时,哨兵会自动选择一个从节点提升为新的主节点。此过程不需要人工干预,可以保证服务的连续性。
- 通知功能:
- 哨兵能够在检测到故障时通过不同方式(如钉钉、邮件等)通知系统管理员,从而及时处理问题。
- 动态配置刷新:
- 哨兵可以动态更新客户端的主节点和从节点配置,确保应用程序总是连接到可用的节点。
哨兵机制示意图
不带画了 ,理解起来应该比较简单找个网图放这里得了:
有个简单认识后,下边开始配置哨兵。
3.配置 Redis 哨兵
在每台虚拟机(192.168.122.136,192.168.122.137,192.168.122.138)上配置并启动 Redis Sentinel,以实现自动故障转移功能。
创建哨兵配置文件
因为我是手动安装的redis 所以 哨兵文件 sentinel.conf已经在 安装目录了,如图:
所以我直接编辑哨兵文件即可(命令:sudo vim /home/hzz/redis/redis-6.2.6/sentinel.conf),编辑内容如下:(这里我们配置136为主节点,主节点名称为 hzz_redis_master)
# 哨兵默认端口
port 26379
# 后台运行 system管理的话其实不用这么设置,不过这里显示指定也没错
daemonize yes
# 哨兵日志文件
logfile "/var/log/redis/sentinel.log"
# 指定监控的主节点信息
# hzz_redis_master 是主节点别名(这里我定义为 hzz_redis_master ) 之后跟的主节点的ip和端口,
# 最后一个参数 quorum = 2 表示仲裁数量,这个值比较重要,简单来说这个值的意思是:
# 至少需要 quorum 个数量的哨兵认为主节点已下线,才能执行故障转移的操作
sentinel monitor hzz_redis_master 192.168.122.136 6379 2
# 超过 5 秒没响应认为主节点不可达
sentinel down-after-milliseconds hzz_redis_master 5000
# 故障转移的超时时间设置为 10000 毫秒(10 秒)。如果在此时间内故障转移未完成,将放弃本次转移
sentinel failover-timeout hzz_redis_master 10000
# 设置通知脚本,用于在主从切换时发送通知(注意要给脚本赋予执行权限 )
# chmod +x dingding-notification.sh 或 将权限搞到最大: chmod +777 dingding-notification.sh
sentinel notification-script hzz_redis_master /home/hzz/redis/dingding-notification.sh
因为我指定了主从切换时的通知脚本,所以这里把通知脚本的内容也粘出来:
ps: (这个脚本会在每一个状态 (有很多状态,具体可以见脚本的中文翻译) 都发送钉钉通知,并且有可能3个哨兵都发,不太直观,但是这个没关系,主要是这个故障要确保最大努力的通知到(这也是我不把通知脚本交给某一个哨兵的原因),在收到钉钉通知后,之后的排查和观察就得去服务器上看日志了)
通知脚本(/home/hzz/redis/dingding-notification.sh)内容如下:
#!/bin/bash
# 设置日志路径
log_file="/home/hzz/redis/dingding_notification.log"
# 钉钉 webhook 地址
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=1987306d4d783fef4c2ad3417809992b306bea49b59e1c5f68d0c2b2bc8e6bbc"
# 哨兵配置文件路径
sentinel_config="/home/hzz/redis/redis-6.2.6/sentinel.conf"
# 获取 Sentinel 的 myid
sentinel_id=$(grep -i "^sentinel myid" "$sentinel_config" | awk '{print $3}')
# 如果未找到 myid,则默认为 unknown
if [ -z "$sentinel_id" ]; then
sentinel_id="unknown"
fi
# 获取当前时间戳
current_time=$(date '+%Y-%m-%d %H:%M:%S')
# 初始化状态信息变量
status_message=""
# 检查是否有状态信息
if [ "$#" -gt 0 ]; then
# 拼接状态信息到通知消息体
status_message="【告警】Redis 主节点发生故障!\n"
status_message+="故障发生时间: $current_time\n"
status_message+="触发哨兵的id: $sentinel_id\n\n"
# 处理每个状态变化
for status in "$@"; do
# 添加中文描述
case "$status" in
+sdown*)
status_message+="$status (主节点下线)\n"
;;
+odown*)
status_message+="$status (从节点下线)\n"
;;
+new-epoch*)
status_message+="$status (新一轮选举)\n"
;;
+vote-for-leader*)
status_message+="$status (选举领导者)\n"
;;
+elected-leader*)
status_message+="$status (当选领导者)\n"
;;
+try-failover*)
status_message+="$status (尝试故障转移)\n"
;;
+switch-master*)
status_message+="$status (选择主节点)\n"
;;
+promoted-slave*)
status_message+="$status (提升从节点为主节点)\n"
;;
+selected-slave*)
status_message+="$status (选择从节点)\n"
;;
+config-update-from*)
status_message+="$status (更新哨兵的配置信息)\n"
;;
+failover-state-select-slave*)
status_message+="$status (选择从节点进行故障转移)\n"
;;
+failover-state-send-slaveof-noone*)
status_message+="$status (发送从节点无主命令)\n"
;;
+failover-state-wait-promotion*)
status_message+="$status (等待提升状态)\n"
;;
+failover-state-reconf-slaves*)
status_message+="$status (重新配置从节点)\n"
;;
+slave-reconf-sent*)
status_message+="$status (从节点重新配置已发送)\n"
;;
+slave-reconf-inprog*)
status_message+="$status (从节点重新配置进行中)\n"
;;
+slave-reconf-done*)
status_message+="$status (从节点重新配置完成)\n"
;;
+failover-end*)
status_message+="$status (故障转移结束)\n"
;;
*)
status_message+="$status\n" # 默认情况
;;
esac
done
# 发送钉钉通知
response=$(curl -s -o /dev/null -w "%{http_code}" "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"msgtype\": \"text\", \"text\": {\"content\": \"$status_message\"}}")
# 检查钉钉通知发送结果
if [ "$response" -eq 200 ]; then
echo "$current_time - 消息发送成功" >> "$log_file"
else
echo "$current_time - 消息发送失败,HTTP 状态码: $response" >> "$log_file"
fi
# 输出日志
echo "$current_time - 状态信息:$status_message" >> "$log_file"
else
echo "$current_time - 没有状态信息,未发送钉钉通知。" >> "$log_file"
fi
之后,将这个文件使用scp 分别copy到 137和138(这样哨兵就部署了3个, 防止单机哨兵挂掉后, 哨兵机制失效):
scp /home/hzz/redis/redis-6.2.6/sentinel.conf root@192.168.122.137:/home/hzz/redis/redis-6.2.6/
scp /home/hzz/redis/redis-6.2.6/sentinel.conf root@192.168.122.138:/home/hzz/redis/redis-6.2.6/
哨兵文件sentinel.conf 准备好了
启动哨兵进程
为了方便管理哨兵进程,这里使用system进行管理,在每台虚拟机(136,137,138)上分别创建并编辑文件:sudo vim /etc/systemd/system/redis-sentinel.service 加入以下内容:
[Unit]
Description=Redis Sentinel
After=network.target
[Service]
# 设置环境变量,确保所有命令的路径都能正确找到
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/home/hzz/redis/redis-6.2.6/src/redis-sentinel /home/hzz/redis/redis-6.2.6/sentinel.conf
ExecStop=/home/hzz/redis/redis-6.2.6/src/redis-cli -p 26379 shutdown
Restart=always
User=root
Group=root
[Install]
WantedBy=multi-user.target
在每台虚拟机(136,137,138)上分别启动 Redis Sentinel:
# 重新加载system文件
sudo systemctl daemon-reload
# 启动哨兵进程
sudo systemctl start redis-sentinel.service
# 查看哨兵进程启动状态
sudo systemctl status redis-sentinel.service
查看所有哨兵进程
redis-cli -p 26379
127.0.0.1:26379> SENTINEL sentinels hzz_redis_master
4.通过哨兵,检查redis主/从节点的状态
在任意一台哨兵虚拟机上,可以通过 Redis 命令行来检查redis的状态:
redis-cli -p 26379
127.0.0.1:26379> SENTINEL master hzz_redis_master
127.0.0.1:26379> SENTINEL slaves hzz_redis_master
查看主节点
进入任意一台哨兵(这里选择136哨兵进程),查看redis主节点信息:
查看从节点
进入任意一台哨兵(这里选择136哨兵进程),查看redis从节点信息:
到这里redis主从和哨兵机制就都已经弄好了。
最终的部署效果就是 :
- 136(redis 主,哨兵1)
- 137(redis 从,哨兵2)
- 138(redis 从,哨兵3)
5.验证故障转移
因为哨兵支持故障切换时的通知机制(钉钉/邮件等),且我们在 3小节的 创建哨兵配置文件 这一小节已经配置并定义了通知脚本,所以这里我来验证下自动切换以及通知效果,首先触发自动故障转移机制的话 肯定是需要停掉主节点,所以这里我们在主节点上(136)上 停止redis进程。命令如下:
sudo systemctl stop redis
- 查看哨兵日志,验证是否发生了故障转移:
journalctl -u redis-sentinel -f
可能下边这个故障切换日志,不太好阅读,但是还是给出来(最重要的一行是 switch-master 也就是选主 可以看到在136的redis主节点停止后,137选为了主节点):
从以上日志可以看出 136停掉后 选择了137为主节点,在137验证下:
同时我们的故障触发了脚本的执行,在钉钉中 也有了告警通知,截图如下:
补充下,与钉钉交互需要在钉钉提前建好webhook机器人,然后将其url连带token写到提醒脚本中去使用curl调用:
同时,在哨兵选出新的redis主节点后(这里演示的是137成了主节点),会 自动更新 哨兵中 的主节点配置,如下:
如果我们再次启动136的话,他会自动加入到redis集群,并且身份是从节点,如下:
6. 使用客户端连接redis哨兵集群
在客户端程序中我们需要配置redis哨兵的ip端口(有几个哨兵就配几个),哨兵会将主节点的信息返回之后进行连接,下面使用python做演示:
注意:(我已做了端口转发, 所以连redis时 ip是127.0.0.1就可以了,另外修改了redis端口(136修改为6379,137修改为6380,138修改为6381,否则都是6379的话本地没法做端口转发))
py代码:
import redis
from redis.sentinel import Sentinel
import time
# 哨兵实例的地址和端口
sentinel_hosts = [
('127.0.0.1', 26379),
('127.0.0.1', 36379),
('127.0.0.1', 46379)
]
# 创建 Sentinel 对象
sentinel = Sentinel(sentinel_hosts, socket_timeout=10)
# 获取主节点信息,假设主节点的别名是 "hzz_redis_master"
master_name = "hzz_redis_master"
# 尝试连接并执行 Redis 操作
while True:
try:
# 获取主节点信息
master = sentinel.discover_master(master_name)
print(f"当前主节点信息: {master}") # 输出主节点 IP 和端口
# 连接到主节点
master_client = redis.Redis(host='127.0.0.1', port=master[1])
# 设置哈希键 'hk1' 的值
hash_key = 'hk1'
# 使用 hset 设置哈希字段及值
master_client.hset(hash_key, mapping={
'name1': '张无忌',
'name2': '蝎子莱莱爱打怪'
})
# 获取并输出哈希的所有字段和值
hash_value = master_client.hgetall(hash_key)
# 将字典中的字节解码为字符串
for name, value in hash_value.items():
print(f"{name.decode('utf-8')}: {value.decode('utf-8')}")
except redis.exceptions.ConnectionError as e:
print(f"连接错误: {e},尝试重新连接...")
time.sleep(2) # 等待2秒后重试
except Exception as e:
print(f"出现错误: {e}")
break # 退出循环
验证结果如下:
可以看到,在发生故障时,客户端可以自动切换到新的主节点,无需手动干预。
7. 总结
最后总结一下redis的优劣:
- 优点:
- 可通过读写分离来降低master节点的压力(因为哨兵本质上也是基于主从复制的只是多了哨兵监控以及自动故障恢复等功能)
- 拥有多个数据副本
- 自动故障转移。当master节点发生故障下线时,哨兵自动地将某个slave节点切换为新的master节点,并且客户端不需要切换连接新的master节点(由于客户端连接哨兵集群,而不直接连接master节点)
- 缺点:
- 单个master内存空间有限,无法存放太多的数据量
- 依然只有master节点可以处理写请求,存在写瓶颈
- 当副本比较多时,主从复制机制将会增加 主redis 的压力
综合来说,redis哨兵机制是足以应对中等规模的项目的,尤其是分布式部署哨兵实例的情况下,更增加了高可用性。