与亲生的Redis Cluster,来一次亲密接触

更多精彩文章。node

《微服务不是所有,只是特定领域的子集》nginx

《“分库分表" ?选型和流程要慎重,不然会失控》程序员

这么多监控组件,总有一款适合你redis

《使用Netty,咱们到底在开发些什么?》算法

《这多是最中肯的Redis规范了》数据库

《程序员画像,十年沉浮》vim

最有用系列:后端

《Linux生产环境上,最经常使用的一套“vim“技巧》数组

《Linux生产环境上,最经常使用的一套“Sed“技巧》缓存

《Linux生产环境上,最经常使用的一套“AWK“技巧》

若是你认同这些知识,欢迎关注微信公众号小姐姐味道

ID:xjjdog

笔者曾经维护过上千个redis实例,这些实例采用的简单主从结构,集群方案主要是客户端jar包。刚开始,我的并非太喜欢redis cluster,由于它的路由实在是太死板,运维复杂。为何这么说,能够参考以前的一篇文章《现实中的路由规则,可能比你想象中复杂的多》

但官方在推这个东西,注定了它的应用愈来愈普遍,这在日常的交流中就可以发现。虽然有这样那样的缺点,但总抵挡不了权威推进的浪潮。随着redis cluster愈来愈稳定,是时候和redis cluster来一次灵魂交流了。

简介

redis cluster是亲生的集群方案,目前,在高可用和稳定性方面,都有了很大的进步。据统计和观察,采用redis cluster架构的公司和社区愈来愈多,已经成为事实的标准。它的主要特色就是去中心化,无需proxy代理。其中一个主要设计目标就是达到线性可扩展性(linear scalability)。

仅仅靠redis cluster服务器自己,并不能完成官方承诺的功能。广义上的redis cluster应该既包含redis服务器,又包含客户端实现好比jedis等。它们是一个总体。

分布式存储无非就是处理分片和副本。 对redis cluster来讲,核心概念就是槽(slot),了解了它,基本就了解了集群的管理方式。

优缺点

当了解这些特性之后,运维上实际上是更简单了。咱们先看下比较明显的优缺点。

优势

一、再也不须要额外的Sentinel集群,为使用者提供了一致的方案,减小了学习成本。
二、去中心架构,节点对等,集群可支持上千个节点。
三、抽象出了slot概念,针对slot进行运维操做。
四、副本功能可以实现自动故障转移,大部分状况下无需人工介入。

缺点

一、客户端要缓存部分数据,实现Cluster协议,相对复杂。
二、数据是经过异步复制的,不能保证数据的强一致性。
三、资源隔离困难,常常流量不均衡,尤为是多个业务共用集群的时候。数据不知道在哪里,针对热点数据,也没法经过专项优化完成。 四、从库是彻底的冷备,没法分担读操做,真是太太浪费了。须要作额外工做。 五、MultiOp和Pipeline支持有限,老代码要是进行架构升级,要当心了。 六、数据迁移是基于key而不是基于slot的,过程较慢。

基本原理

从槽到key,定位过程明显就是一个双层的路由。

key的路由

redis cluster和经常使用的一致性hash没什么关系,它主要采用了哈希槽的概念。当须要在其中存取一个key时,redis客户端会首先对这个key采用crc16算法算出一个值,而后对这个值进行mod操做。

crc16(key)mod 16384
复制代码

因此,每一个key都会落在其中的一个hash槽上。16384等同于2^14(16k),redis节点发送心跳包时,须要把全部的槽信息放在这个心跳包里,因此要不遗余力的优化,感兴趣的能够看下为何默认的槽数量是16384。

服务端简单原理

上面谈到,redis cluster共定义了16384个槽,全部的集群操做都是围绕着这个槽数据进行编码。服务端使用一个简单的数组存储这些信息。

对于判断有无的操做,使用bitmap来存储是最节省空间的。redis cluster就是使用一个叫作slot的数组来保存当前节点是否持有了这个槽。

如图,这个数组的长度为 16384/8=2048 Byte,那么就可使用0或者1来标识本节点对某个槽是否拥有。

其实,只须要第一份数据ClusterState也能完成操做,保存另一个维度的Slot数组,可以方便编码和存储。一个节点除了会将本身负责处理的槽记录在两个地方(clusterNode结构的slots和numslots),它还会将本身的slots数组经过消息发送给集群中的其余节点,以此来告诉其余节点本身目前拥有的槽。

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,若是数据库中有任何一个槽没有获得处理,那么集群处于下线状态(fail)。

当客户端向节点发送相关命令时,接收命令的节点会计算出命令要处理的key属于哪一个槽,并检查这个槽是否指派给了本身。若是不是本身的,会指引客户端到正确的节点。

因此,客户端链接集群中的任意一台机器,都可以完成操做。

安装一个6节点集群

准备工做

假如咱们要组装一个3分片的集群,每一个分片有一个副本。那么总共须要的node实例就有3*2=6个。redis能够经过指定配置文件的方式启动,咱们所作的工做就是修改配置文件。

