标签:
主要特性和设计原理
Redis集群目标
Redis集群是Redis集群中的一种分布式实现,它有以下目标(按照重要性进行排序):
- 高性能,可以线性扩展至1000个节点;没有代理,使用异步复制,在values上面没有合并操作。
- 可接受范围内的写安全:系统尽最大努力执行从客户端(和绝大多数master节点连接)发过来的所有写操作。在极端情况下,可能会有一小部分确认写会发生丢失。如果客户端只是连接了少数节点( are
in a minority partition),丢失的确认写数目会更多。
- 可用性:当每个挂掉的master都至少有一个可达的slave,并且绝大多数master节点都是正常的时候,Redis集群还可以正常运行。通过副本迁移(replicas
migration,),没有slave的master会从另外一个master(它有多个slave)得到一个slave。
实现的功能子集
- Redis集群实现了所有非分布式Redis版本中对单个key操作的命令。哪些对多个key进行操作的命令(比如:set的union或intersection等),如果这些key都在一个节点上,那么Redis集群也是支持的。
- Redis集群实现一个hash
tags的概念,它主要用于将某些key存放在同一个节点上面;但是在手动reshading的时候,多个key的操作命令,在一段时间内可能会无法使用,不过单个key的操作还是可以正常使用的。
- Redis集群不支持多个数据库的概念,也就是说只有数据库0(SELECT命令无效)。
在Cluster集群中,client和server的角色
- 在Redis当中,cluster节点负责持有数据(hoding
the data),获取cluster的状态(包括:将key映射到对应节点上面)。cluster节点还能够自动发现其它节点,检测到失败节点,并能够在需要的时候将slave提升为master,以确保有错误发生的时候,集群还能够继续工作。
- 为了执行任务,cluster的节点之间使用tcp进行连接,并使用私有的二进制协议进行信息传输(也称为:Redis
Cluster Bus)。在Cluster Bus上,节点之间可以使用gossip协议对当前集群的信息进行泛洪传播,这样有利于发现新节点;可以发送ping包以确认其它节点都是正常工作的,也可以发送cluster
messages来通知一种特殊事件的发生。Cluster Bus也用来在cluster节点间广播Pub/Sub消息,当有用户发送了主备切换的指令后,配合完成人工切换。
- 由于Cluster节点不能够代理请求,所有client也许会被重定向到其它节点上面(使用错误信息:MOVED或ASK)。理论上,client可以向集群中任何节点发送请求,期间可能获取到重定向请求,因此client不需要了解集群的状态。然而客户端可以缓存key和node的对应关系来提升性能。
写安全
- Redis集群的节点之间是异步复制的,上一次故障转移成功(last
failover wins)隐含着数据合并的功能。这就意味着上次被选举的master的数据集最终会被所有其它副本所替换。在数据分片之间总是存在一个丢失写的时间窗口。然而这个时间窗口对于连接到少数master的客户端和连接到多数master的客户端是很不一样的。Redis集群对连接到多数master的client发送的写,比连接到少数master的client发送的写,提供更好的成功保障。下面是一些在错误发生的时候,导致多数分片丢失确认写的场景例子:
1、一个写到达master;master在本地写入成功后,返回了client成功消息;但是孩没有来得及将本次写发送给slave进行复制就挂掉了;如果master过了一段时间都还没来得及恢复(其它slave提升为master了),那么,这个写就永远丢失了。这种情况应该比较少出现,master在同时回复客户端和slave的时候挂掉了。但这种情况的确是可能存在的。
2、另外一种理论上可能出现写丢失的情况是:
- master因为一个分片变得不可访问
- 它被一个slave替换了
- 过了一段时间,master变得可以访问了
- 持有老的路由表的client可能会将数据写入到原来的master上面,直到它的路由表进行了更新。
第2种情况,一般不太可能发生;因为一个master如何在发生切换的这段时间内都无法和其它大多数master进行通信的时候,它是不会接受任何写操作的。当这个master的分片状态恢复写的时候,它也会预留一段不接受写的时间,以便通知其它节点配置发生变化了。而且这个问题发生还需要client的路由表也没有发生更新。
对某个分片的少部分master进行写操作有更大的写丢失窗口。比如:RedisCluster会丢失一定数目的只有少部分节点才完成的写操作;客户端发送的写操作,只有少数master进行了更新,而其它大多数节点都没有更新,在这个过程中如果大多数节点中有的节点发生了切换,那么这些写就可能被丢失。
总结:一个master挂了,条件是至少在NODE_TIMEOUT时间内它和绝大部分master都无法通信,所以从这个时候开始到分片修复这段时间,写不会丢失;当分片失败持续时间超过NODE_TIMEOUT,那么截至到这个时间点,在少部分node上执行的写也许会丢失;但是那些少部分的节点,如果经过了NODE_TIMEOUT后还无法连接大多数的master,那么它们会启动禁止写操作;所以,对于少部分节点变得不可达有一个最大的时间窗口(就是NODE_TIMEOUT)。经过了这个时间窗口之后,没有写会接受或丢失。
高可用性
- Redis Cluster在分片的少数节点上不可用。假定每一个不可用的少数节点都有一个slave,该分片的大多数节点都是可用的,那么在NODE_TIME加上几秒钟的切换时间之后(一般1,2秒钟),Redis
Clusteri又会变得可用。
- 这就是说Redis Cluster的设计适用于集群中有部分节点失败的时候,还可以自动进行恢复。但是,它不适用于哪些要求在大多数节点都失败的情况下还能恢复集群的应用。
- 假设有N个Master的Redis集群,每个Master都有一个slave;如果有一个节点挂掉了,那么集群还是可用的;如果有两个节点挂掉了,那么可用性为:1-(1/(N*2-1)),因为两个节点挂掉了的时候,第一个节点挂掉之后还剩余节点:N*2-1个,而这个节点的slave也挂掉的可能性为:1/(N*2-1).
- 如果有5个节点(每个节点有一个slave),那么有1/(5*2-1)=11.1%的可能性,有2个节点挂掉之后,集群不可用了。
- 由于Redis Cluster有副本迁移(replica
migration)的功能,在现实场景下,Redis Cluster的可靠性更高,因为Redis
Cluster会自动将副本迁移到没有任何副本(slave)的master上面(orphanded
masters)。因此,每一次从失败中恢复之后,集群会自动调整slave的部署,以便下次更好地从失败中恢复过来。
性能
- Redis Cluster节点不会将命令发送到负责这个key的真正节点上面,而是引导client到负责给定key所在key空间的正确节点上面。最终client可以获取到最新的集群结构,那个node负责哪些key,后续,client可以直接将key转发到对应节点上面。
- 由于使用了异步复制,节点不会等待其它节点的确认(如果没有使用WAIT命令的话)
- 多个key的命令只适用于“接近”的keys上面,所以除了resharding,数据不会在在节点间迁移
- 一般的操作都和单机上执行一样,所以性能一般是N*单机性能。同时一个查询一般就是一个RT,因为客户端一般都会维持和节点的连接,所以延迟也和单机Redis差不多。
- 高性能和高可靠性;以及在可接受范围内的数据安全和可靠性的前提下,提供很好的容错性;这些就是Redis Cluster的主要目标。
为什么要避免合并操作
- Redis Cluster的设计避免了相同key-value对在不同节点间的版本冲突,因为Redis数据模型不能够完全满足这个要求。在Redis中存储的数据量常常是很大的,同时数据类型一般都是比较复杂的。传输和合并各类数据值可能成为系统瓶颈,可能需要业务逻辑的参与,需要存储元数据的额外内存等等。
- 这里不是技术的问题,CRDTs或其他同步复制的机器可以有和Redis类似的复杂数据模型,但是它们的实际运行运行的情况和Redis
Cluster不一样;Redis Cluster设计的目标是让Redis
Cluster的使用和单机版本一样。
Redis Cluster主要组件概述
Keys分布模型
- key空间被分到16384个槽中,也就决定了Redis
Cluster最多有16384个master节点(建议节点数目不要超过1000)。
- cluster中的每一个master处理16384个hash
slot的子集。当集群没有进行重配置的时候(重配置的时候,槽会从一个节点移动另外一个节点),我们称集群处于stable状态。当集群处于stable的时候,一个hash
slot只会被一个node负责处理(但是读可以通过slave进行扩充)。
- 通过下面的算法将key映射到对应slot(槽)中
HASH_SLOT =CRC16(key) mod 16384
- CRC16的算法过程如下:
- 名称:XMODEM(也称之为ZMODEM或CRC-16/ACORN)
- 宽度:16位
- Poly:1021(也就是x^16
+ x^12 + x^5+1)
- 初始化:0000
- Reflect Input Type:False
- Reflect Output Type:False
- Xor constant to output CRC:0000
- Output for "123456789":31C3
Keys Hash tags
- 在hash slot计算中,有一个例外,它用来实现hash
tags。Hash Tags是确保多个keys被分配到同一个hash槽中的方式。这个是为了在Redis
Cluster上实现多个key的操作。
- 为了实现hash tags,一个key的hash
slot计算稍稍有点不一样;如果一个key包含"{…..}"的样式,只有{和}之间的部分会用于计算hash
slot。然而由于key中可能有多个{和},算法使用下面规则:
- 如果key包含一个{字符
- 并且如果有一个}字符和{对应
- 并且在第一个{和之后第一个}之间有一个或多个字符
这个时候将使用第一个{和之后第一个}之间的字符用来计算hash
slot。
比如:
- {user1000}.following和{user1000}.followers会hash到同一个槽上面,因为只有user1000用来计算hash值
- foo{}{bar},将使用所有字符串进行hash值计算,因为第一个{和}之间没有字符
- foo{{bar}}zap,{bar用来计算hash
- foo{bar}{zap},只有bar用于计算hash
- 这个算法中有一个很有用的地方是,如果key以{}开头,那么整个key都会用来计算hash
Cluster节点属性
- Cluster中的每一个节点都有唯一的名字。节点名字是一个160位的16机制字符串,在节点第一次启动时候生成(一般使用/dev/urandom生成)。节点将会把这个名字保存在文件当中,以后都为使用这个名字保持不变(只要管理员没有删除这个文件或没有通过CLUSTER
RESET命令发送的hand reset请求到来)。
- node ID是用来标识集群中每个节点的,给某个节点只改动ip,而不改动node
id是可以得。cluster可以检测到IP/Port的改变,然后使用运行在cluster
bus上的gossip协议来重新配置。
- nodeID不是每个集群中节点的唯一信息,但是是全局一致的信息。每个节点还有如下一些信息;一些信息是关于这个节点的详细配置信息,一些(比如:节点上次发送ping的时间)是节点本地拥有的信息。
- 每个节点都维护了其它节点的以下信息:nodeID,节点的IP、端口,一些标志,如果某个节点的flag是slave,那它对应的master是什么?,上次给这个节点发送ping的时间,最近一次接收到ping的响应pong的时间,节点的当前配置年代(configuration
epoch),连接状态,负责处理的hash slot。
- 具体的配置信息,可以通过命令CLUSTER NODES来获取;这个命令可以发送给任何集群中节点,这个命令会返回集群的状态,以及这个节点看到的这个集群每个节点的信息
$ redis-cli clusternodes
d1861060fe6a534d42d8a19aeb36600e18785e04 127.0.0.1:6379 myself - 0 1318428930 1connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 13184289301318428931 2 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 13184289311318428931 3 connected 2730-4095
Cluster bus
- 每一个集群节点都有一个接收其它节点连接的端口,这个端口的大小是接收客户端连接的端口大小 + 10000,比如:接收client的端口为6379,那么这个端口的大小为16379.
- 节点和节点之间的通信无一例外都是通过Cluster Bus来进行的,通信的协议是一个二进制协议,它由不同类型和大小的frame组成。这个二进制协议没有公开文档,因为不想让其它外部软件和Redis
Cluster 节点通过这个协议进行通信。但是我们可以通过阅读源代码来了解这个协议。
Cluster拓扑
- Redis Cluster是一个完全图,每一个节点都通过tcp和其它所有节点进行连接。在N个节点的集群中,每一个节点都拥有N-1个outgoingTCP连接,N-1个incoming
TCP连接。
- 这些TCP连接始终是保持连接的,而不是按需创建;当一个节点期望它通过Cluster
Bus发出的ping有一个pong的响应,如果等待了一段时间还没收到,则认为这个节点不可达;它将会尝试和这个节点进行重连。
- 虽然Redis Cluster是一个完全图,但是节点间使用gossip协议和一个配置更新机制,从而避免在一般情况下,在节点间交换太多的信息。所以交换的信息不是指数级的。
Node HandShake
- 节点通过clusterbus
port来接收连接,当接收到ping时,使用这个端口进行响应,即使ping的节点不是被信任的。但是,如果发送报文的节点被认为不是集群的一部分,这个节点发送的其它报文都会被接收节点丢弃。
- 有两种方式,可以让一个节点来接受另外一个节点作为集群的一部分:
- 如果一个节点发送了MEET消息;一个meet消息和一个ping消息是类似的,但是会让接收者接收它为集群的一部分。节点只有在系统管理员使用以下命令发送请求后才会发送MEET消息:
CLUSTER MEET ip port
- 如果一个节点已经被信任,信任这个节点的节点将会通过gossip消息来通知其它节点,有个节点加入到了集群中;例如:A
knows B,B knows C,最终B会发送gossip消息个A,告诉C加入了集群;然后A将会降入C,并尝试连接C
- 通过上面的方式,只有我们向任何已经在集群中的节点发送加入消息,最终会自动形成一个完全连接的图。也就是节点可以自动发现其它节点,但是只有在系统管理员发出了命令才行(也就是系统管理员信任这个节点)。
Redirecting and resharding(重定向和重分片)
Moved重定向
- Redis client可以向集群中的任何节点发送查询请求,包括slave节点。节点分析这个请求,如果这个请求是可接受的(请求只有一个key,或多个key都在同一个hash
slot上面),它就会查询那个节点负责这个hash slot。
- 如果这个hash slot是当前结点负责,则直接处理;否则,向客户端返回一个MOVED错误,比如:
GET x
-MOVED 3999 127.0.0.1:6381
- 这个错误信息包括:hash slot(3999)以及处理这个slot的ip和端口;client会根据这个错误信息,重定向到给出的节点,但是这次请求也可能失败,比如:在请求过程中,集群配置发生了变化或者上次请求的节点信息没有及时得到更新
- 从集群节点来看,节点都是使用ID来标识的;但是对于client来说,只暴露hash
slot和IP/port之间的map关系。
- client一般都会对这个hash
slot和ip/port之间的关系进行缓存;
- 另外一种刷新hash slot和ip/port之间关系的方式是在遇到MOVED消息之后使用CLUSTER
NODE或CLUSTER SLOTS命令;如果碰到了一个MOVED消息,那就说明集群的配置发生了变化,应该不止一个hash
slot发生了关系变化,而是很多个,所以全量刷新一遍是最好的策略
- 当集群是stable的时候,最终所有client都会获得hash
slots ->ip/port之间的关系,使得集群变得高效,因为clients可以直接发送消息到正确的nodes上面
- client还必须能够处理ASK重定向消息
Cluster在线重配置
- Redis Cluster支持在Cluster正在运行的时候增加和移除节点。增加和移除节点被抽象成同一个操作,将hash
slot从一个节点转移到另外一个节点。这个机制也可以用于rebalance the cluster,增加删除节点等等
- 向cluster增加一个节点:一个空节点增加到集群,有些hash
slot需要从其它节点迁移到新加入的节点
- 从cluster删除一个节点:在这个节点上面的slot需要迁移到其它节点
- rebalance cluster:一些给定的节点需要在节点间进行迁移。
- 所有实现的核心就是hash slot的迁移;实时上,hash
slot就是一个key的集合;所以在Resharding的过程中,就是把key从一个节点迁移到另外一个节点;move一个hash
slot,就是迁移所有这个hash slot上面的key。
- 和迁移相关的CLUSTER命令有:(用于操作一个Cluster节点上的slot迁移表)
- CLUSTER ADDSLOTS slot1 [slot2]
... [slotN]
- CLUSTER DELSLOTS slot1 [slot2]
... [slotN]
- CLUSTER SETSLOT slot NODE
node
- CLUSTER SETSLOT slot MIGRATING
node
- CLUSTER SETSLOT slot IMPORTING
node
前面两个两个命令只是简单的从一个Redis节点上分配和移除slots。分配一个槽就是告诉指定的master,它将负责存储和服务于和给定槽的内容。在槽分配之后,它们会通过gossip协议,将这个配置信息在集群中进行传播。
ADDSLOTS命令一般用于在新建集群的时候为每一个master节点分配它需要处理的hashslot的子集。
DELSLOTS命令一般用于在调试的时候来手动修改cluster的配置,一般不会使用
SETSLOT命令将某个slot分配给某个节点;否则一个slot可以被设置为两个特殊状态:MIGRATING和IMPORTING,他们都是为了进行slot迁移而引入的。
- 当一个slot是MIGRATING状态的时候,如果key存在的话,节点会接受这个hash
slot的所有请求;否则的话,这个请求会使用-ASK重定向转移到节点迁移的目的节点上。
- 当一个slot是IMPORTING状态的时候,节点会接受这个hash
slot的所有请求,但是前提是这个请求执行发送过ASKING命令;如果之前没有发送过ASKING命令,那么这个请求使用MOVED重定向到真正hash
slot拥有的节点上。
我们举个例子来说明slot的迁移:
假定我们有两个master节点A和B,我们需要将slot
8 从A迁移到B,因此我们使用下面的命令:
- 向B发送命令:CLUSTER
SETSLOT 8IMPORTING A
- 向A发送命令:CLUSTER
SETSLOT 8MIGRATING B
- 所有其它节点将会继续指示client对于slot
8的请求都转向机器A;因此,所有还存在的key会被A处理;所有已经迁移了得key会被B处理,因为A会重定向client到B上面。
使用这种方式,我们不再在A上创建新的key;同时一个名为redis-trib的程序在resharding过程中会被使用,ReidsCluster配置会将slot
8中存在的key从A迁移到B上面,这个过程使用下面的命令完成:
CLUSTER GETKEYSINSLOT slot count
- 上面的命令将会从slot中返回count个key;对于每一个返回的key,redis-trib给节点A发送一个MIGRATE命令,它将将这个key从A原子的迁移到B上(两个节点上都会锁住一段时间,所以不会产生竞争)。
MIGRATE target_host target_port keytarget_database id timeout
MIGRATE将会连接target实例,发送key序列化后的版本,一旦接收到了OK之后,它会从自己dedataset里面删除这个key。从外部client来看,在任何给定的时间内,一个key只会存在在A上或B上。
- 在Redis Cluster中,target_database是固定为0的,但是这个命令可以用于其它情况下,而不仅仅是Redis
Cluster上面。MIGRATE并优化为尽量快的,即使我们移动的是一个复杂的数据结构,比如:很长的list。但是在Redis
Cluster中,在有big key存在的情况下进行cluster的重配置,不是一个明智的选择,特别是对延迟有要求的情况下。
- 当迁移最终完成,命令SETSLOT
<slot> NODE <node-id>命令会发送给参与迁移的两个节点,以便将slot置为正常的状态;同样的这个命令也会向其它节点进行发送,以便尽快进行配置更新。
ASK重定向
- ASK命令是用来告诉client,只有下一个请求要发送给给定的节点;而MOVED是告诉client,后续和这个slot相关的所有请求都发送到给定的节点;这里的ASK是必须的,因为可能当前还有slot
8的key在A上面。所以对于slot
8 都需要先从A上面查询,然后到B上面;因为只有1个slot会这么做,所以整体性能还是可以接受的。
- 最基本的,ASKING命令在客户端设置one-time(一次)flag,以迫使节点处理处于IMPORTING状态的查询请求。
- 从client的角度来看,ASK重定向的完成场景如下:
- 如果接收到了ASK重定向,只发送当前请求到ASK指定的节点上面,但是其它请求还是发送到原来的节点上面
- 使用ASKING命令发送请求到ASK指定的节点上面
- 还不会将slot8到B插入到本地缓存里面
- 一但slot迁移完成,A将会发送MOVED消息,client将会始终发送消息到B上面。注意:如果一个有bug的客户端,过早的将slot8到B插入到本地缓存,这个没有问题,因为这个client不会在发送请求前发送ASKING命令,所以,B将会使用MOVED来重定向请求到A上面。
客户端第一次连接和重定向处理
- client必须要在内部缓存slot和对应node的关系,否则这个client性能会比较低
- 虽然client需要缓存slot和对应node的关系,但是它不需要实时去更新这个缓存,因为如果发送query到了错误的节点,可以使用重定向来进行更新
- client在两种情况下,需要获取一个完整的slot和node的对应关系表:
- client可以在获取到MOVED重定向请求的时候,只更新这个slot和node的对应关系,但是这么做,很没有效率,因为通常情况是多个slot会同时进行对应关系的修改(比如:一个slave提升为master,原master
server的所有slot对应的node关系都发生了变化)。
- 为了获取slots和node的对应关系,Reids
Cluster提供了一个可选的命令:CLUSTER NODES,这个命令不需要进行解析,只提供客户端需要的slots和node的对应关系信息。
- 另外还有一个CLUSTER SLOTS命令,提供了一个slots范围列表,和对应的master和slave
node。
多个Key操作
- 可以使用hash tag,client可以使用多个key的操作,比如,下面的命令:
MSET {user:1000}.name Angela{user:1000}.surname White
- 多个key的操作在key对应slot正在进行迁移的时候,会变得暂时不可用。但是,即使当key对应slot在迁移,如果操作的这些key都在同一个节点上面,操作还是可以顺利执行。如果操作实在不可用,Redis
Cluster会向客户端发送TRYAGAIN错误,client可以稍后再试,或向客户端返回一个错误提示。
使用slave节点扩展读
- 一般情况下,slave节点收到请求后,会将命令重定向到(使用MOVED)key对应slot的master节点上面。但是client可以使用READONLY命令,使用slave来扩展读。
- READONLY告诉Redis
Cluster的slave节点,请求只对读数据感兴趣,对写不感兴趣。
- 当创建了只读连接之后,当请求中的key不是slave对应master节点serve的slot,则会发挥重定向给client。当这种情况发生了,client也必须按照前面提到的方法来更新slot到node的映射关系。
Redis集群规范(一)
标签:
原文地址:http://blog.csdn.net/qq_28674045/article/details/51360350