Redis Cluster

使用 Redis Cluster

Redis 3.0 在2015年出了Stable版本,3.0版本相对于2.8版本带来的主要新特性包括:

  • 实现了Redis Cluster,从而做到了对集群的支持;
  • 引入了全新的"embedded string" 对象编码方式,从而实现了更少的缓存丢失和在特定的工作负载下速度的大幅提升;
  • AOF重写过程中的 "last write" 操作降低了AOF child -> parent数据传输的延迟;
  • 大幅提升LRU算法的性能以用于值的失效;
  • WAIT命令能够阻塞传输到指定数量从节点的写操作;
  • 实现了对MIGRATE连接缓存的支持,从而大幅提升键值迁移的性能;
  • 为MIGARTE新增了参数:COPY和REPLACE;
  • CLIENT PAUSE命令实现了在指定时间内停止处理客户端请求;
  • 提高了BITCOUNT、INCR操作的性能;
  • CONFIG SET能够接受不同单位的内存值,如CONFIG SET maxmemory 1gb;
  • 调整Redis日志格式。

下面要重点说一下Redis Cluster的实现。

Reids Cluster 架构

特征:

  • redis cluster 采用无中心结构,每个节点都保存数据和整个集群的状态;
  • 节点之间使用GOSSIP协议彼此互联(PING-PONG机制),这些连接保持活跃,内部使用二进制协议优化传输速度和带宽;
  • 节点的 fail 是通过集群中超过半数的节点检测失效时才生效;
  • 客户端与redis节点直连,不需要中间proxy层;client根据node返回的错误信息重定向请求;

Redis 集群不支持那些需要同时处理多个键的 Redis 命令(例如mget), 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的行为。

Redis 集群提供了以下两个好处:

  • 将数据自动切分(split)到多个节点的能力;
  • 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。

分片

Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽。

集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群有三个节点,每个节点维护一部分哈希槽, 其中:

  1. 节点 A 负责处理 0 号至 5500 号哈希槽;
  2. 节点 B 负责处理 5501 号至 11000 号哈希槽;
  3. 节点 C 负责处理 11001 号至 16384 号哈希槽;

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。
因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

数据迁移

基于桶的数据分布方式大大降低了迁移成本,只需将数据桶从一个Redis Node迁移到另一个Redis Node即可完成迁移。
当桶从一个Node A向另一个Node B迁移时,Node A和Node B都会有这个桶,Node A上桶的状态设置为MIGRATING,Node B上桶的状态被设置为IMPORTING
当客户端请求时,所有在Node A上的请求都将由A来处理,所有不在A上的key都由Node B来处理。同时,Node A上将不会创建新的key

主从

为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作, Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个副本(replica),其中一个副本为主节点(master), 而其余的 N-1 个副本为从节点(slave)。

在之前列举的节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000号的哈希槽。

另一方面,假如在创建集群的时候(或者至少在节点 B 下线之前),我们为主节点 B 添加了从节点 B1 ,那么当主节点 B 下线的时候,集群就会将 B1 设置为新的主节点,并让它代替下线的主节点 B ,继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。不过如果节点 B 和 B1 都下线的话, Redis 集群还是会停止运作。

选举

  • 如果半数以上master节点与master节点通信超时(cluster-node-timeout),就认为当前master节点挂掉;
  • 如果集群超过半数以上master挂掉,无论是否有slave,集群都进入fail 状态,即集群不可用;
  • 当集群不可用时,所有对集群的操作做都不可用,收到  ((error) CLUSTERDOWN The cluster is down) 错误;

a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态.

ps : redis-3.0.0.rc1加入cluster-require-full-coverage参数,默认关闭,打开集群兼容部分失败.

Reids Cluster 客户端

目前单机版的 redis,各个语言的client基本非常成熟。而redis cluster的client功能还不完备,自己开发维护cluster client的代价又太高,大多数团队也不能忍,更何况可能还有bug。如果把redis cluster设计成类似Cassandra,请求集群中任何一个节点都可以负责转发请求,client会好写一些,甚至可能支持用单机driver来请求cluster实现平滑升级,但多一次转发之后相对于proxy的方案就完全没有性能优势了。这个缺点在当前很严重,业务等不起,几个月后可能java不是问题、一两年后可能其他主流语言也不是问题,但还是那句话,业务不等人,你这一两年怎么办?当然不如直接用codis。

Redis 集群中的节点不仅要记录键和值的映射,还需要记录集群的状态,包括键到正确节点的映射。它还具有自动发现其他节点,识别工作不正常的节点,并在有需要时,在从节点中选举出新的主节点的功能。

为了执行以上列出的任务, 集群中的每个节点都与其他节点建立起了“集群连接(cluster bus)”, 该连接是一个 TCP 连接, 使用二进制协议进行通讯。

节点之间使用 Gossip 协议 来进行以下工作:

a).传播(propagate)关于集群的信息,以此来发现新的节点。

b).向其他节点发送 PING 数据包,以此来检查目标节点是否正常运作。

c).在特定事件发生时,发送集群信息。

除此之外, 集群连接还用于在集群中发布或订阅信息。

集群节点不能前端代理命令请求, 所以客户端应该在节点返回 -MOVED或者 -ASK转向(redirection)错误时, 自行将命令请求转发至其他节点。

客户端可以自由地向集群中的任何一个节点发送命令请求, 并可以在有需要时, 根据转向错误所提供的信息, 将命令转发至正确的节点, 所以在理论上来说, 客户端是无须保存集群状态信息的。但如果客户端可以将键和节点之间的映射信息保存起来, 可以有效地减少可能出现的转向次数, 籍此提升命令执行的效率。

每个节点在集群中由一个独一无二的 ID标识, 该 ID 是一个十六进制表示的 160 位随机数,在节点第一次启动时由 /dev/urandom 生成。节点会将它的 ID 保存到配置文件, 只要这个配置文件不被删除, 节点就会一直沿用这个 ID 。一个节点可以改变它的 IP 和端口号, 而不改变节点 ID 。 集群可以自动识别出IP/端口号的变化, 并将这一信息通过 Gossip协议广播给其他节点知道。

下面是每个节点都有的关联信息, 并且节点会将这些信息发送给其他节点:

a).节点所使用的 IP 地址和 TCP 端口号。

b).节点的标志(flags)。

c).节点负责处理的哈希槽。

b).节点最近一次使用集群连接发送 PING 数据包(packet)的时间。

e).节点最近一次在回复中接收到 PONG 数据包的时间。

f).集群将该节点标记为下线的时间。

g).该节点的从节点数量。

如果该节点是从节点的话,那么它会记录主节点的节点 ID 。 如果这是一个主节点的话,那么主节点 ID 这一栏的值为 0000000。

原文地址:https://www.cnblogs.com/chenny7/p/5262889.html