复制6份默认的配置文件。

for i in {0..5}  
do  
cp redis.conf  redis-700$i.conf
done  
复制代码

修改其中的配置文件内容,拿redis-7000.conf来讲,咱们要启用它的cluster模式。

cluster-enabled yes
port 7000
cluster-config-file nodes-7000.conf
复制代码

nodes-7000.conf会保存一些集群信息到当前节点,因此须要独立。

启动&关闭

一样的,咱们使用脚原本启动它。

for i in {0..5}
do
nohup ./redis-server redis-700$i.conf &
done
复制代码

为了演示,咱们暴力把它关闭。

ps -ef| grep redis | awk '{print $2}' | xargs kill -9
复制代码

组合集群

咱们使用redis-cli进行集群的组合。redis将自动完成这个过程。这一系列的过程,是经过发送指令给每一个节点进行组合的。

./redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
复制代码

几个高级原理

节点故障

集群中的每一个节点都会按期地向集群中的其余节点发送ping消息,以此来检测对方是否在线,若是接收ping消息的节点没有在规定的时间内返回pong消息,那么发送ping消息的节点就会将接收ping消息的节点标记为疑似下线(PFAIL)。

若是在一个集群里面,半数以上节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线(FAIL),将x标记为FAIL的节点会向集群广播一条关于x的FAIL消息,全部收到这条FAIL消息的节点都会当即将x标记为FAIL。

你们能够注意到这个过程,与es和zk的节点判断相似,都是半数以上才进行判断,因此主节点的数量通常都是奇数。因为没有最小组群配置,理论上会有脑裂(暂时并未遇到过)。

主从切换

当一个节点发现本身的主节点进入fail状态,将会从这个节点的从节点当中,选出一台,执行slaveof no one命令,变身为主节点。

新的节点完成本身的槽指派之后,会向集群广播一条pong消息,以便让其余节点当即知道本身的这些变化。它告诉别人:我已是主节点了,我已经接管了有问题的节点,成为了它的替身。

redis内部对集群的这些管理,大量使用了已经定义好的这些指令。因此这些指令不只仅供咱们从命令行使用,redis本身内部也用。

数据同步

当一台从机链接到master以后,会发送一个sync指令。master在收到这个指令后,会在后台启动存盘进程。执行完毕后,master将整个数据库文件传输到slave,这样就完成了第一次全量同步。

接下来,master会把本身收到的变动指令,依次传送给slave,从而达到数据的最终同步。从redis 2.8开始,就支持主从复制的断点续传,若是主从复制过程当中,网络链接断掉了,那么能够接着上次复制的地方,继续复制下去,而不是从头开始复制一份。

数据丢失

redis cluster中节点之间使用异步复制,并无相似kafka这种ack的概念。节点之间经过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提高,完成这个过程注定了须要时间。在发生故障的过程当中就容易存在窗口,致使丢失写入的数据。好比如下两种状况。

1、命令已经到到master,此时数据并无同步到slave,master会对客户端回复ok。若是这个时候主节点宕机,那么这条数据将会丢失。redis这样作会避免不少问题,但对一个对数据可靠性要求较高的系统,是不可忍受的。

2、因为路由表是在客户端存放的,存在一个时效问题。若是分区致使一个节点不可达,提高了某个从节点,但原来的主节点在这个时候又能够用了(并未完成failover)。这个时候一旦客户端的路由表并无更新,那么它将会把数据写到错误的节点,形成数据丢失。

因此redis cluster在一般状况下运行的很好,在极端状况下某些值丢失问题,目前无解。

复杂的运维

redis cluster的运维很是的繁杂,虽然已经进行了抽象,但这个过程依然不简单。有些指令,必须在详细了解它的实现原理以后,才能真正放心的去用。

上图就是扩容会用到的一些命令。在实际使用的过程当中,可能须要屡次频繁地输入这些命令,且输入的过程当中还要监视它的状态,因此基本上是不可能人工跑这些命令的。

运维的入口有两个。 一个是使用redis-cli链接任意一台,而后发送cluster打头的命令,这部分命令大多数是对槽进行操做。 在开始组合集群时,就是反复调用这些命令进行的具体逻辑执行。

另一个入口是使用redis-cli命令,加上--cluster参数和指令。这种形式主要是用来管控集群节点信息 ,好比增删节点等。因此推荐使用这种方式。

redis cluster提供了很是复杂的命令,难于操做和记忆。推荐使用相似CacheCloud的工具进行管理。

下面是几个例子。


经过向节点 A 发送 CLUSTER MEET 命令,客户端可让接收命令的节点 A 将另外一个节点 B 添加到节点 A 当前所在的集群里面:

CLUSTER MEET  127.0.0.1 7006
复制代码

经过cluster addslots命令,能够将一个或者多个槽指派给某个节点负责。

