前面咱们介绍了国人本身开发的Redis集群方案——Codis,Codis友好的管理界面以及强大的自动平衡槽位的功能深受广大开发者的喜好。今天咱们一块儿来聊一聊Redis做者本身提供的集群方案——Cluster。但愿读完这篇文章,你可以充分了解Codis和Cluster各自的优缺点,面对不一样的应用场景能够从容的作出选择。html
Redis Cluster是去中心化的,这点与Codis有着本质的不一样,Redis Cluster划分了16384个slots,每一个节点负责其中的一部分数据。slot的信息存储在每一个节点中,节点会将slot信息持久化到配置文件中,所以须要保证配置文件是可写的。当客户端链接时,会得到一份slot的信息。这样当客户端须要访问某个key时,就能够直接根据缓存在本地的slot信息来定位节点。这样就会存在客户端缓存的slot信息和服务器的slot信息不一致的问题,这个问题具体怎么解决呢?这里先卖个关子,后面会作解释。node
首先咱们来看下官方对Redis Cluster的介绍。es6
是否是不(kan)想(bu)看(dong)?不要紧,我来给你掰开了揉碎了解释一下。redis
Redis Cluster使用异步的主从同步方式,只能保证最终一致性。因此会引发一些写入数据丢失的问题,在继续阅读以前,能够先本身思考一下在什么状况下写入的数据会丢失。算法
先来看一种比较常见的写丢失的状况:windows
client向一个master发送一个写请求,master写成功并通知client。在同步到slave以前,这个master挂了,它的slave代替它成为了新的master。这时前面写入的数据就丢失了。缓存
此外,还有一种状况。安全
master节点与大多数节点没法通讯,一段时间后,这个master被认为已经下线,而且被它的slave顶替,又过了一段时间,原来的master节点重写恢复了链接。这时若是一个client存有过时的路由表,它就会把写请求发送的这个旧的master节点(已经变成slave了)上,从而致使写数据丢失。bash
不过,这种状况通常不会发生,由于当一个master失去链接足够长时间而被认为已经下线时,就会开始拒绝写请求。当它恢复以后,仍然会有一小段时间是拒绝写请求的,这段时间是为了让其余节点更新本身的路由表中的配置信息。服务器
为了尽量保证写安全性,Redis Cluster在发生分区时,会尽可能使客户端链接到多数节点的那一部分,由于若是链接到少数部分,当master被替换时,会由于多数master不可达而拒绝全部的写请求,这样损失的数据要增大不少。
Redis Cluster维护了一个NODE_TIMEOUT变量,若是上述状况中,master在NODE_TIMEOUT时间内恢复链接,就不会有数据丢失。
若是集群的大部分master可达,而且每一个不可达的master至少有一个slave,在NODE_TIMEOUT时间后,就会开始进行故障转移(通常1到2秒),故障转移完成后的集群仍然可用。
若是集群中得N个master节点都有1个slave,当有一个节点挂掉时,集群必定是可用的,若是有2个节点挂掉,那么就会有1/(N*2-1)的几率致使集群不可用。
Redis Cluster为了提升可用性,新增了一个新的feature,叫作replicas migration(副本迁移,ps:我本身翻译的),这个feature其实就是在每次故障以后,从新布局集群的slave,给没有slave的master配备上slave,以此来更好的应对下次故障。
Redis Cluster不提供代理,而是让client直接重定向到正确的节点。
client中会保存一份集群状态的副本,通常状况下就会直接链接到正确的节点。
因为Redis Cluster是异步备份的,因此节点不须要等待其余节点确认写成功就能够直接返回,除非显式的使用了WAIT命令。
对于操做多个key的命令,所操做的key必须是在同一节点上的,由于数据是不会移动的。(除非是resharding)
Redis Cluster设计的主要目标是提升性能和扩展性,只提供弱的数据安全性和可用性(可是要合理)。
Redis Cluster共划分为16384个槽位。这也意味着一个集群最多能够有16384个master,不过官方建议master的最大数量是1000个。
若是Cluster不处于从新配置过程,那么就会达到一种稳定状态。在稳定状态下,一个槽位只由一个master提供服务,不过一个master节点会有一个或多个slave,这些slave能够提供缓解master的读请求的压力。
Redis Cluster会对key使用CRC16算法进行hash,而后对16384取模来肯定key所属的槽位(hash tag会打破这种规则)。
标签是破坏上述计算规则的实现,Hash tag是一种保证多个键被分配到同一个槽位的方法。
hash tag的计算规则是:取一对大括号{}之间的字符进行计算,若是key存在多对大括号,那么就取第一个左括号和第一个右括号之间的字符。若是大括号以前没有字符,则会对整个字符串进行计算。
说了这个多,可能你仍是一头雾水。别急,咱们来吃几个栗子。
前面聊性能的时候咱们提到过,Redis Cluster为了提升性能,不会提供代理,而是使用重定向的方式让client链接到正确的节点。下面咱们来详细说明一下Redis Cluster是如何进行重定向的。
Redis客户端能够向集群的任意一个节点发送查询请求,节点接收到请求后会对其进行解析,若是是操做单个key的命令或者是包含多个在相同槽位key的命令,那么该节点就会去查找这个key是属于哪一个槽位的。
若是key所属的槽位由该节点提供服务,那么就直接返回结果。不然就会返回一个MOVED错误:
GET x
-MOVED 3999 127.0.0.1:6381
复制代码
这个错误包括了对应的key属于哪一个槽位(3999)以及该槽位所在的节点的IP地址和端口号。client收到这个错误信息后,就将这些信息存储起来以即可以更准确的找到正确的节点。
当客户端收到MOVED错误后,可使用CLUSTER NODES或CLUSTER SLOTS命令来更新整个集群的信息,由于当重定向发生时,不多会是单个槽位的变动,通常都会是多个槽位一块儿更新。所以,在收到MOVED错误时,客户端应该尽早更新集群的分布信息。当集群达到稳定状态时,客户端保存的槽位和节点的对应信息都是正确的,cluster的性能也会达到很是高效的状态。
除了MOVED重定向以外,一个完整的集群还应该支持ASK重定向。
对于Redis Cluster来说,MOVED重定向意味着请求的slot永远由另外一个node提供服务,而ASK重定向仅表明下一个请求须要发送到指定的节点。在Redis Cluster迁移的时候会用到ASK重定向,那Redis Cluster迁移的过程到底是怎样的呢?
Redis Cluster的迁移是以槽位单位的,迁移过程总共分3步(相似于把大象装进冰箱),咱们来举个栗子,看一下一个槽位从节点A迁移到节点B须要通过哪些步骤:
有同窗会问了,说好的用到ASK重定向呢?上面咱们所描述的只是迁移的过程,在迁移过程当中,Redis仍是要对外提供服务的。试想一下,若是在迁移过程当中,我向A节点请求查询x的值,A说:我这没有啊,我也不知道是传到B那去了仍是我一直就没有存,你仍是先问问B吧。而后返回给咱们一个-ASK targetNodeAddr的错误,让咱们去问B。而这时若是咱们直接去问B,B确定会直接说:这个不归我管,你得去问A。(-MOVED重定向)。由于这时候迁移尚未完成,因此B也没说错,这时候x真的不归它管。可是咱们不能让它俩来回踢皮球啊,因此在问B以前,咱们先给B发一个asking指令,告诉B:下面我问你一个key的值,你得当成是本身的key来处理,不能说不知道。这样若是x已经迁移到B,就会直接返回结果,若是B也查不到x的下落,说明x不存在。
了解了Redis Cluster的重定向操做以后,咱们再来聊一聊Redis Cluster的容错机制,Redis Cluster和大多数集群同样,是经过心跳来判断一个节点是否存活的。
集群中的节点会不停的互相交换ping pong包,ping pong包具备相同的结构,只是类型不一样,ping pong包合在一块儿叫作心跳包。
一般节点会发送ping包并接收接收者返回的pong包,不过这也不是绝对,节点也有可能只发送pong包,而不须要让接收者发送返回包,这种操做一般用于广播一个新的配置信息。
节点会每一个几秒钟就发送必定数量的ping包。若是一个节点超过二分之一NODE_TIME时间没有收到来自某个节点ping或pong包,那么就会在NODE_TIMEOUT以前像该节点发送ping包,在NODE_TIMEOUT以前,节点会尝试TCP重连,避免因为TCP链接问题而误觉得节点不可达。
前面咱们说了,ping和pong包的结构是相同的,下面就来具体看一下包的内容。
ping和pong包的内容能够分为header和gossip消息两部分,其中header包含如下信息:
gossip包含了该节点认为的其余节点的状态,不过不是集群的所有节点。具体有如下信息:
gossip消息在错误检测和节点发现中起着重要的做用。
错误检测用于识别集群中的不可达节点是否已下线,若是一个master下线,会将它的slave提高为master。若是没法提高,则集群会处于错误状态。在gossip消息中,NODE flags的值包括两种PFAIL和FAIL。
若是一个节点发现另一个节点不可达的时间超过NODE_TIMEOUT ,则会将这个节点标记为PFAIL,也就是Possible failure(可能下线)。节点不可达是说一个节点发送了ping包,可是等待了超过NODE_TIMEOUT时间仍然没有收到回应。这也就意味着,NODE_TIMEOUT必须大于一个网络包来回的时间。
PFAIL标志只是一个节点本地的信息,为了使slave提高为master,须要将PFAIL升级为FAIL。PFAIL升级为FAIL须要知足一些条件:
若是知足以上条件,A节点会将B节点标识为FAIL而且向全部节点发送B节点FAIL的消息。收到消息的节点也都会将B标为FAIL。
FAIL状态是单向的,只能从PFAIL升级为FAIL,而不能从FAIL降为PFAIL。不过存在一些清除FAIL状态的状况:
PFAIL提高到FAIL使用的是一种弱协议:
因为是弱协议,Redis Cluster只要求全部节点对某个节点的状态最终保持一致。若是大部分master认为某个节点FAIL,那么最终全部节点都会将其标为FAIL。而若是只有一小部分master节点认为某个节点FAIL,slave并不会被提高为master,所以,FAIL状态将会被清除。
原理说了这么多,咱们必定要来亲自动手搭建一个Redis Cluster,下面演示一个在一台机器上模拟搭建3主3从的Redis Cluster。固然,若是你想了解更多Redis Cluster的其余原理,能够查看官网的介绍。
首先要搭建起咱们须要的Redis环境,这里启动6个Redis实例,端口号分别是637九、6380、647九、6480、657九、6580
拷贝6份Redis配置文件并进行以下修改(以6379为例,端口号和配置文件根据须要修改):
port 6379
cluster-enabled yes
cluster-config-file nodes6379.conf
appendonly yes
复制代码
配置文件的名称也须要修改,修改完成后,分别启动6个实例(图片中有一个端口号改错了……)。
实例启动完成后,就能够建立Redis Cluster了,若是Redis的版本是3.x或4.x,须要使用一个叫作redis-trib的工具,而对于Redis5.0以后的版本,Redis Cluster的命令已经集成到了redis-cli中了。这里我用的是Redis5,因此没有再单独安装redis-trib工具。
接下来执行命令
redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6479 127.0.0.1:6480 127.0.0.1:6579 127.0.0.1:6580 --cluster-replicas 1
复制代码
当你看到输出了
[OK] All 16384 slots covered
复制代码
就表示Redis Cluster已经建立成功了。
此时咱们使用cluster nodes 命令就可查看Redis Cluster的节点信息了。
能够看到,637九、6380和6479三个节点被配置为master节点。
接下来咱们再来尝试一下reshard操做
如图,输入命令
redis-cli --cluster reshard 127.0.0.1:6380
复制代码
Redis Cluster会问你要移动多少个槽位,这里咱们移动1000个,接着会询问你要移动到哪一个节点,这里咱们输入6479的NODE ID
reshard完成后,能够输入命令查看节点的状况
redis-cli --cluster check 127.0.0.1:6480
复制代码
能够看到6479节点已经多了1000个槽位了,分别是0-498和5461-5961。
咱们也能够用add-node命令新增slave节点,只不过须要加上--cluster-slave参数,而且使用--cluster-master-id指明新增的slave属于哪一个master。
最后来总结一下,咱们介绍了
Redis Cluster的特性:写安全、可用性、性能
Key分配模型:使用CRC16算法,若是须要分配到相同的slot,可使用tag
两种重定向:MOVED和ASK
容错机制:PFAIL和FAIL两种状态
最后又动手搭建了一个实验的Redis Cluster。