不依赖客户端库,实现Redis主从架构的高可用/读写分离/负载均衡

点击上方 "编程技术圈"关注, 星标或置顶一起成长

后台回复“大礼包”有惊喜礼包!

日英文

Don't always in the memories of the past love, the sun yesterday, how sun does not dry clothes today.

不要总在过去的回忆里缠绵,昨天的太阳,怎么都晒不干今天的衣裳。

每日掏心话

柔软的时光,揉碎了执着,荒芜了等待。岁月,来时,脚步很轻,却惊醒了时光。

责编:乐乐 | 来自:kelgon

链接:jianshu.com/p/99be0a88517d

编程技术圈(ID:study_tech)第 1167 次推文

往日回顾:记住看小电影前一定要检查网址是不是 HTTPS 的,不然…

     

   正文   

在《Redis基础、高级特性与性能调优》这篇文章中,我曾比对过Redis主从复制架构和分片集群架构,并提到了避免过度设计架构的观点。我始终认为良好调优过的主从复制架构的Redis集群能够支撑绝大部分体量(10万tps以下)的系统。

要满足这个要求,主从架构的Redis集群应具备几个能力:

  • 自动故障转移

  • 读写分离(主写,从读)

  • 读负载均衡(在多个从节点之间)

Redis Sentinel为我们实现了主从架构下的故障转移能力,但后两项能力Redis并未提供支持,需要自己实现。各种语言的众多Redis客户端中,只有少数重量级的库实现了这两个能力(比如Java的Redisson和Lettuce)。被迫和某些库绑死是一件让人不太舒服的事情(被迫换库就更难受了),本文介绍的方法将读写分离和读负载均衡的能力实现在服务端。

