Redis Cluster 3.0

目录

一:集群架构

二:Client 请求重定向

三:集群节点通信

四:集群高可用&主备切换

五:衡量分布式系统指标(在此指集群)

可用性

可扩展性

一致性

六:集群不可用条件

七:扩容和缩容

扩容

缩容

八:目前市面上redis集群的架构

直连cluster(直连架构)

Codis架构(代理架构)

Twemproxy架构(代理架构)

九:分布式架构拓展

分布式系统中的数据分布方式

hash分区(该数据分布就是redis cluster3.0的数据分布实现,一种非常常见的数据分布方式)

range分区

数据量分区

副本的数据分布

一致性hash

本地化计算数据分布

工作中常见的应用场景

十:思考



一:集群架构

直接上redis cluster架构图(理解集群的重点在于理解其分布式架构以及其中分布式原理)

Redis Cluster 3.0_第1张图片

之前讲过哨兵,在此直接抛出一个观点:redis cluster = replication + sentinal

官方3.0集群拥有以下特点:

1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽(gossip通讯)。

2、节点的fail是通过集群中超过半数的节点检测失效时才生效。

3、客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

4、redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value。

5、Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。

6.去中心化,去中心化指的上述图片中节点平等无主次之分,中心化和去中心化也是分布式思想中一个非常重要的理论。

7.生产环境中,每个蓝色节点下面会挂1~2个slave,便于高可用,写数据是通过master写入,异步同步到slave节点之上。

8.集群至少3节点,过半可用,raft选举。

9.没有没有没有使用哨兵,但是还是使用哨兵的那一套原理实现高可用。

二:Client 请求重定向

首先,在此抛出来一个问题,client怎么才能知道我的这个请求该打在哪个节点上面?

假设我们有三台节点,分别为a,b,c,其中a负责0~5000的slot,b负责5001~10000slot,c负责10001~16383的slot。

如果一个请求的key所对应的slot是6000,隶属于节点是b管辖,client怎么才能知道我的请求应该打在b节点上面,在此涉及到client请求的重定向,首先client在刚一开始并不知道这个请求该打在b节点上,client会随机打在a,b,c其中的一个节点上,如果打在b节点上,那么b节点发现这个key所对应的slot是数据自己管辖范围,直接处理掉,如果随机打在了a节点上,a发现这个key对应的slot不是自己负责的,会像client返回一个moved错误,该信息中包括了该key负责的节点ip信息,然后client通过该ip把请求重定向到该节点上。

在此,我们再抛出来一个问题,a节点怎么知道这个key所对应的slot隶属于b管辖?带着这个问题,我们去看集群中的节点通信。

三:集群节点通信

还是直接先抛出来一个问题:redis集群有没有元数据节点,比如:hdfs集群有namenode元数据管理,kafka有zookeeper元数据管理,什么是元数据,好比集群中的节点信息,集群中节点所负责的slot。

redis通过gossip协议去进行数据的交换,如节点所负责的slot,节点对应的ip和port,该交换是陆陆续续的,不是集中式的管理,通过这种手段也是可以让节点中互相知道彼此的数据信息,通过这种信息交换方式,当然这种方式也有一定的弊端,一是同步时间不确定,二是gossip协议在集群过大会信息交换传递压力,这也是redis cluster为什么把slot数量设置成16384的原因,到现在,我们可以解答上面提出来的“a节点怎么知道这个key所对应的slot隶属于b管辖?”的问题。

使用元数据节点有什么弊端?

1.元数据节点容易形成单点,需要做高可用,否则,一旦元数据节点挂掉,会直接导致该集群不可用。

2.元数据节点数据比较大的时候,会对元数据节点造成压力,比如hdfs的namenode会在文件快过小导致元数据庞大的时候对元数据节点产生大的压力,hdfs官方也已经在优化namenode压力过大的导致的问题了。

四:集群高可用&主备切换

之前说过redis集群使用redis哨兵去做集群的高可用,官方的3.0 cluster并没有使用哨兵去做高可用的主备切换。

回看我们一开始给出来的redis的架构图,图中蓝色的节点都是主节点,该节点下面在生产环境中会有若干个slave节点,当master挂掉之后,slave切换成master继续提供服务。

说一下master选举流程:和哨兵选举几乎是一摸一样,名副其实的换汤不换药,只不过原本由哨兵的选举变成了集群中的master节点。

还是有主观宕机和客观宕机不过不再由哨兵去管理,而是集群中的节点,和哨兵几乎一致,不再重复。

确定客观宕机后开始master的选举,流程如下:

一:检查slave和master断开的时间,超时的没有资格成为master节点。

二:优先级(slave priority)越高,越优先成为master

三:其次是offset越大的,就是同步数据最多的,最先成为master

四:再其次就是runid最小的(最先启动的)成为master。

