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

《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配置,请见https://github.com/kelgon/redisHA/blob/master/haproxy.cfg

写在最后

  • 关于主从复制的Redis集群如何搭建,Sentinel怎么建,官网上有详细的文档,中文资源也相当丰富,本文就不做赘述了
  • 其他负载均衡软件的配置,可以参考HAProxy,理论上只要能够支持自定义TCP健康检查,就没有问题
  • 注意负载均衡服务也不能是单点,否则Redis的故障转移做得再好也都没有意义。我的另一篇文章《HAProxy从零开始到掌握》的最后一部分中,有如何使用Keepalived搭建自动故障转移的主备负载均衡服务的描述,供参考
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容