当然一套成功的大型分布式系统仅有这些是不够的,还必须包括数据多副本复制策略以及分区算法等,也要有能应对复杂的现网运营环境的能力。我们结合各层的服务特点,制订了相对应的数据强一致算法,如内存层通过版本号控制来保证与存储层的完全一致,存储层通过 Paxos Group 实现多副本容灾,而机械盘层则通过串行写来保证。我们同时也实现了自己的去中心化的数据路由算法,确保了数据和流量的均匀分布,并且保证这种特性在横向扩展后依然成立。
为了系统的健壮性,一些异常情况也是需要考虑的,如果一台内存层机器突然离线,会有数十 G 的缓存数据失效,我们当然不会希望这数十 G 数据的压力,会全部的落到一台存储机器的磁盘上。――这无疑会引起系统的抖动。因此,我们按照组的方式来部署了内存层。每组有多台机器。一份数据可能在这多台机器内有多个副本。客户端通过随机的次序访问这些机器。这样就尽力避免了单结点失效对整个系统的影响。
经典的一致性哈希算法的初衷是为了健壮分布式缓存,基于运行时动态的计算哈希值和虚拟节点来进行寻址。数据存储与分布式缓存的不同在于,存储必须保证数据映射的单调性,而缓存则无此要求,所以经典的一致性哈希通常会使用机器 IP 等作为参数来进行哈希,这样造成的结果一方面是数据的落点时而发生改变,一方面是负载通常不均衡。因此我们改造了此算法。
组是数据分区的独立单元,是虚拟节点对应的实体单位。组之间是互相独立的。每组由多台物理机器组成,这是 Paxos Group 生效的基本单位。一份数据包括的多份副本分别散落在组内的各台机器上。为了在组内机器上保证负载均衡,我们同样设计了一套算法,规定了数据副本之间的访问优先级,前端会依优先级逐一的请求数据,只要成功获取,即中断这个过程。然后我们再将副本按优先级均匀的散落在组内机器上,如此即可实现组内负载的均衡。
因为每个数据都是根据键值单独进行路由的,如果要进行请求合并,我们就必须确保同一个批量请求内的数据,都会寻址到相同的 Paxos Group 上。因此,我们必须在内存层将落到同一台存储机器上的 Get 请求聚合起来。我们首先在内存层和存储层采用了相同的路由算法,然后将内存层的组数同存储层的组数进行对齐,来完成了这一目标。
然而它们的经验我们是无法彻底套用的,主要两种原因:我们可使用的机器机型是固定的,不存在自己定制硬件的条件。同时它处理的是照片这种大 value 的数据。而我们基本上是文本这种类型的小 value 数据。从前文提及的 TB 访问量角度来看,它们处理的数据是容量瓶颈的,而我们处理的是 IO 瓶颈的,可以算是不太冷的冷数据带来的挑战。所以,我们只能实现自己的冷数据管理策略。
同样,业界有诸多关于如何实现数据一致性的方案。包括我们微信自研的 Quorum 协议,它是一种 NWR 协议,采用异步同步的方式实现数据多副本。即然是异步同步,那在多副本达到最终一致,必然存在一个时间差,那么在单机出现离线的情况下,就会有一定概率导致数据的不可用。而我们追求的是在单点故障下,所有的数据都保证强可用性。
因此,我们采用了无主的去中心化的 Paxos Group 实现了这一目标,其中非租约是 PaxosStore 架构的一个创新亮点。在故障时通常系统是抖动的,会有时断时续的状况,常见的租约做法在这种场景下容易出现反复切换主机而导致长期不可用,而 PaxosStore 的非租约结构能够轻松应对,始终提供良好的服务。PaxosStore 核心代码正在整理开源当中,预计四季度会正式发布,同时该项目的底层框架也基于我们已开源的协程库 github.com/libco。