Redis高可用集群特性

Redis集群:Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误。

redis单节点的主从模式,可以解决机器宕机的问题,因为redis可以在这种情况下进行主从切换,但是随着业务越来越大,对缓存的大小需求也越来越大,redis集群就是为了应对这种场景而诞生。

1、分布式系统中的CAP定理&&BASE理论

  • Availability:可用性。意思是只要收到用户的请求,服务器就必须给出回应。
  • Consistency:强一致性。在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • Partition tolerance:分区容错。分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

在Redis集群中,单独对一个主从节点分析:当一个客户端发送set命令时,如果要保证强一致性,那么在master节点中set完成后,还要将这个数据同步给从节点,同步未完成之前,客户端是拿不到这个数据的( a)。如果不要求强一致性,可以采用异步更新的方式来同步数据给从节点(图 b)。也可以采用消息队列如RocketMQ、Kafka(图 c)。后两种方式都是保证数据的最终一致性,从而提升redis的响应速度。

可见Redis 并不能保证数据的强一致性,redis采用异步复制机制。 这意味这在实际中集群在特定的条件下可能会丢失写操作。

第一个原因是因为集群是用了异步复制, 写操作过程:

  • 客户端向主节点B写入一条命令.
  • 主节点B向客户端回复命令状态.
  • 主节点将写操作复制给他得从节点 B1, B2 和 B3.

主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。

Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1。 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1。Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项

CAP三个特性只能满足其中两个。现在基本都会用BASE来权衡CAP。

BASE:Basic Avaliable、Soft state、Eventually consistency。

Basic Avaliable基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性——但绝不等价于系统不可用,以下两个就是“基本可用”的典型例子。

  • 响应时间上的损失:正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
  • 功能上的损失:正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。

Soft state:指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据听不的过程存在延时。

Eventually consistency:最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

结合各个产品(Redis、Zookeeper)设计的初衷,进行BASE权衡,最目前主流的设计方法。

2、redis 集群原理

客户端访问数据时,到底要访问哪台redis?通常的做法是:

1、对key进行hash算法,然后对redis机器数量取模,假设有16台redis, redis_id = hash(key)%16,redis_id的值会在0-15之间。这样可以解决数据访问问题,但是如果要对redis集群扩容的话,这种方式是不好的,因为机器数量变了,就拿不到原来的数据了。

2、一致性hash,本质上也是对key做hash运算,然后路由到不同的机器上,但是一致性hash是这样做的,先用各个redis机器的ip作为进行hash,映射到0-232-1之间。如下图

对key用同样的hash运算,再对232-1取模,也会映射到0-232-1这个范围,那么这些key会存在顺时针方向上离这个key最近的一台redis上。

如图在一致性hash算法的作用下,user1和user2会存在redis03上,user3会存在redis02上,user4会存在redis01上。一致性hash的好处就是,当我们对集群进行扩容时,只需要迁移一小部分的key就行了,不用全部rehash,重新转移key。

Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽.
  • 节点 B 包含5501 到 11000 号哈希槽.
  • 节点 C 包含11001 到 16384号哈希槽.

这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。

Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。

槽位定位算法

Cluster 默认会对 key 值使用 CRC16算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。

HASH_SLOT = CRC16(key)  % 16384

跳转重定向

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。

3、redis集群选举

3.1 redis集群选举流程

当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:

1.slave发现自己的master变为FAIL

2.将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息

3.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack

4.尝试failover的slave收集master返回的FAILOVER_AUTH_ACK

5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)

6.广播Pong消息通知其他集群节点。

从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票

延迟计算公式:

 DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

SLAVE_RANK 表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。

集群是否完整才能对外提供服务

当redis.conf的配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。

3.2 redis 集群节点之间的通信

gossip协议包含多种消息,包括ping,pong,meet,fail等等。  

ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元

数据;  

pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新;  

fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。

meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信,不需要

发送形成网络的所需的所有CLUSTER MEET命令。发送CLUSTER MEET消息以便每个节点能够达到其他每个节点只需通

过一条已知的节点链就够了。由于在心跳包中会交换gossip信息,将会创建节点间缺失的链接。

