一 前言
这几天在研究如何做 Redis 的高可用容灾方案, 查询了资料和咨询 DBA 同行, 了解到 Redis 可以基于 consul 和 sentinel 实现读写分离以及 HA 高可用方案. 本文讲述基于 consul 的 Redis 高可用方案实践.
感谢邓亚运的提示和资料协助.
二 consul 是什么?
Consul 是 HashiCorp 公司基于 go 语言研发用于服务发现和配置共享开的分布式高可用的系统. 提供内服务注册与发现框架, 分布一致性协议实现, 健康检查, Key/Value 存储, 多数据中心方案, 以及 web UI 支持, 相比于 Zookeeper,Consul 不再需要依赖其他工具.
Consul 提供的如下关键特性:
Consul 采用 Raft 一致性协议算法来保证服务的高可用, 使用 GOSSIP 协议管理成员和广播消息, 并且支持 ACL 访问控制.
服务注册与发现: Consul 同时支持 DNS 或者 HTTP 两种接口进行服务注册和服务发现.
支持健康检查: Consul 可以通过定期运行脚本进行健康检查, 根据脚本的返回值判断机器或服务是否健康, 返回值为 0 时才代表健康. 用户可以自定义的任意 shell/Python 脚本进行服务 (Redis/MySQL 等) 的健康检查, 这点相比其他同类工具更灵活. 值得注意的是 consul 中 返回值 0 是没问题(passed),1 是警告(warning), 其他值是检查失败(critical)
Key/Value 存储: 一个用来存储动态配置的系统. 提供简单的 HTTP 接口, 可以在任何地方操作.
支持多数据中心方案: 支持多机房配置, 可以避免单机房单点故障, 多个数据中心要求每个数据中心都要安装一组 Consul cluster, 多个数据中心间基于 gossip protocol 协议来通讯, 使用 Raft 算法实现一致性.
简易安装部署: 安装包仅包含一个二进制文件, 支持跨平台 (*Nix,WIN) 运行. 可与 Docker 等轻量级容器实现无缝配合.
官方提供 web 管理界面, etcd 无此功能.
三 Consul 架构组成
Consul 是分布式的高可用系统, 该系统中有两种角色 Server 和 Client. 通过启动 agent 时指定 Server 或者 client 模式实现 Consul 集群中角色划分.
Consul Server: 服务端用于保存 Consul Cluster 的状态信息, 实现数据一致性, 响应 RPC 请求. 在局域网内与本地客户端通讯, 通过广域网与其他数据中心通讯. 每个 Consul Cluster 的 Server 数量推荐为 3 或 5 个.
Consul Client: 客户端, 只维护自身的状态, 并将 HTTP 和 DNS 接口请求转发给服务端.
注意:
Server 和 Client 的角色和 Consul Cluster 上运行的应用服务无关, 是基于 Consul 层面的一种角色划分.
四 Consul 和同类工具的对比表
五 如何使用
环境准备
- server: 10.9.51.13
- client: 10.215.20.13 10.215.20.7 10.9.141.52
下载和安装
wget "https://releases.hashicorp.com/consul/1.0.7/consul_1.0.7_linux_amd64.zip"
如果前面的特性介绍所说, consul 其实就是一个二进制文件, 下载以后解压放到 / usr/local/bin 之后就可以使用, 不依赖任何东西.
cp consul /usr/local/bin/
初始化目录
mkdir /etc/consul.d/ -p && mkdir /data/consul/shell -p vim /etc/consul.d/client.json
配置文件 在 server 节点 10.9.51.13 编辑配置文件 /etc/consul.d/server.json
- {
- "data_dir": "/data/consul",
- "datacenter": "dc1",
- "log_level": "INFO",
- "server": true,
- "bootstrap_expect": 1,
- "bind_addr": "10.9.51.13",
- "client_addr": "10.9.51.13",
- "ui":true
- }
在三个 client 节点配置文件
- /etc/consul.d/client.json
- {
- "data_dir": "/data/consul",
- "enable_script_checks": true,
- "bind_addr": "10.215.20.13",
- "retry_join": ["10.9.51.13"],
- "retry_interval": "15s",
- "rejoin_after_leave": true,
- "start_join": ["10.9.51.13"]
- }
其中 bind_addr 是 client 节点的 ip 地址, retry_join 和 start_join 是 server 节点的 ip 地址.
启动 server 和 client 节点
先启动 server 节点
nohup consul agent -config-dir=/etc/consul.d> /data/consul/consul.log &
再分别启动三个 client 节点
nohup consul agent -config-dir=/etc/consul.d> /data/consul/consul.log &
2018/05/04 12:02:23 [INFO] agent: Join LAN completed. Synced with 1 initial agents 说明 client 节点已经加入到了 consul 集群.
查看各个节点的状态
consul members
consul operator raft list-peers
Node ID Address State Voter RaftProtocol
qabb-rdsa0 93b8e2a9-1ae1-3726-2993-c4f068ffd9b4 10.9.51.13:8300 leader true 3
通过 server 节点 10.9.51.13:8500/ui 查看 web ui
现在 qabb-r1db13 qabb-r1db7 上还没部署服务, 所以显示为 0 个服务. 接下来我们开始部署 redis 服务, 构建基于 Consul 的 Redis 的高可用架构
六 基于 Consul 的 Redis 高可用架构
Redis 现有高可用架构 sentinel 遇到的问题
Redis 哨兵架构下, 服务器节点部署了哨兵, 但业务部门没有在应用程序层面使用 jedis 哨兵驱动来自动发现 Redis master, 而使用直连 IP master. 当 master 挂掉, 其他 redis 节点担当新 master 后, 应用需要手工修改配置指向新 master.
Redis 客户端驱动还没有读写分离的配置, 若想 slave 的读负载均衡, 暂时没好的办法.
Consul 可以满足以上需求, 配置两个 DNS 服务, 一个是 master 的写服务, 利用 consul 自身的服务健康检查和探测功能, 自动发现新的 master. 然后定义一个 slave 的读服务, 基于 DNS 本身, 能够对 slave 角色的 redis IP 做轮询, 实现负载均衡的效果.
环境搭建
其实首先我们要在搭建一主两从的 redis 集群, 因为本文是模拟练习, 所以在 client 节点上部署. 具体搭建过程省略, Redis 的主从配置如下
- master 10.215.20.13:6380
- slave 10.215.20.7:6380
- 10.9.141.52:6380
定义服务配置文件
我们需要在每个 client 节点的 / etc/consul.d / 下面服务配置文件如下:
- $ ll
- total 12
- -rw-r--r-- 1 root root 217 May 4 18:54 client.json
- -rw-r--r-- 1 root root 317 May 4 18:55 r-6380-redis-test.json
- -rw-r--r-- 1 root root 320 May 4 18:54 w-6380-redis-test.json
redis 主节点的服务定义配置文件 W-6380-redis-test.json
- {
- "services": [
- {
- "name": "w-6380-redis-test",
- "tags": [
- "master-test-6380"
- ],
- "address": "10.215.20.13",
- "port": 6380,
- "checks": [
- {
- "args":["sh","-c","/data/consul/shell/check_redis_master.sh 6380"],
- "interval": "15s"
- }
- ]
- }
- ]
- }
redis 从节点的服务定义配置文件 r-6380-redis-test.json
- {
- "services": [
- {
- "name": "r-6380-redis-test",
- "tags": [
- "slave-test-6380"
- ],
- "address": "10.215.20.7",
- "port": 6380,
- "checks": [
- {
- "args": ["sh","-c","/data/consul/shell/check_redis_slave.sh 6380"],
- "interval": "10s"
- }
- ]
- }
- ]
- }
关于服务的定义文件这里做简单说明:
Consul 默认会根据 names 的值定义 DNS 域名, 默认以 .service.consul 结尾. 不过我们可以利用 domain 参数修改.
根据上面的 Redis 主从服务定义配置文件, Consul client 节点向 server 集群注册后, 对应有两个域名:
w-6380-redis-test.service.consul 对应 Redis master 服务器 ip.
r-6380-redis-test.service.consul 对应两个 slave 服务器 ip, 客户端发送请求时 DNS 轮训随机分配一个 slave ip.
其中 "args": ["sh","-c","/data/consul/shell/check redis slave.sh 6380"]代表对 redis 6380 端口进行健康检查, 如果异常返回 2,consul client 会通知 server 端对异常服务进行服务治理.
动态加载配置
此时我们检查 web-ui,
检查 dns 配置
dig @10.9.51.13 -p 8600 w-6380-redis-test.service.consul
显示 10.215.20.13 对应于 w-6380-redis-test.service.consul
dig @10.9.51.13 -p 8600 r-6380-redis-test.service.consul
显示 2 个 slave ip 10.215.20.7 ,10.9.141.52 对应 r-6380-redis-test.service.consul.
注意因为是测试, dns 配置并未在我们公司的 dns 域名服务器器做解析, 所以日志中提示
;; WARNING: recursion requested but not available
切换演练
我们需要做 2 种容灾演练
读写分离, 如一个 slave (10.215.20.13:6380)挂了之后, 观察系统的表现
继续关闭其他 redis slave (10.9.141.52:6380)实例
可以看到结果符合预期: 当多个从库依次关闭, dns 域名后端 ip 发生变化, 会逐步剔除关闭的实例对应的 ip, 所有的 slave 关闭之后, 域名 r-6380-redis-test.service.consul 后端的 ip 指定到 redis master 对应的 ip 上.
配合 sentinel, 实现高可用, master 挂了之后, 观察系统的表现
在这里我选择 consul 集群中的 server 节点部署了 sentinel 监控,(生产中要 3 或者 5 个节点, 测试过程只是部署一个). 模拟 redis 主库 10.215.20.13:6380 crash.
127.0.0.1:6380> shutdown
Sentinel 监控日志显示选举从库 10.215.20.7:6380 为主库.
此时我们再检查 dns 域名 主库对应的 dns 域名 w-6380-redis-test.service.consul. 对应 ip 已经变为 10.215.20.7
r-6380-redis-test.service.consul. 对应的 ip 减少了一个, 变为 10.9.141.52.
从目前的结果来看 切换符合预期.
另外说一下不符合预期的地方, 在老的 master 重新起来之后, sentinel 会将老的 master 设置为新的 slave, 但是 Consul 对应的 dns 并未及时更新准确, 需要执行 consul reload 才可以.
生产过程中要注意这点, 切换之后及时更新 DNS 缓存.
总结
从环境搭建以及测试结果来看, Consul 这款工具使用起来十分简便, 配合 Redis sentinel 切换速度符合预期, 上线生产环境时需要注意 dns 缓存时间的配置, 以及 DNS 域名管理方面的支持. 另外更多的技术知识点还是要多阅读 Consul 官方文档.
来源: http://www.tuicool.com/articles/M3iUR3E