127.0.0.1:7000> CLUSTER ADDSLOTS 0 1 2 3 4 . . . 5000
复制代码

设置从节点。

CLUSTER REPLICATE <node_id>
复制代码

redis-cli --cluster

redis-trib.rb是官方提供的Redis Cluster的管理工具,但最新版本已经推荐使用redis-cli进行操做。

向集群中添加新节点

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7007 --cluster-replicas 1
复制代码

从集群中删除节点

redis-cli --cluster del-node 127.0.0.1:7006 54abb85ea9874af495057b6f95e0af5776b35a52
复制代码

迁移槽到新的节点

redis-cli --cluster reshard 127.0.0.1:7006 --cluster-from 54abb85ea9874af495057b6f95e0af5776b35a52 --cluster-to 895e1d1f589dfdac34f8bdf149360fe9ca8a24eb  --cluster-slots 108
复制代码

相似的命令还有不少。

create:建立集群
check:检查集群
info:查看集群信息
fix:修复集群
reshard:在线迁移slot
rebalance:平衡集群节点slot数量
add-node:添加新节点
del-node:删除节点
set-timeout:设置节点的超时时间
call:在集群全部节点上执行命令
import:将外部redis数据导入集群

其余方案概览

主从模式

redis最先支持的,就是M-S模式,也就是一主多从。redis单机qps可达到10w+,可是在某些高访问量场景下,依然不太够用。通常经过读写分离来增长slave,减小主机的压力。

既然是主从架构,就面临着同步问题,redis主从模式的同步分为全同步和部分同步。当刚建立一个从机的时候,不可避免的要进行一次全量同步。等全量同步结束以后,进入增量同步阶段。这个和redis cluster是没什么区别的。

这种模式仍是比较稳定的,但要额外作一些工做。用户须要自行开发主从切换的功能,也就是使用哨兵去探测每一个实例的健康情况,而后经过指令进行集群状态的改变。

当集群规模增大,主从模式会很快遇到瓶颈。因此通常会采用客户端hash的方法进行扩展,包括相似于memcached的一致性哈希。

客户端hash的路由可能会很复杂,一般会经过发布jar包或者配置的方式维护这些meta信息,这也给线上环境增长了不少不肯定性。

不过,经过加入相似ZK主动通知的功能,将配置维护在云端,能够显著下降风险。笔者曾经维护过的几千个redis节点,就是用这种方式进行管理的。

代理模式

代码模式在redis cluster出现以前,很是流行,好比codis。代理层经过把本身模拟成一个redis,接收来自客户端的请求,而后按照自定义的路由逻辑进行数据分片以及迁移,而业务方不须要改动任何代码。除了可以平滑的进行扩容,一些主从切换、FailOver的功能也在代理层完成,客户端甚至能够没有任何感知。这类程序又称为分布式中间件。

一个典型的实现以下图,背后的redis集群甚至能够是混合的。

但这种方式的缺点也是显而易见的。首先,它引入了一个新的代理层,在结构上、运维上都显复杂。须要进行大量的编码,好比failover、读写分离、数据迁移等。另外,proxy层的加入,对性能也有相应的损耗。

多个proxy通常使用lvs等前置进行负载均衡的设计,若是proxy层的机器不多然后端redis的流量很高,那么网卡会成为主要的瓶颈。

Nginx也能够做为redis的代理层,比较专业的说法叫作Smart Proxy。这种方式较为偏门,若是你对nginx比较熟悉,不失为一种优雅的作法。

使用限制和坑

redis的速度特别的快。通常越快的东西,出问题的时候形成的后果越大。 不久以前,写过一篇针对于redis的规范:《这多是最中肯的Redis规范了》。规范和架构同样,适合本身公司环境的,才是最好的,但会提供一些起码的思路。

严格禁止的东西,通常都是前人踩坑的地方。除了这篇规范的内容,对于redis-cluster,补充如下几点。

一、redis cluster号称可以支持1k个节点,但你最好不要这么作。当节点数量增长到10,就可以感觉到集群的一些抖动。这么大的集群证实你的业务已经很牛x了,考虑一下客户端分片吧。

二、必定要避免产生热点,若是流量所有打到了某个节点,后果通常很严重。

三、大key不要放redis,它会产生大量的慢查询,影响正常的查询。

四、若是你不是做为存储,缓存必定要设置过时时间。占着茅坑不拉屎的感受是很是讨厌的。

五、大流量,不要开aof,开rdb便可。

六、redis cluster的操做,少用pipeline,少用multi-key,它们会产生大量不可预料的结果。

以上是一些补充,更全仍是参考规范吧 《这多是最中肯的Redis规范了》。。

End

redis的代码才那么一丁点,确定不会实现很是复杂的分布式供能。redis的定位就是性能、水平伸缩和可用性,对于简单的、通常流量的应用,已经足够了。生产环境无小事,对于复杂的高并发应用,注定了是一个组合的优化方案。

相关文章
相关标签/搜索