(不想看废话的大佬们可以直接看这里:https://github.com/kelgon/redisHA)


先决条件

HAProxy,或者其他任何能够自定义TCP健康检查策略的负载均衡服务都行(比如Nginx,F5等等),本文将以HAProxy为例。

实现思路

在Redis服务前面搭建L4负载均衡服务,做下面两件事:

  • 把写请求定向到当前的Master节点上

  • 把读请求在当前的Slave节点间负载均衡

这就要求负载均衡服务不仅要知道当前哪些Redis节点是可用的,还要知道这些Redis节点的身份。
通过自定义TCP健康检查策略可以很容易实现:

backend redis-legacy
    mode tcp
    balance first
    option tcp-check
    tcp-check send info\ replication\r\n     #向Redis发送'INFO replication'命令
    tcp-check expect string role:master      #如Redis返回的内容中包含'role:master',即说明此节点为Master,即认为其健康
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis-1 <ip>:<port> maxconn 5000 check inter 2s
    server redis-2 <ip>:<port> maxconn 5000 check inter 2s
    server redis-3 <ip>:<port> maxconn 5000 check inter 2s

也就是说,只要对命令'INFO replication'的返回内容中包含'role:master'的Redis节点,就被认为是有效节点,所有写请求都被定向到这个节点上去。

看上去似乎很OK,但其实这里存在双Master的风险
上面的配置只依靠每个Redis节点本身的反馈来决定其是否为Master,是不够严谨的。错误的配置和巧合的异常网络状态都有可能导致集群中一个以上的Redis节点认为自己是Master。
一旦出现双Master,那么写到错误的Master节点上的数据实际上就等同于丢失了,一旦出现会造成很严重的问题。
要解决这一问题,负载均衡服务就不能仅询问Redis节点本身,还要综合Sentinel们的意见。

下面这段配置,是在询问redis-1本身是否认为自己是Master:

backend redis-master-1
    mode tcp
    balance first
    option tcp-check
    tcp-check send info\ replication\r\n
    tcp-check expect string role:master
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis <ip>:<port> maxconn 5000 check inter 2s


而下面这段配置会询问三个Sentinel节点当前redis-1是否为Master:
backend mastercheck-redis-1
    mode tcp
    option tcp-check
    tcp-check send SENTINEL\ master\ <your-master-name>\r\n  ##向Sentinel服务发送'SENTINEL master <name>'命令
    tcp-check expect string <ip-of-redis-server-1>           ##如Sentinel返回的内容中包含redis-1的地址,那么说明Sentinel们认为redis-1为Master
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server sentinel-1 <ip>:<port> check inter 2s
    server sentinel-2 <ip>:<port> check inter 2s
    server sentinel-3 <ip>:<port> check inter 2s
综合上面两个信息,就能得知当前redis-1节点的准确状态:
frontend redis-write
    mode tcp
    option tcplog
    bind *:16379
    #如果redis-1认为自己是Master,同时至少有两个Sentinel也认为redis-1是Master,那么就将写请求定向至redis-1
    use_backend redis-master-1 if { srv_is_up(redis-master-1/redis) } { nbsrv(mastercheck-redis-1) ge 2 }
    #以此类推,对redis-2和redis-3的策略
    use_backend redis-master-2 if { srv_is_up(redis-master-2/redis) } { nbsrv(mastercheck-redis-2) ge 2 }
    use_backend redis-master-3 if { srv_is_up(redis-master-3/redis) } { nbsrv(mastercheck-redis-3) ge 2 }
    #如果redis-1/2/3都不满足,那么就使用最上面提到的原始办法,即仅询问Redis节点本身
    #实际上此时集群肯定是有问题的,所以下面这句只是挣扎一下,并不是什么保险措施
    default_backend redis-legacy
读写分离和读负载均衡就简单了。如果一个Redis节点认为自己是Slave,那它就肯定是Slave,不可能存在节点认为自身是Slave而Sentinel认为其是Master的情况。
配置如下:
listen redis-read
    bind *:16479
    mode tcp
    balance roundrobin
    option tcp-check
    tcp-check send info\ replication\r\n
    tcp-check expect string role:slave
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis-1 <ip>:<port> maxconn 5000 check inter 2s
    server redis-2 <ip>:<port> maxconn 5000 check inter 2s
    server redis-3 <ip>:<port> maxconn 5000 check inter 2s
不过,使用上述配置,假设在极端情况下某个Slave由于配置错误正在从错误的Master上同步数据,也会出现问题,因为HAProxy只询问了Redis节点“是否为Slave”,而没有询问“是谁的Slave”。
因为这种情况太极端了,所以我也就没有管,不过也并不代表没有办法,INFO replication命令的返回中同样会包含Master的host和port,如果一定要的话,HAProxy利用这些信息也能做出判断。

配置完成后,让客户端访问HAProxy的16379端口执行写操作,访问16479端口执行读操作就OK了。

在公众号后端架构师后台回复“架构整洁”,获取一份惊喜礼包。

完整的HAProxy配置,请见:

global
    daemon
    maxconn 30000


defaults
    mode tcp
    log global
    option tcplog
    option dontlognull
    maxconn 30000
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms


#HAProxy stats page
listen stats
    bind *:8888
    stats enable
    mode http
    #replace the following auth-string with your own
    stats auth admin:admin


#Read-traffic load-balance
listen redis-read
    bind *:16479
    mode tcp
    balance roundrobin
    option tcp-check
    #comment following 2 lines if your redis server doesn't requirepass
    tcp-check send AUTH\ <your-passphrase>\r\n
    tcp-check expect string +OK
    #if you want to balance read-traffic between both slaves and master, comment following 2 lines and decomment the PING/+PONG lines
    tcp-check send info\ replication\r\n
    tcp-check expect string role:slave
    #tcp-check send PING\r\n
    #tcp-check expect string +PONG
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis-1 <ip>:<port> maxconn 5000 check inter 2s
    server redis-2 <ip>:<port> maxconn 5000 check inter 2s
    server redis-3 <ip>:<port> maxconn 5000 check inter 2s


#Write-traffic HA
frontend redis-write
    mode tcp
    option tcplog
    bind *:16379
    #if you have more than 3 sentinels, please change the "ge 2" to a number meets the quorum of sentinels
    #below lines means to forward write traffic to the redis server which is identified as master both by itself and sentinels
    use_backend redis-master-1 if { srv_is_up(redis-master-1/redis) } { nbsrv(mastercheck-redis-1) ge 2 }
    use_backend redis-master-2 if { srv_is_up(redis-master-2/redis) } { nbsrv(mastercheck-redis-2) ge 2 }
    use_backend redis-master-3 if { srv_is_up(redis-master-3/redis) } { nbsrv(mastercheck-redis-3) ge 2 }
    #if cannot find a valid master, fall back to legacy mode, which forward write traffic to the first redis server who identifies itself as master
    default_backend redis-legacy


#Following 3 backends tells if the 3 redis server identify themselves as masters
backend redis-master-1
    mode tcp
    balance first
    option tcp-check
    #comment following 2 lines if your redis server doesn't requirepass
    tcp-check send AUTH\ <your-passphrase>\r\n
    tcp-check expect string +OK
    tcp-check send info\ replication\r\n
    tcp-check expect string role:master
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis <ip>:<port> maxconn 5000 check inter 2s


backend redis-master-2
    mode tcp
    balance first
    option tcp-check
    #comment following 2 lines if your redis server doesn't requirepass
    tcp-check send AUTH\ <your-passphrase>\r\n
    tcp-check expect string +OK
    tcp-check send info\ replication\r\n
    tcp-check expect string role:master
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis <ip>:<port> maxconn 5000 check inter 2s


backend redis-master-3
    mode tcp
    balance first
    option tcp-check
    #comment following 2 lines if your redis server doesn't requirepass
    tcp-check send AUTH\ <your-passphrase>\r\n
    tcp-check expect string +OK
    tcp-check send info\ replication\r\n
    tcp-check expect string role:master
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis <ip>:<port> maxconn 5000 check inter 2s


#Following 3 backends tells if sentinels identify the 3 redis servers as masters
backend mastercheck-redis-1
    mode tcp
    option tcp-check
    tcp-check send SENTINEL\ master\ <your-master-name>\r\n
    tcp-check expect string <ip-of-redis-server-1>
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server sentinel-1 <ip>:<port> check inter 2s
    server sentinel-2 <ip>:<port> check inter 2s
    server sentinel-3 <ip>:<port> check inter 2s


backend mastercheck-redis-2
    mode tcp
    option tcp-check
    tcp-check send SENTINEL\ master\ <your-master-name>\r\n
    tcp-check expect string <ip-of-redis-server-2>
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server sentinel-1 <ip>:<port> check inter 2s
    server sentinel-2 <ip>:<port> check inter 2s
    server sentinel-3 <ip>:<port> check inter 2s


backend mastercheck-redis-3
    mode tcp
    option tcp-check
    tcp-check send SENTINEL\ master\ <your-master-name>\r\n
    tcp-check expect string <ip-of-redis-server-3>
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server sentinel-1 <ip>:<port> check inter 2s
    server sentinel-2 <ip>:<port> check inter 2s
    server sentinel-3 <ip>:<port> check inter 2s


#The legacy HA mode
backend redis-legacy
    mode tcp
    balance first
    option tcp-check
    #comment following 2 lines if your redis server doesn't requirepass
    tcp-check send AUTH\ <your-passphrase>\r\n
    tcp-check expect string +OK
    tcp-check send info\ replication\r\n
    tcp-check expect string role:master
    tcp-check send QUIT\r\n
    tcp-check expect string +OK
    server redis-1 <ip>:<port> maxconn 5000 check inter 2s
    server redis-2 <ip>:<port> maxconn 5000 check inter 2s
    server redis-3 <ip>:<port> maxconn 5000 check inter 2s

写在最后

  • 关于主从复制的Redis集群如何搭建,Sentinel怎么建,官网上有详细的文档,中文资源也相当丰富,本文就不做赘述了

  • 其他负载均衡软件的配置,可以参考HAProxy,理论上只要能够支持自定义TCP健康检查,就没有问题

  • 注意负载均衡服务也不能是单点,否则Redis的故障转移做得再好也都没有意义。我的另一篇文章《HAProxy从零开始到掌握》的最后一部分中,有如何使用Keepalived搭建自动故障转移的主备负载均衡服务的描述,供参考

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

欢迎加入后端架构师交流群,在后台回复“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

图解 Git 工作原理,看了秒懂!

入职腾讯第九年,我辞职了

深圳一名程序员因跳槽违反《竞业协议》,赔偿腾讯 97.6 万元。。

BAT等大厂Java面试经验总结

别找了,想获取 Java大厂面试题学习资料

扫下方二维码回复「手册」就好了



嘿,你在看吗

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值