依然是raft选举,其实好多分布式系统的设计都离不开raft,redis/etcd/zookeeper 其核心原理都是raft,设计raft目的就是为了简化paxos的难以理解,更方便学术界和工业界的使用,附上raft汉化论文,还是比较容易理解的,之所以比paxos容易理解是因为作者把raft分成了leader选举,日志复制,安全,成员变更几个模块。

注:哨兵其实就是raft中的leader选举模块的一个实现。

五:衡量分布式系统指标(在此指集群)

衡量分布式系统指标还是离不开cap理论,还是要围绕着cap去衡量。

可用性

集群拥有主备切换功能,当master挂掉之后,由的slave充当新的master,继续提供服务,master挂掉后,无slave,该节点管辖的的slot数据失效。

可扩展性

可扩展性指的当用户量上来之后,伴随而到的就是集群规模的扩大,redis cluster中如果新增节点,就要考虑到新增节点带来的数据迁移复杂度以及其间的迁移时候的集群可用性,redis cluster做的比较简单明了:当你新增一些实例的时候,只需要将一部分槽位迁移到新的实例即可。在迁移的过程中,客户端会先去旧的实例上去查询数据,因为迁移正在发生,如果对应的数据还在本机上,那么直接返回,否则返回让客户端重定向到新的实例

一致性

不保证强一致性,因为异步复制&网络分区可能存在数据丢失,在讲哨兵时候也说过该问题。

六:集群不可用条件

一):过半master挂掉(选举就会有问题)

二):某一个master和下面的slave全部挂掉(请求可能会打到挂掉的机器)

七:扩容和缩容

扩容

Redis Cluster 3.0_第2张图片

缩容

Redis Cluster 3.0_第3张图片

八:目前市面上redis集群的架构

直连cluster(直连架构)

官方默认的连接方式,缺点:存在 MOVED 和 ASK 转向(请求重定向会增加 IO 开销)

Redis Cluster 3.0_第4张图片

 

Codis架构(代理架构)

Redis Cluster 3.0_第5张图片

 

Twemproxy架构(代理架构)

推特开源,也是加了一个代理,Twemproxy是一种代理分片机制,由Twitter开源。Twemproxy作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis服务器,再原路返回。

后端依赖于原生的 redis 节点与 redis-sentinel。

缺点:proxy存在单点,扩容问题

Redis Cluster 3.0_第6张图片

 

其实redis集群分两大类,就是直连和代理模式,试想下,我们可以通过代理做一些什么操作?

1.大key统计。

2.热key搜集

3.路由控制,避免直连的moved/ask转向。

九:分布式架构拓展

redis集群是分布式的,redis仅仅是应用分布式理论的一个应用,了解分布式系统原理&设计是至关重要的,了解分布式原理,可以很快的掌握业界中常见的kafka,etcd,zookeeper,redis,hdfs,spark等基础工具的设计原理,分布式原理都是通用的,对于redis集群最主要的是数据的分布方式,需要一种算法去精确的判断redis的key落在哪个具体节点上,并且考虑到节点的变更扩展是否容易,本文简单说一下分布式系统中的数据分布方式,后续大家如果对分布式原理感兴趣,会专题去讲解分布式系统原理。

分布式系统中的数据分布方式

hash分区(该数据分布就是redis cluster3.0的数据分布实现,一种非常常见的数据分布方式)

我们可以把hash分区想像成一个巨大的hash表,每一台机器都是一个bucket,hash分区就是按照数据的某一纬度计算该纬度的hash值,通过计算出来的hash对节点取模计算判断落在哪些节点上面

优点:理想情况下,hash方式可以达到理论上的数据均分,元数据非常简单,就是hash和机器数量取模的结果。

缺点:

(1)数据分布可能存在数据倾斜,毕竟可能有些情况并不是很理想,redis集群也存在这样的问题

  (2) 横向扩展比较繁琐,涉及到大量的数据迁移,所以hash分布方式往往如此涉及到成倍的增加 节点,这样可以减少数据迁移的成本,实际只需要一半的数据被迁移即可。

应用举例:如kafka的producer产出来的数据也是通过对key取hash然后对partition 数量取模运算定位到需要写入的partition,如果没有指定key,kafka会使用atomicinteger生成一个线程安全的数字,通过该数字和partition数量取模运算定位到partition,然后后续没有key的会在此数字上调用atomicinteger 的 incrementandget去自增然后去计算。

range分区

比较简单,如图:

Redis Cluster 3.0_第7张图片

优点:根据业务需求纬度分,可以灵活的根据数据量的具体情况拆分原有数据区间, 拆分后的数据区间可以迁移到其他机器,一旦需要集群完成负载均衡时,与哈希方式相比非常灵活

缺点:规模庞大需要维护元数据,或者还需要维护元数据服务器。

数据量分区

每个节点上的数据量大小是一致的,比如hdfs的文件块,默认是64m。

