李 燚,顾乃杰,黄增士,任开新
(1.中国科学技术大学 a.计算机科学与技术学院; b.先进技术研究院,合肥 230027; 2.安徽省计算与通信软件重点实验室,合肥 230027)
Redis[1]是一种基于键值对存储的内存数据库,能够提供高速的数据库访问,并实现了复制备份和数据持久化的功能。相比于其他内存数据库,例如Memcached[2],Redis支持多种数据类型。由于具有高性能、多数据类型支持、数据安全性等优势,Redis近年来发展迅速,并得到了广泛应用。已经有很多互联网企业使用Redis,例如新浪、GitHub、Pinterest[3]等。Redis应用领域也很广泛,例如做动态缓存[4]、可视化数据库[5]、云数据库[6]等。
Redis集群能够弥补单线程的Redis节点性能上的不足,还可以提供数据冗余、结构冗余的特性。在Redis集群中,数据分布存储在各个节点,单点故障问题不可避免。发生故障后集群的容错机制决定了集群恢复的效率,也就影响到集群对外服务的可靠性。Redis集群现有的容错机制效率比较低,在实际使用过程中,当主要节点发生故障时,恢复过程缓慢,集群可靠性还有待提升。提升集群可靠性,一方面可以推动Redis的发展,另一方面可以降低集群故障给用户带来的损失。
影响集群可靠性的因素主要有集群架构、网络通信质量、软硬件可靠性、集群容错机制、数据恢复机制等。目前对于分布式集群的架构和可靠性的研究非常多。文献[7]提供一种在TCP连接层对集群可靠性进行优化的方法。通过使用冗余的TCP栈实现套接字迁移,来保证服务端节点的TCP连接的可靠性。这种方法只是在网络连接层降低了发生单点故障的概率,如果是因为其他原因导致节点故障,仍然需要通过容错机制进行集群恢复。文献[8]研究了数据冗余和硬件冗余对集群可靠性的影响,并提出一种结合了节点互备份和冗余节点备份的恢复机制,其中冗余节点备份类似于Redis的主从模式。文献[9]介绍一种新的复制备份方案NRDT,该方案通过在邻接点间进行数据备份来提高集群可靠性,即一个节点的服务数据复制备份到邻居节点上。但是发生多节点故障时,这种冗余方法容易导致个别节点的负载较重,影响集群的性能。文献[10]实现了针对UCWW系统的Redis集群架构。该架构使用ZooKeeper对Redis节点进行管理,需要的ZooKeeper节点个数与Redis节点个数相关。该集群方案需要额外的监测节点,而且架构的设计针对于UCWW系统,不具有通用性。
相比于现有的研究方案,本文对Redis集群可靠性的研究和优化集中在通信层。通过对Redis集群的容错机制进行阶段划分,针对各个阶段进行分析。基于对各阶段通信过程的研究,提出一种集群节点间的通信模型,并在此模型基础上,实现一种通信负载均衡的优化方法。
Redis节点可以主从方式构成集群,从节点对主节点进行复制备份,相当于一种冗余结构[11]。集群节点两两之间建立了P2P的TCP连接,以发送和接收消息的方式进行通信,构成全网状的网络拓扑结构[1]。图1是6个节点组成的Redis集群,最小的Redis集群只有3个主节点。作为一种分布式的结
构,Redis集群中实现了一种基于Gossip协议的一致性通信算法,来完成消息的消息传播、数据交换和状态同步[12]。使用Gossip协议既能有效保证集群的一致性,又避免了通信负载指数级的增长。
图1 6个节点组成的一主一从Redis集群
Gossip协议是在大规模并行环境内部建立稳定、可靠的通信机制的一种有效方案,广泛地应用到P2P网络中。在Gossip协议中,节点以一种类似疫情传播的方式进行数据交换、消息通信[13]。Gossip协议是一种最终一致性算法,即弱一致性算法,不能保证在某个时间点整个集群的所有节点达到一致的状态,但可以保证随着时间的推移,集群最终会达到一致性的状态。
在集群运行过程中,如果存在主节点因为软硬件原因离线,需要通过容错机制来确定离线的主节点,并进入故障转移过程来指派新的主节点。如图2所示,下线检测和故障转移是集群容错机制的2个主要阶段。如果按照节点状态和集群状态进行划分,又可分为PFAIL阶段、FAIL阶段和RECOVER阶段。PFAIL和FAIL来源于节点的状态,分别对应疑似下线和下线。
图2 容错机制下的宕机恢复过程
1.2.1 下线检测
集群中的每个节点和其他节点进行周期性的间隔通信。这种周期性通信消息,即心跳消息在集群间传递节点状态以及可用性的信息[14]。周期性通信的方法记为F,执行周期记为HZ。Redis集群中心跳消息主要包括PING、PONG,一次PING、PONG通信简化过程的等待超时时间记为T。
发送方节点通过以下2种方式选取需要发送PING消息的节点:
1)每隔1 s随机性地选取一个节点,向其发送心跳消息。
2)每次执行F,一次性选出上次通信距现在超过T/2的节点,向这些节点发送心跳消息,从而保证每隔T/2的时间都和其他节点通信一次。
每条心跳消息(PING、PONG)包含发送方节点的状态信息和GossipSection。GossipSection包含若干条Gossip数据,每条Gossip数据对应一个Redis节点,包含该节点的状态信息,即发送方节点将集群中若干节点的信息随心跳消息发送给接收方。GossipSection中节点的选取具有随机性。如图3所示,接收方收到PING、PONG消息后,首先解析消息提取发送方的信息进行更新,然后解析GossipSection中的每条数据,更新到本地。
图3 节点间PING、PONG通信过程
如图3发送PING消息后,在超时时间T内未收到PONG回复,就将对应节点标记为PFAIL状态,对应图2的PFAIL阶段。节点间不断进行心跳消息的通信,随着时间的推移,在节点间对于PFAIL状态节点的判断逐渐形成一致性的认识,在此过程中如果超过半数主节点都标记某个节点为PFAIL,就确定该节点下线,对应图2的FAIL阶段(GossipSection中解析的PFAIL标记有2T的有效时间,超时后不能用来判断FAIL状态)。所以FAIL状态是集群中多数节点协同得到的一致性结果。通过PFAIL阶段和FAIL阶段就完成了下线检测的过程。
1.2.2 故障转移过程
故障转移是在主节点故障后进行主从切换的过程[15]。在通过下线检测过程确认主节点宕机后,其从节点就会通过Raft选举算法获得多数主节点的认可,当选为新的主节点。首先从节点在集群中发起选举进入故障转移的过程。选举信息在集群中广播,收到选举信息的主节点给该从节点投票,当从节点得到的票数超过主节点总数一半时,可当选为新的主节点并广播当选信息,集群恢复上线。
根据对集群结构和集群容错机制的分析,Redis集群有以下3点优势:
1)多冗余功能:Redis的主从集群结构,给自身提供了硬件和结构冗余的功能,从节点对主节点的复制备份的功能提供了数据冗余的功能。
2)自动容灾:集群发生故障时,在集群内部能够自动完成下线检测、故障转移的过程,不需要额外的监控节点协助完成。
3)一致性保证:在容错机制中使用Gossip通信协议维持集群状态的一致性,保证下线检测的准确性。在故障转移过程中,使用Raft一致性算法,保证主从切换的唯一。
通过对Redis集群容错机制中节点通信过程的分析,考虑在集群中对于PFail状态进行疫情传播的通信模型,可结合疫情传播的易受感染(Susceptible Infective,SI)模式进行分析。假设集群的主节点个数为N,将集群中的节点分为2种:1)感知态,感知态节点已经知道PFail状态节点,下一步需要进行传播;2)未知态,未知态节点没有感知PFail状态节点。
初始时只有一个感知态节点,每次随机选取一个节点进行状态传播,记为一轮。则在一轮过后,增加一个感知态节点的概率为1-1/N。在下一轮,所有感知节点都分别再进行一轮状态传播。传播过程记为{I(k),k≥0},I(k)表示k轮传播后感知态节点的个数,其中I(0)=1。
(1)
使用二项式定理和组合数公式简化后得到下式:
(2)
解二次递推公式得到下式:
(3)
式(3)给出了在k轮消息传播后感知态节点的个数。当感知态个数达到N时,集群达到一致状态;当感知态个数达到N/2+1时,能够完成下线检测。对于后者,根据式(3)得到PFail状态传播轮数k:
(4)
在上述通信模型中,k是状态传播经过的轮数,每次传播一个节点。对于每次状态传播发送多条消息的情况,按照上述过程难以得到简化结果。可以简化考虑:将发送的每条消息看作状态传播的一轮。对Redis集群,假设消息发送频率为k′,则感知态个数达到N/2+1的耗时为t=k/k′。
根据第1节对集群通信过程的介绍,假设集群通信消息发送频率为m,由于心跳消息的GossipSection包含PFail状态有一定随机性,假设包含PFail状态的概率为p(0
(5)
所以按照一条心跳消息作为一轮状态传播的过程考虑,PFail状态传播N/2+1个节点时,集群能够完成下线检测,耗时t和mp成反比关系。而结合第1节对通信过程的分析,存在以下问题:
1)节点发送心跳消息的频率m较低。由目标节点的选取和发送消息的时机分析,理论上每隔T/2的时间,会有一次心跳消息集中发送的现象发生,产生消息发送的峰值点,非峰值点的消息发送频率比峰值点要低很多,导致消息通信在时间上的负载不均衡。这种不均衡也通过测试结果得到了验证。
2) GossipSection节点选取的随机性导致p较小。在发送的心跳消息中,添加到GossipSection的节点的选取有随机性,不能保证PFAIL状态的节点添加到GossipSection,降低了有效PFail状态的心跳消息的发送频率。
所以根据以上分析,可以从两方面对Redis集群容错机制的FAIL阶段进行优化:1)通信负载均衡,既不增加通信负荷,又能提高非峰值点的消息发送频率m;2)GossipSection节点的选取,优先添加PFAIL状态的节点,增加心跳消息中有效消息占的比例p。
根据第1节的介绍,原始的心跳消息发送过程可以分为2个部分:周期定量发送,T/2时刻批量补发送,导致心跳消息的发送在时间上分布不均。通过使用通信负载均衡的方法可以消除峰值,增加非峰值点的发送频率。
简化消息发送过程,实现负载均衡,要考虑以下3个问题:
1)节点心跳消息的通信量在时间上均匀分布,保证通信负载均衡;
2)既要保证集群节点之间的通信量:在T/2时间内单个节点和其他节点至少通信一次,又不能增加通信负载,对集群性能产生太大影响;
3)避免和某些节点产生较长的通信空白,所以需要按照一定的优先级调度通信节点。
假设在T/2时间内与其他所有节点通信次数为S,为了满足负载均衡的目的,可以用减量均分的方法动态计算每次执行周期函数F时的消息发送量。设F周期为HZ,则T/2时间内执行次数为K;设第i次执行F发送消息量记为pi(p0=0),在T/2时间内,每次执行F,需要根据剩余消息量重新计算pi。
(6)
(7)
为了避免同其他节点产生通信空白,使用优先级队列保存所有节点,优先级设置为与该节点上次通信距离现在的时间,时间越长优先级越高,可以保证优先选取的目标节点是最久没有通信过的。在集群建立时初始化2个优先级队列Q1和Q2,Q1是待发送消息的目标节点,Q2是已发送消息的节点。每次从Q1队列头选取目标节点,发送消息后放入Q2队列尾。每隔T/2,Q1和Q2完成一次交换。在集群规模发生变化时,也很容易通过更新Q1更新集群通信量。
负载均衡的消息发送过程如下所示:
1)集群建立时,初始化队列Q1为空,Q2包含所有节点,初始化计数器i=1;
2)如果Q1为空且i=1,交换Q1和Q2;
3)结合式(6)计算消息发送量pi=Q1.length/(K-i+1);
4)从Q1中弹出pi个节点,发送心跳消息,并将节点压入Q2,i=(i+1)%K;
5)周期函数下一轮从2)继续执行。
通信过程具体实现如下:
1.//集群启动时初始操作
2.i←0;
3.K←(T*HZ)/2 000;
4.InitPriorityQueue(Q1,null);
5.InitPriorityQueue(Q2,cluster_nodes);
6.…
7.//以下是循环过程
8.if Q1.empty() & i == 1 then
9. Swap(Q1,Q2);
10.end
11.size←Q1.length;//剩余节点数
12.pi←size/(K-i+1);
13.i←i % K+1;
14.for j=1 to pi do
15. node←Q1.pop();
16. if node.ping_sent = =0 then
17. sendPingToNode(node);
18. end;
19. Q2.push(node);
20.end
第11行~第12行结合式(3)根据F执行次数和Q1的长度更新需要发送的消息个数pn,保证在T/2时间内和所有节点通信一次。第14行~第20行完成节点选取和发送消息的过程,第16行筛选掉已经发送的心跳消息,但没有收到回复也没有超时的节点,防止消息堆积。
原始的心跳消息GossipSection包含节点的选取具有随机性。针对这个问题,改进Gossip单元选取过程,将处于PFAIL状态的节点优先添加到Gossip单元中,根据式(2),此举的目的是为了增加p的值。节点选取过程较简单,主要需要考虑以下问题:
1)消除随机性,绝对优先级选取PFail状态的节点;
2)确定GossipSection的大小,使尽快完成节点状态交换。
在集群中添加额外数据结构PFailNodes,使用自带的字典结构实现,保存所有PFAIL状态的节点。在更新节点状态时,同步地更新PFailNodes。在添加PFail节点时,可以直接从PFailNodes中选取。
不考虑优先添加节点的情况,设添加到GossipSection的节点个数为n。对节点的PFAIL标记的有限期限为2T,在2T的时间内,能和单个节点完成4次PING/PONG通信,共8条消息。设集群大小为N,如果要在2T的有效时间内完成对所有节点的状态信息交换,就需要在每条信息里添加N/8个节点的GossipSection数据,即n=N/8。
相对于消息通信量,GossipSection的增加对通信负载的影响小得多,所以在优先添加PFail节点的情况下,可以适量地放宽n的值。设GossipSection的大小为n+PFailNodes.size(),后者是PFail状态节点的个数。每次构造心跳消息选取GossipSection节点的过程如下:
1)添加PfailNodes中所有节点到GossipSection;这样能够保证发送的每条心跳消息都包含PFail节点,即p=1,达到最大值;
2)在剩余节点中,随机选取n=N/8和节点添加到GossipSection,并保证GossipSection节点互异。
具体实现为:n是添加到GossipSection的非PFAIL状态的节点个数,gossipcount是已经添加的节点个数,msg是构造的消息。先遍历PFailNode,将其中节点添加到GossipSection。然后随机选取n个节点添加到GossipSection,并保证GossipSection中节点的不重复。
1.n←Max(N/8,3);
2.gossipcount←0;
3.for i=1 to PFailNodes.size() do
4. node←PFailNodes[i];
5. AddNodeToGossipSection(msg,node);
6. gossipcount←gossipcount+1;
7.end
8.for i=1 to n do
9. node←getRandomNode();
10. …
11.for j=1 to gossipcount do
12. if node = msg→gossipsection[j] then
13. continue;
14. end
15. end
16. AddNodeToGossipSection(node,msg);
17. gossipcount = gossipcount+1;
18.end
测试的Redis版本分别是Redis-3.0.7稳定发布版本以及在Redis-3.0.7上的优化版本,本文中原版、优化前版本都是指Redis-3.0.7。测试用到2台物理主机,每台有48核、256 GB内存,CPU型号为Intel(R) Xeon(R) CPU E5-2690 v3@2.60 GHz;测试集群规模为30个主节点、一主两从,共90个节点。为了对优化效果进行验证,以及说明优化后产生很小的通信负载,而且这部分通信负载的产生对集群的性能不会产生明显影响,分别从以下3点进行测试:1)集群宕机恢复过程的耗时对比;2)优化前后单个节点对其他节点的通信量对比;3)优化前后集群的吞吐量和操作延时的对比。
集群恢复时间的测试能够体现出集群对于故障宕机的处理效率,能够体现集群处理故障的可靠性。本文将集群宕机恢复过程分为3个阶段进行测试,即PFAIL阶段、FAIL阶段、RECOVER阶段,各阶段耗时依次记为t1、t2、t3。阶段划分参考图2。
宕机恢复总时间=t1+t2+t3。t1≈T(默认15 000 ms),t3和节点选取过程有关。因为优化方案主要针对于FAIL阶段,所以为了测试优化后宕机恢复过程效率的提升,在测试集群中人为宕机1个~14个主节点(超过主节点总数一半的话,不能恢复),分别对优化前后的FAIL阶段耗时t2和恢复总时间进行对比分析。
测试结果对比如图4和图5所示。由图4可以看出,优化后的Redis集群在FAIL阶段的效率有很大提升;相对于原版的Redis集群,FAIL阶段宕机判断的效率提升了80%以上,优化效果很明显。图5显示了FAIL阶段的优化对于宕机恢复整体过程的影响,优化后的集群在宕机恢复的效率即恢复总时间上,相比于原版有28%以上的提升。
图4 FAIL阶段耗时对比
图5 宕机恢复总时间对比
本文提出并实现的优化方案对节点发送心跳消息的过程进行优化,使得消息发送的过程在时间上均匀分布,达到负载均衡的目的,避免消息发送过程中出现周期性的峰值。通过提取消息发送日志,对优化前后单个节点在60 s内的消息发送过程进行统计,结果如图6所示。由图6对比可以看出,优化后的集群节点在发送消息时在时间上的分布均匀,没有出现明显的峰值。
图6 消息发送时间分布对比
通过对图6中60 s内的消息发送总量进行统计发现,优化前发送消息总量为717条,优化后为723条,通信负载增加幅度在0.84%左右,通信负载的增加可以忽略。
使用redis-benchmark对Redis集群的常用操作命令进行测试,测试指标包括集群执行每条操作的时延以及集群的吞吐量(QPS:每秒钟处理的请求数)。分别对每种操作在优化前后的时延(ta和tb)、吞吐量(qa和qb)进行对比,计算时延、吞吐量的变化Δt和Δq,结果如表1、表2所示。其中,Δt=(ta-tb)/ta,Δq=(qb-qa)/qa。
表1 优化前后时延对比
表2 优化前后吞吐量对比
根据表1和表2的数据可以看出,通过redis-benchmark测试得出的时延和吞吐量,优化前后对比性能有升有降,变化幅度大小不一。其实原始版本Redis集群的吞吐量和时延在进行测试时,也会在一定范围内波动。在相同的测试条件下,表1和表2体现出优化后的Redis集群的性能波动,在可接受的误差范围之内,所以总体来说对集群的吞吐量、时延的影响可以忽略不计。
本文通过对Redis集群通信层进行研究,对集群间基于心跳消息的通信过程进行优化,提出了一种适用于大规模集群的消息传输模型。通过通信负载均衡的方法以及对消息的Gossip字段的优先级选取,在不带来额外的通信负载、不影响集群性能的情况下,提升了集群宕机恢复过程的效率,使得Redis集群宕机恢复过程的效率有了28%以上的提升,并增强了Redis集群的可靠性。
Redis集群只能在宕机主节点数少于主节点总数一半时才能从故障中恢复,这是由Redis本身实现的选举算法决定的。下一步工作是通过对选举算法进行分析改进,使得多数主节点故障宕机时集群也能够成功恢复,提升Redis集群对宕机节点数目的容忍度。
[1] Redislabs.Redis cluster specification[EB/OL].[2017-03-13].http://redis.io/topics/cluster-spec/.2016 April.
[2] FITZPATRICK B.Distributed caching with memcached[EB/OL].[2017-03-13].http://www.linuxjournal.com/article/7451.
[3] SHARMA V,CARROLL J,KHUNE A.Scaling deep social feeds at pinterest[C]//Proceedings of IEEE International Conference on Social Computing.Washington D.C.,USA:IEEE Press,2013:777-783.
[4] CIDON A,EISENMAN A,ALIZADEH M,et al.Dynacache: dynamic cloud caching[C]//Proceedings of the 7th USENIX Conference on Hot Topics in Cloud Computing.New York,USA:ACM Press,2015:19.
[5] 焦 健,李 岩.基于Redis的SVG空间信息可视化数据库[J].小型微型计算机系统,2015,36(6):1193-1198.
[6] 阿里云.云数据库Redis版[EB/OL].[2017-03-13].https://help.aliyun.com/document_detail/26342.html.
[7] SHAO Zhiyuan,JIN Hai,CHEN Bin,et al.HARTs:high availability cluster architecture with redundant TCP stacks[C]//Proceedings of IEEE International Conference on Performance,Computing,and Communications.Washington D.C.,USA:IEEE Press,2003:253-260.
[8] BASSEK C K,PIERRE S,QUINTERO A.Redundancy schemes for high availability computer clusters[J].Journal of Computer Science,2006,2(1):33-47.
[9] DERIS M M,RABIEI M,NORAZIAH A,et al.High service reliability for cluster server systems[C]//Proceedings of IEEE International Conference on Cluster Computing.Washington D.C.,USA: IEEE Press,2003:281-288.
[10] JI Zhanlin,GANCHEV I,O'DROMA M,et al.A distributed Redis framework for use in the UCWW[C]//Proceedings of International Conference on Cyber-enabled Distributed Computing and Knowledge Discovery.Washington D.C.,USA:IEEE Press,2014:241-244.
[11] 黄健宏.Redis设计与实现[M].北京:机械工业出版社,2015.
[12] KERMARREC A M,VAN STEEN M.Gossiping in distributed systems[J].Acm Sigops Operating Systems Review,2007,41(5):2-7.
[13] 刘德辉,尹 刚,王怀民,等.分布环境下的Gossip算法综述[J].计算机科学,2010,37(11):24-28.
[14] ROBERTSON A.Linux-HA heartbeat system design[C] //Proceedings of the 4th Annual Linux Showcase & Conference.New York,USA:ACM Press,2000:20.
[15] 张小芳,胡正国,郑继川,等.高可用性集群技术的研究和应用[J].计算机工程,2003,29(4):26-27.