gossip协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,

有一定的延时,降低了压力;缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后。

Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?

因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。 奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相

比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的。

4、redis集群配置参数

# 当一个slave失去和master的连接,或者同步正在进行中,slave的行为有两种可能:
# 1) 如果 slave-serve-stale-data 设置为 "yes" (默认值),slave会继续响应客户端请求,可能是正常数据,也可能是还没获得值的空数据。
# 2) 如果 slave-serve-stale-data 设置为 "no",slave会回复"正在从master同步(SYNC with master in progress)"来处理各种请求,除了 INFO 和 SLAVEOF 命令。
slave-serve-stale-data yes
 
# 你可以配置salve实例是否接受写操作。可写的slave实例可能对存储临时数据比较有用(因为写入salve# 的数据在同master同步之后将很容被删除),但是如果客户端由于配置错误在写入时也可能产生一些问题。
# 从Redis2.6默认所有的slave为只读
# 注意:只读的slave不是为了暴露给互联网上不可信的客户端而设计的。它只是一个防止实例误用的保护层。
# 一个只读的slave支持所有的管理命令比如config,debug等。为了限制你可以用'rename-command'来隐藏所有的管理和危险命令来增强只读slave的安全性。
slave-read-only yes
 
# 同步策略: 磁盘或socket,默认磁盘方式
repl-diskless-sync no
 
# 如果非磁盘同步方式开启,可以配置同步延迟时间,以等待master产生子进程通过socket传输RDB数据给slave。
# 默认值为5秒,设置为0秒则每次传输无延迟。
repl-diskless-sync-delay 5
 
# slave根据指定的时间间隔向master发送ping请求。默认10秒。
repl-ping-slave-period 10
 
# 同步的超时时间
# 1)slave在与master SYNC期间有大量数据传输,造成超时
# 2)在slave角度,master超时,包括数据、ping等
# 3)在master角度,slave超时,当master发送REPLCONF ACK pings# 确保这个值大于指定的repl-ping-slave-period,否则在主从间流量不高时每次都会检测到超时
repl-timeout 60
                                  
# 设置数据备份的backlog大小。backlog是一个slave在一段时间内断开连接时记录salve数据的缓冲,所以一个slave在重新连接时,不必要全量的同步,
#而是一个增量同步就足够了,将在断开连接的这段# 时间内把slave丢失的部分数据传送给它。
# 同步的backlog越大,slave能够进行增量同步并且允许断开连接的时间就越长。
# backlog只分配一次并且至少需要一个slave连接。
repl-backlog-size 1mb
                                  
#redis提供了可以让master停止写入的方式,如果配置了min-replicas-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个
#健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能
min-replicas-to-write 3

# 延迟小于min-replicas-max-lag秒的slave才认为是健康的slave
# min-replicas-max-lag 10
#表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,
#需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重新复制)。
cluster node timeout = 15

5、redis集群架构设计

如果按照原生的redis集群结构搭建方式(如下图),过多的客户端连接会给redis带来巨大压力。

twitter开源了一款连接代理服务:twemproxy,类似于nginx的反向代理。它的实现如下图。redis 集群部署方式大部分采用类 Twemproxy 的方式进行部署。即通过 Twemproxy 对 redis key 进行分片计算,将 redis key 进行分片计算,分配到多个 redis 实例中的其中一个。(https://github.com/twitter/twemproxy

key是怎么分布在各个redis上的:

  • random (随机存到一台redis 上)

6、redis集群存在的问题

1 不支持事务

2 数据的聚合操作如set取交集,并集很难实现

3 当key处于不同的机器上,不支持mset hmset等批量操作,但是redis中可以实现让多个key处于同一个hash槽,就是用类似于{xxx}:user这样的以{ }前缀的key,这样redis在对key做hash时,会用括号里面的字符串计算hash值,只要保证括号里面的字符串相同就可以让不同的key落在同一个hash槽。这个特性redis称之为hash tags。

语雀连接:https://www.yuque.com/michael-qambz/igeais/aydau4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值