优点:可以达到解决均分,不涉及到数据倾斜,如spark/mr默认的数据计算都是基于hdfs的文件块去做task分配的,一般不涉及到由于数据倾斜带来的数据计算木桶效应。

缺点:维护大量元数据,如:hsfs要求文件快不要过小,过小的话会涉及到大量的元数据维护,会给namenode带来很大的压力。

副本的数据分布

有三个数据段,o,p,q,在需要有副本的情况下,数据分布如图:

Redis Cluster 3.0_第8张图片

典型的例子是kafka的副本分区就是该设计的一个的实现。

优点:数据副本的分配可以保证节点在挂掉之后可以由其他节点提供服务。

缺点:如o副本所在机器全部挂掉之后就要考虑可用性和一致性的抉择。

一致性hash

redis官方集群出来之前,一致性hash就是redis集群的一种实现方式,网上资料较多,一致性hash在此不再深入。

本地化计算数据分布

说到mapreduce和spark大家应该都不陌生,这些是大数据计算工具,大数据就涉及到大量数据的分布式存储,mr和spark都会把一任务split成一个个的小任务取机器节点上取执行,最后再reduce,在此涉及到移动数据和移动计算的一个问题,我们可以把task分配到数据所在机器相关的节点上(移动计算),也可以把数据移动到任务所在节点上(移动数据),很明显数据过大的时候移动数据很明显的网络瓶颈,所以我们通常会把计算移动到数据所在节点上,这是分布式一种很重要的思想“移动数据不如移动计算”。

工作中常见的应用场景

1.如mysql在做分表的时候,很常见的一种策略就是通过userid%表的数量(hash分区)去定位到该用户的数据所在的表名字。

2.冷热数据分离,如mysql两张表,mydata, mydata_del,被删除的数据会单独被放在mydata_del里面,长时间的业务迭代会造成数据库中会有大量的冷数据(被删除的),一是影响查询效率,二是占用数据库大量的的磁盘空间,还有可能造成数据库的扩容,该数据的分布方式就是range分区,未被删除的存放于mydata,删除数据存放于mydata_del。

十:思考

Q:为什么官方的3.0集群规定只能有16384个槽位?

A:首先需要明白key针对槽位的分配方式为:redis使用crc16(key)% 16384 算法计算出来key归属于哪一个槽位 , crc16算法会产出65536个数字(2的16次方),在此为什么要对16384取模运算,我们先讨论如果不取模运算,假设拥有65536个槽位,redis集群节点需要相互交换信息,详见标注的消息体结构,一个char占用1个字节,如果槽数量为16384,那么char数组的大小为:16384➗1024➗8 = 2kb,

如果槽数量为65536,那么char数组的大小为:65536➗1024➗8 = 8kb,节点和节点之间的发送节点的负责槽信息量会是原来的4倍,浪费带宽,作者不建议redis官方集群节点数量大于1000,16384个槽对于1000个节点足够使用了。没必要搞到65536个。

所以得到结论:

(1):65536比16384更加浪费带宽

(2):16384可以满足不超过1000节点的集群

(3):Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低

Redis Cluster 3.0_第9张图片

#define CLUSTER_SLOTS 16384


typedef struct {
    char sig[4];        /* 签名“RCmb”(Redis群集消息总线) */
    uint32_t totlen;    /* 消息的总长度 */
    uint16_t ver;       /* 协议版本,目前设置为1 */
    uint16_t port;      /* TCP基本端口号r. */
    uint16_t type;      /* 消息类型 */
    uint16_t count;     /* 仅用于某种消息 */
    uint64_t currentEpoch;  /* 相应于发送节点的纪元 */
    uint64_t configEpoch;   /* 如果是主服务器的配置纪元,或者如果它是从
                                                         服务器则由其主服务器通告的最后一个纪元 */
    uint64_t offset;    /* 如果节点是从属节点,则节点是主节点或已处理的
                                                 复制偏移量时,主复制偏移量 */
    char sender[CLUSTER_NAMELEN]; /* 发件人节点的名称 */
    unsigned char myslots[CLUSTER_SLOTS/8]; /* 发送节点负责的槽信息 */
    char slaveof[CLUSTER_NAMELEN]; /* 如果发送节点是从节点,记录对应主节点的nodeId */
    char myip[NET_IP_STR_LEN];    /* 发件人IP,如果不存在则为零 */
    char notused1[34];  /* 34个保留字节供将来使用 */
    uint16_t cport;      /* Sender TCP集群总线端口 */
    uint16_t flags;      /* 发件人节点标志 */
    unsigned char state; /* 来自发件人POV的集群状态 */
    unsigned char mflags[3]; /* 消息标志:CLUSTERMSG_FLAG [012] _... */
    union clusterMsgData data; /* 群集消息数据 */
} clusterMsg;

你可能感兴趣的:(redis)