暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

tendis 集群水平扩缩容实现

腾讯游戏存储与计算技术 2021-05-28
2432
作者 陈悟,腾讯游戏云存储工程师,主要从事分布式存储和数据库方面的研究与开发, 2019 年加入腾讯, 期间一直负责Tendis 存储版分布式cluster功能的开发。

tendis集群类似于redis cluster的分布式实现,如下图所示, 所有节点通过gossip协议通讯, 主从之间使用binlog同步数据。

和 redis cluster的cluster分片逻辑类似,tendis集群创建时会将16384个slots分配到每个master nodes, 通过哈希算法来决定写入的数据具体路由到哪个master. 用户通过该redis RESP协议访问集群,可指定hashtag来控制数据分布和访问。

这篇文章主要介绍节点之前的节点之间 migrating(数据搬迁)能力. 通过这个能力能够让tendis集群支持增删节点,数据可以按照slot在任意两节点之间迁移,扩容和缩容过程中对应用运维人员透明。

扩缩容需求

如上图所示,随着数据规模的不断增多,集群的容量和性能不能满足用户的需求,那么就需要进行扩容,扩缩容的本质是数据搬迁,如上图所示,node1, node2和node3分别将其拥有的slot1, slot2 和slots迁移到node4节点。

这里的迁移slot1有三个含义:

(1) 在源节点中node1里存在slot1的数据需要迁移到目标节点

(2) 在搬迁完成后,所有写入和读取slot1的请求需要自动路由到目标节点而不能再写入到原节点,路由切换的过程要保证数据一致性

(3) 所有其他集群节点能够感知到 slot1现在已经归属于目标节点,达成共识

搬迁过程技术难点

1. 如何在搬迁过程中做到用户无感知

在搬迁过程中需要保证用户正常读写请求,这里一般有两种处理方式:

一种是制定一种协议保证请求能够路由到正确的节点,搬迁过程中数据可能在源节点,也可能在目标节点,例如redis cluster的ASK协议,在redis cluster中迁移是原子的,由于migrate命令是同步阻塞的(同步发送同步接收),迁移过程中会阻塞该引擎上的所有key的读写,只有在迁移响应成功之后,才会将本地的key删除,这个过程使用ASK返回来告诉用户应该访问哪个节点

第二种是搬迁过程中 数据一直存放在源节点,等数据搬迁后再切换路由,这种方式一般使用额外的fork线程来处理搬迁,例如腾讯云自研的云redis 搬迁方法。

2.搬迁过程中不需要多余的组件辅助

在搬迁过程中,往往需要多个命令进行,例如redis cluster需要先设置importing flag ,然后获取要搬迁的数据集合 最后执行搬迁指令,这个流程过于复杂需要辅助工具或者脚本才能完成。

而tendis 目标是一条命令解决所有搬迁的流程问题,使用异步方式完成搬迁,搬迁线程通过状态机控制调度并不需要运维干预,搬迁过程中的异常情况能够自动进行重试处理,同时提供命令去实时获取搬迁的进度信息。

3.搬迁过程 如何处理增量数据并保证一致性

在搬迁过程中存量数据通常通过镜像或者某种压缩打包的方式来发送,但由于在搬迁过程中用户又有不断新的数据产生,这里的增量数据就没法通过镜像一次发过去, 因为镜像发送的过程中永远会有新数据产生。

另外在需要在搬迁过程中做到流量智能切换,  比如搬迁了一个slot , 在搬完的一瞬间,用户请求能不能实时转到目标节点并做到完全一致,这是一个难点。一般是辅助的工具去暂时终止用户请求,然后切换路由,但这样会影响用户请求。

技术设计

搬迁设计

下面说明下tendis方案是如何解决上面的难点的:

1. 后台线程池和状态机设计

tendis存储版是多线程模式, 因此这里设计tendis存储版使用独立的线程池进行搬迁,同时tendis存储版的搬迁是以slot为维度,当一批任务下发时,会按照一定粒度给每个搬迁线程分发不同的slot搬迁任务。由于在tendis 底层数据设计中实现了slot锁级别的锁控制,所以并发搬迁中可以进行细粒度的控制,所有搬迁任务是原子的,不存在中间状态,搬迁期间不会影响读写访问,部分任务搬迁失败也不会对系统其它任务造成负面影响。另外在任务调度处理上尽量让多个task处理不同db的slot数据,这样并发竞争的可能就很小,性能会更好。

如上图所示,在发送方的源节点线程池和目标节点线程池,分别有3个线程进行后台的搬迁任务,在搬迁完成后通过请求到源节点的slot请求会通过move协议转发到目标节点。

在tendis实现中每个线程都由状态机触发搬迁流转,并且每一对搬迁子任务都有一个taskid , 源和目标节点的相同子任务相同也具有相同的taskid,从而在调度时候能够一一对应:

(1)目标节点receiver 会接收到搬迁命令,它会做一系列元数据检查,然后发送一个内部命令给sender, 当sender 检查ok时会进入sender的搬迁任务进入WAITING状态,

(2)receiver 接收到回包后,会进入REIVESNAPSHOT状态(表示进入ready状态),然后会根据建立的dstNode信息给对方发一个内部命令(加上自己的taskid)

(3)sender 接受到后会再次检查,然后找到匹配的taskid的任务,将这个taskid的sender的搬迁任务进入START状态, 进入START状态;

(4)调度线程检查到START状态后,会进入全量数据同步阶段,全量数据发送完后,receiver会创建接受命令的session然后进入RECEIVEBINLOG状态, 开始接受增量数据;

