场景
技术的学习提升是需要把技术在实际项目中使用起来,才会有更深入的探究。
在一次项目中我们部署 Redis 服务,并有三个节点组成集群,且部署了 sentinel 。客户提出希望可以把 Redis 全部修改为域名解析,而不是通过 IP 的方式解析(因为我们默认情况下使用的是 IP 地址)。在网上搜索了一下并没有找到满意的答案,大家并没有在讨论这类可能比较低级的问题,如何配置是用域名吧。
环境背景
Redis 在实际使用中是集群化的,我们普遍使用三个节点做集群,起三个 redis 和 redis-sentinel ,所以如果要修改成域名的方式,需要分别修改两个服务。
我们假设如下图为业务场景,三台机器分别启动 redis 和 redis-sentinel 服务,HostName-1 为域名。
下面配置为 redis.conf 中的现存配置,省略其他
......
aof-rewrite-incremental-fsync yes
requirepass "password"
masterauth "password"
slaveof 192.168.1.1 6379
下面配置为 redis-sentinel.conf 的现存配置
logfile "/var/log/redis/sentinel.log"
sentinel monitor redisha 192.168.1.1 6379 2
# redisha 为自定义名称,IP 为主节点 redis IP,端口为主节点 redis 端口,2 表示集群内两个sentinel 认为节点故障则生效
sentinel down-after-milliseconds redisha 30000
# 若主节点没有相应哨兵的 PING 命令,则认为主下线
sentinel failover-timeout redisha 180000
# 设定故障转移允许的毫秒数
sentinel parallel-syncs redisha 1
# 允许有多少个 Redis 服务同步新的主机
sentinel auth-pass redisha password
# 配置主节点密码,在 redis.conf 中设置
port 16379
# 设置 sentinel 占用端口
测试过程
域名修改
从配置来看,理论上把 redis.conf 和 redis-sentinel.conf 的 IP 改成域名应该就可以,所以很快就开始第一轮修改测试。
我把两个文件中的 192.168.1.1 分布修改成对应主机的Hostname:HostName-1,并重启服务。
redis.conf 修改完成后,服务正常启动。但是 sentinel 修改之后出现了一些奇怪的现象:
- sentinel 配置文件中生成了如下内容
sentinel known-slave redisha 192.168.1.2 6379
sentinel known-slave redisha 192.168.1.3 6379
sentinel known-sentinel redisha 192.168.1.1 16379 f16a463d7387bf71f5ebce0c969d01d5bd802ac4
sentinel known-sentinel redisha 192.168.1.3 16379 2a716b1f6e6e9ab6688a99160e7a6616b913336b
sentinel current-epoch 0
- 每当在启用第二个 sentinel 节点时,这段配置 “sentinel monitor redisha HostName-1 6379 2” 中的域名都会被修改成 IP 地址。
查问题
我在网上搜索 sentinel known-slave,大多数都是关于 sentinel 配置介绍,然后在启用服务后生成的文件中有这段配置但并没有解释这段信息的意思。于是我还是求助官方吧,下载 redis 源码在本地找。 大家也可以下载看看,github.com/redis/redis
known-slave 介绍
/* sentinel known-slave */
di2 = dictGetIterator(master->slaves);
while((de = dictNext(di2)) != NULL) {
sentinelAddr *slave_addr;
ri = dictGetVal(de);
slave_addr = ri->addr;
/* If master_addr (obtained using sentinelGetCurrentMasterAddress()
* so it may be the address of the promoted slave) is equal to this
* slave's address, a failover is in progress and the slave was
* already successfully promoted. So as the address of this slave
* we use the old master address instead. */
if (sentinelAddrIsEqual(slave_addr,master_addr))
slave_addr = master->addr;
line = sdscatprintf(sdsempty(),
"sentinel known-replica %s %s %d",
master->name, announceSentinelAddr(slave_addr), slave_addr->port);
rewriteConfigRewriteLine(state,"sentinel known-replica",line,1);
/* rewriteConfigMarkAsProcessed is handled after the loop */
}
上面是从redis-6.2.5 版本中通过全局代码搜索找到的一部分截取,从注释部分我的理解是:如果发生了故障切换,备节点地址已经变成主节点地址了,则会把这个备节点地址替换成原先的主节点地址,而当前集群内主节点地址可以通过 sentinelGetCurrentMasterAddress() 方法获取。
从下面这段代码可以判断 known-slave 和 known-replica 应该是等价的,或许是兼容历史版本。
else if ((!strcasecmp(argv[0],"known-slave") ||
!strcasecmp(argv[0],"known-replica")) && argc == 4)
所以,对于 sentinel known-slave,我们可以认为这是一个 redis 自行计算的过程,通过这个方法dictGetVal(de) 确定 slave ip ,通过 sentinelGetCurrentMasterAddress() 获取主节点 IP 地址,以及其他想要的信息组合。
known-sentinel 介绍
/* sentinel known-sentinel */
di2 = dictGetIterator(master->sentinels);
while((de = dictNext(di2)) != NULL) {
ri = dictGetVal(de);
if (ri->runid == NULL) continue;
line = sdscatprintf(sdsempty(),
"sentinel known-sentinel %s %s %d %s",
master->name, announceSentinelAddr(ri->addr), ri->addr->port, ri->runid);
rewriteConfigRewriteLine(state,"sentinel known-sentinel",line,1);
/* rewriteConfigMarkAsProcessed is handled after the loop */
}
可以看到 known-sentinel 后面的变量分别为 master、 宣告的 sentinel地址,sentinel 端口,以及 runid(可能是一段hash) 。
从代码信息来看,也是一段 sentinel 的计算。所以自动生成的部分可以不考虑。
announceSentinelAddr 介绍
但是在上面代码中均出现 announceSentinelAddr,而这个信息的位置是输出当前节点的 IP 地址,我就从代码中继续找到了这段代码,这是一个内置变量,其中继续引用了 announce_hostnames,并且有两个输出结果一个是IP一个是域名。
const char *announceSentinelAddr(const sentinelAddr *a) {
return sentinel.announce_hostnames ? a->hostname : a->ip;
}
继续找到如下信息,巧了好像发现了 sentinel 的隐藏配置。这段代码的意思是通过 sentinel announce-hostnames 的配置可以通过配置参数 “sentinel announce-hostnames yes/no” 到底是是用 IP 还是域名。貌似把问题2 域名总是被修改成 IP 的原因也找到了。
/* sentinel announce-hostnames. */
line = sdscatprintf(sdsempty(), "sentinel announce-hostnames %s",
sentinel.announce_hostnames ? "yes" : "no");
rewriteConfigRewriteLine(state,"sentinel announce-hostnames",line,
sentinel.announce_hostnames != SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES);
#define SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES 0
因为这段解释不多,我们只知道可以通过 yes no 来配置,默认值为0,那应该用yes 就行,但总想再找一找看看还有没有其他说明。就继续搜素,在 sentinel.conf 文件中找到如下部分:
# Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR
# to specify an IP address. Also, it requires the Redis replica-announce-ip
# keyword to specify only IP addresses.
# You may enable hostnames support by enabling resolve-hostnames. Note
# that you must make sure your DNS is configured properly and that DNS
# resolution does not introduce very long delays.
#
SENTINEL resolve-hostnames no
# When resolve-hostnames is enabled, Sentinel still uses IP addresses
# when exposing instances to users, configuration files, etc. If you want
# to retain the hostnames when announced, enable announce-hostnames below.
#
SENTINEL announce-hostnames no
从我自己的理解:
正常情况下 sentinel 只使用 IP 地址,并且要求SENTINEL MONITOR 也使用 IP 地址(问题2 实锤了)。 我们可以通过打开 “resolve-hostnames” 开启域名解析,但是前提是 DNS 服务是可用的。 当打开了 resolve-hostnames,sentinel 依然是用 IP 地址,如果你想保留域名,则需要开启 announce-hostnames。
结尾
按照上述查到的方法,我修改了配置
logfile "/var/log/redis/sentinel.log"
sentinel resolve-hostnames yes
sentinel announce-hostnames yes
sentinel monitor redisha 192.168.1.1 6379 2
sentinel down-after-milliseconds redisha 30000
sentinel failover-timeout redisha 180000
sentinel parallel-syncs redisha 1
sentinel auth-pass redisha password
port 16379
此时启动 sentinel 服务,域名不再被修改成 IP 地址。
测试 sentinel 状态,查看 sentinel.log ,发现已经被修改成对应的域名解析。