Redis(11):Redis-cluster的一些原理

一、redis的基础通信原理

1.redis采用的是gossip协议

    一般用来维护集群元数据的通信为集中式或者gossip,集中式是将集群元数据(节点信息、故障信息等)存储在某一节点上,所有点的节点都和这一个节点交换信息。而gossip是分散的,每个节点都在自己本地维护一份元数据,元数据的信息交换都是当前节点和其他的节点通信。画一个图加深一下理解:

集中式和gossip的对比

(1)集中式优缺点

        优点:元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 

        缺点:所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力;

(2)gossip的优缺点

        优点:元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;

        缺点:元数据更新有延时,可能导致集群的一些操作会有一些滞后。

2.+10000端口

        每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口。每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong。交换的信息主要是故障信息、节点的增加和移除,hash slot的信息等等。

3.redis-cluster如何使用gossip协议?

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

        meet: 当我们使用redis-trib.rb add-node命令的时候,实际上就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群,然后新节点就会开始与其他节点进行通信。

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

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

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

        redis-cluster发送ping很频繁,而且要携带一些元数据,所以可能会加重网络负担。每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点。当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长。比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率。每次ping,一个是带上自己节点的信息,还有就是带上1/10个其他节点的信息,发送出去,进行数据交换。至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息。

二、JedisCluster的一些工作原理

1.请求重定向

        我们之前使用redis-cluster存储数据的时候,体验到了redis-cluster的请求重定向,我们在命令行加上-c就会自动的去重定向。如果当前key对应的hash slot不在当前使用的master上,那么就会返回moved error。那我们使用重定向的话就会增加很多网络请求,所以基于重定向的客户端,很消耗网络IO,因为大部分情况下,可能都会出现一次请求重定向,才能找到正确的节点。Smart Jedis 做了对应的优化,他会在本地缓存维护一份hashslot -> node的映射表,大部分情况下,直接走本地缓存就可以找到hashslot -> node,不需要通过节点进行moved重定向。

        在JedisCluster初始化的时候,就会随机选择一个节点,初始化hashslot -> node映射表,同时为每个节点创建一个JedisPool连接池。每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点。如果那个node正好还是持有那个hashslot,那么就ok; 如果说进行了reshard这样的操作,可能hashslot已经不在那个node上了,就会返回moved error。如果JedisCluter API发现对应的节点返回moved,那么利用该节点的元数据,更新本地的hashslot -> node映射表缓存。重复上面几个步骤,直到找到对应的节点,如果重试超过5次,那么就报JedisClusterMaxRedirectionException错误。

2.hash slot迁移和ask重定向

        当hash slot正在迁移的时候,JedisClusterAPI如果要请求这个hash slot上的数据,那么就会返回一个ask消息给JedisClusterAPI,JedisClusterAPI就可以根据ask消息中的信息去到新的hash slot尝试请求数据。但是JedisClusterAPI本地缓存的hash slot-->node映射表是不会根据aks消息进行更新的,如果已经确定hash slot迁移完毕,JedisClusterAPI接收到moved才会更新本地缓存的hash slot-->node映射表。

三、高可用性与主备切换原理

redis cluster的高可用的原理,几乎跟哨兵类似。

1、判断节点宕机

        如果一个节点认为另外一个节点宕机,那么就是pfail,形成主观宕机(这一点和哨兵的sdown类似)。如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的odown原理几乎一样。在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail。如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail。

2、从节点过滤

        对宕机的master node,从其所有的slave node中,选择一个切换成master node。首先检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master。这个也是跟哨兵是一样的,从节点超时过滤的步骤。

3、从节点选举

        哨兵:对所有从节点进行排序,主要参考的依据:slave priority,offset,run id。

        每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master,然后从节点执行主备切换,从节点切换为主节点。

4、与哨兵比较

整个流程跟哨兵非常类似,redis cluster功能非常强大,直接集成了replication和sentinal的功能。

四、redis-cluter在linux上一些常见问题

1.fork耗时导致高并发请求延时

        当我们开启RDB和AOF的时候,Redis会fork一个子进程去生成RDB快照、AOF rewrite,这个过程是很耗费磁盘IO的。fork的时候,子进程需要拷贝父进程的空间内存页表的,所以很耗费时间。一般来说,如果父进程内存有1个G的数据,那么fork可能会耗费在20ms左右,如果是10G~30G,那么就会耗费200ms,甚至600ms,也就是几百毫秒的时间。我们使用info stats中的latest_fork_usec,可以看到最近一次form的时长。redis单机QPS一般在几万,fork可能一下子就会拖慢几万条操作的请求时长,从几毫秒变成1秒。而且如果每个redis实例的内存太大,在进行全量复制的时候也容易出现很多问题。

优化思路:fork耗时跟redis主进程的内存大小有关系,一般控制redis的内存在10GB以内。

2.AOF的阻塞问题

    redis将数据写入AOF缓冲区的时候会单独开一个线程每秒做一次fsync操作,但是redis主线程每次写的时候都会检查两次fsync的时间,如果距离上次fsync时间超过了2秒,那么写请求就会直接阻塞。everysec,最多丢失2秒的数据。一旦fsync超过2秒的延时,整个redis就被拖慢。

优化思路:优化硬盘写入速度,建议采用SSD,不要用普通的机械硬盘,大幅度提升磁盘读写的速度。

3.主从复制延迟问题

        主从复制可能会严重超时。我们可以在info replication中看到master和slave复制的offset,做一个差值就可以看到对应的延迟量。

优化思路:这个时候需要良好的监控和报警机制,如果延迟过多,那么就进行报警。具体可以采用shell脚本实现。

4.主从复制风暴问题

    如果你的一个master节点挂载了很多的slave节点,假如整个集群宕机了导致一下子让多个slave从master去执行全量复制,一份大的rdb同时发送到多个slave,会导致网络带宽被严重占用,很可能直接占满网络带宽。

解决思路:如果一个master真的要挂载多个slave,那尽量用树状结构,不要用星型结构。理解下图:


复制风暴问题的解决方案

关于redis linux一些内核参数的优化,我们可以先从redis的启动日志开始,reids会建议我们去做相关的一些优化,我们可以根据启动日志做一些优化。比如下图:


redis启动日志

5、vm.overcommit_memory

    0: 检查有没有足够内存,没有的话申请内存失败

    1: 允许使用内存直到用完为止

    2: 内存地址空间不能超过swap + 50%

如果是0的话,可能导致类似fork等操作时执行失败,原因是申请不到足够的内存空间。

查看指令:cat /proc/sys/vm/overcommit_memory

修改指令:echo "vm.overcommit_memory=1" >> /etc/sysctl.conf

sysctl vm.overcommit_memory=1

6、swapiness

cat /proc/version,查看linux内核版本

如果linux内核版本<3.5,那么swapiness设置为0,这样系统宁愿swap也不会oom killer(杀掉进程)

如果linux内核版本>=3.5,那么swapiness设置为1,这样系统宁愿swap也不会oom killer

保证redis不会被杀掉

echo 0 > /proc/sys/vm/swappiness

echo vm.swapiness=0 >> /etc/sysctl.conf

7、最大打开文件句柄

ulimit -n 10032 10032

8、tcp backlog

cat /proc/sys/net/core/somaxconn

echo 511 > /proc/sys/net/core/somaxconn

你可能感兴趣的:(Redis(11):Redis-cluster的一些原理)