(5)sender在完成所有增量数据后,会发送一个命令通知receiver , receiver找到对应的takid 后需要修改cluster 元数据并将task状态设置其状态为 SUCC, sender收到回包后也修改自己的元数据信息 并将状态设置为SUCC, 如果中间出现异常会设为FAIL状态并进入异常状态处理逻辑。

2. binlog增量同步设计

如上图所示,搬迁数据基于的技术是快照+增量binlog的技术, snapshot快照是rockdb底层引擎天然支持的特性,能够生成某个时间点的全量数据,而binglog是类似于 mysql主从同步使用的binlog , 是用来增量同步主从数据的,tendis 在底层引擎实现中实现了这两个特性,因此搬迁的过程可以利用这两个特性分别发送全量和增量数据。

在tendis binlog 设计中个一个事务一条binlog,并生成递增的binlogId , 通过binlogId能够搬迁源节点和目标节点获取当前的搬迁进度,并能够实现断点续传。

搬迁的时序描述如下:

(1)源节点获取当前源节点的binlog最高水位作为起始beginLogid

(2)源节点设置事务并创建快照snapshot

(3)用基于搬迁的slot前缀创建游标,遍历发送快照的数据。

(4)发送完shanpshot数据后(全量同步完成),获取当前源节点的HighestBinlogId,  然后发送[beginLogid,  HighestBinlogId] 这个区间的数据。

这里设计一个迭代收敛的算法,让这个过程循环迭代,没迭代完一次求一个HighestBinlogId和begin的差值,直到这个差值小于一定的阀值以内,算法描述如下:

  1. send_binlogs(slots) {

  2. begin = beginLogid

  3. end = gethighestID();

  4. retry = 10;

  5. while (retry-- == 1) {

  6. sendlog(begin ,end);

  7. begin = end;

  8. end = gethighestID();

  9. if (end - begin < 10000) {

  10. finished = true;

  11. break;

  12. }

  13. }

  14. lock_chunks(slots);

  15. end = gethighestID();

  16. sendlog(begin ,end);

  17. unlock_chunks(slots);

  18. }

上面的差值之所以会不断逼近是由于apply binlog是执行物理binlog , 没有命令逻辑处理和锁控制, 而系统binlog水位是处理命令后提交事务才因此增加。

这里设计的目是保证最后上锁的时间很短,因为最终必须通过锁来让源和目标节点、的数据进度完全匹配上。所以当while循环完成后 这里会给这次任务的slot 上锁(阻塞了这部分slot相关的请求),这样源节点binlogID就不会增加,这个时候再发送最后一个binlog序列,将这10000条发送过去。这个锁时间通常在100ms以内,用户几乎无感知。

3. 元数据一致性切换

tendis cluster 是一个基于gossip实现的P2P网络架构,每个节点都存放了全部集群节点的信息,因此在搬迁数据完成时,可以通过gossip的能力来实现所有节点的元数据的一致性切换 ,实现方法如下:

  1. 那么结束后 sender自动发送一个migrateEnd命令给receiver,通知对方数据已经发送完毕

  2. receiver设置slot归属的元数据,并且将数据的epoch提升为集群所有节点最大id , 然后进行gossip广播

  3. sender和其它节点收到gossip广播后, 比对epoch , 由于其它节点都小这个id , 因此元数据以广播信息为准,都会修改slot归属为receiver

这个过程几乎可以实时广播slot信息变化,从而能在几百毫秒内完成这个数据一致性切换。


4. 搬迁过程异常处理

搬迁过程中由于网络异常导致部分搬迁任务失败的话往往需要人工干预甚至需要新开始搬迁。而tendis设计中子任务如果搬迁失败可以自动重试失败任务,这里主要会在两种场景中重试:

(1)由于发送方和接收方 线程资源不匹配造成的失败,这里可能是由于线程数分配不同或者发送方和接收方节点数不匹配造成

(2)binlog发送过程中 某些网络原因发送部分binlog失败,这里搬迁任务可以自动进行重试和断点续传(在发送失败的binlogID位置重试)

另外如果搬迁设计节点发生failover等特殊情况, 这里如果人工干预也比较麻烦,tendis搬迁任务能够自动感知并终止相关任务 , 状态机也能够自动设置为ERR状态。后续如果需要继续重试,可以使用另一个 CLUSTER SETSLOT RESTART nodeID slotlist 命令实现搬迁 整体的重试,这里搬迁过的数据会自动跳过。

整体搬迁流程如下所示:

总结一下,tendis搬迁流程中特点如下:

1、不需要运维人为干预,只需要执行一条命令就可以完成搬迁,另外tendis提供命令可以进行中止和继续搬迁,能做到在搬迁过程中进度可控。

2、 搬迁异步线程池执行,tendis搬迁过程全部由线程池的task自动进行状态机流转完成。

3、支持多个slot 并发高效搬迁,可以动态调整搬迁线程数来控制搬迁速度。

搬迁过程中相关命令的使用说明可以参考tendis文档官网:http://tendis.cn/ ,在上面命令使用介绍部分中有 cluster setslot 搬迁相关系列命令说明

在搬迁方面的性能和稳定性方面还有很多优化空间,这部分代码开源在https://github.com/Tencent/Tendis (tendisplus/cluster/目录),有兴趣的小伙伴可以提交代码参与开发。




文章转载自腾讯游戏存储与计算技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论