高并发高性能的DB数据同步方案

SyncNavigator是一款功能强大的数据库同步软件,适用于SQL SERVER, MySQL,具有自动/定时同步数据、无人值守、故障自动恢复、同构/异构数据库同步、断点续传和增量同步等功能,支持Windows xp以上所有操作系统,适用于大容量数据库快速同步。

安装包下载地址:https://www.syncnavigator.cn/Setup.zip

帮助文档地址:https://www.syncnavigator.cn/Help_zh-CN.chm

Web文档地址:https://www.syncnavigator.cn/chm/index.htm


背景

在当前的数据库系统生态中,大部分系统都支持多个节点实例间的数据同步机制,如Mysql Master/Slave主从同步,Redis AOF主从同步等,MongoDB更是支持3节点及以上的副本集同步,上述机制很好的支撑了一个逻辑单元的数据冗余高可用。

跨逻辑单元,甚至跨单元、跨数据中心的数据同步,在业务层有时候就显得很重要,它使得同城多机房的负载均衡,多机房的互备,甚至是异地多数据中心容灾和多活成为可能。我们来介绍一款MongoDB的数据同步框架,MongoShake。

MongoShake架构

MongoShake从源库抓取oplog数据,然后发送到各个不同的tunnel通道。源库支持:ReplicaSet,Sharding,Mongod,目的库支持:Mongos,Mongod。现有通道类型有:

1. Direct:直接写入目的MongoDB

2. RPC:通过net/rpc方式连接

3. TCP:通过tcp方式连接

4. File:通过文件方式对接

5. Kafka:通过Kafka方式对接

冲突检测算法

目前MongoShake支持表级别(collection)和文档级别(id)的并发,id级别的并发需要db没有唯一索引约束,而表级别并发在表数量小或者有些表分布非常不均匀的情况下性能不佳。所以在表级别并发情况下,需要既能均匀分布的并发,又能解决表内唯一键冲突的情况。为此,如果tunnel类型是direct时候,MongoShake提供了写入前的冲突检测功能。

目前索引类型仅支持唯一索引,不支持前缀索引、稀疏索引、TTL索引等其他索引。

冲突检测功能的前提需要满足两个前提约束条件:

1. MongoShake认为同步的MongoDB Schema是一致的,也不会监听Oplog的System.indexes表的改动

2. 冲突索引以Oplog中记录的为准,不以当前MongoDB中索引作为参考。

另外,MongoShake在同步过程中对索引的操作可能会引发异常情况:

1. 正在创建索引。如果是后台建索引,这段时间的写请求是看不到该索引的,但内部对该索引可见,同时可能会导致内存使用率会过高。如果是前台建索引,所有用户请求是阻塞的,如果阻塞时间过久,将会引发重传。

2. 如果目的库存在的唯一索引在源库没有,造成数据不一致,不进行处理。

3. oplog产生后,源库才增加或删除了唯一索引,重传可能导致索引的增删存在问题,我们也不进行处理。

为了支持冲突检测功能,我们修改了MongoDB内核,使得oplog中带入uk字段,标识涉及到的唯一索引信息,如:

{
    "ts" : Timestamp(1484805725, 2),
    "t" : NumberLong(3),
    "h" : NumberLong("-6270930433887838315"),
    "v" : 2,
    "op" : "u",
    "ns" : "benchmark.sbtest10",
    "o" : { "_id" : 1, "uid" : 1111, "other.sid":"22222", "mid":8907298448, "bid":123 }
    "o2" : {"_id" : 1}
    "uk" : {
        	"uid": "1110"
        	"mid^bid": [8907298448, 123]
        	"other.sid_1": "22221"
    }
}

uk下面的key表示唯一键的列名,key用“^”连接的表示联合索引,上面记录中存在3个唯一索引:uid、mid和bid的联合索引、other.sid_1。value在增删改下具有不同意义:如果是增加操作,则value为空;如果是删除或者修改操作,则记录删除或修改前的值。

具体处理流程如下:将连续的k个oplog打包成一个batch,流水式分析每个batch之内的依赖,划分成段。如果存在冲突,则根据依赖和时序关系,将batch切分成多个段;如果不存在冲突,则划分成一个段。然后对段内进行并发写入,段与段之间顺序写入。段内并发的意思是多个并发线程同时对段内数据执行写操作,但同一个段内的同一个id必须保证有序;段之间保证顺序执行:只有前面一个段全部执行完毕,才会执行后续段的写入。

如果一个batch中,存在不同的id的oplog同时操作同一个唯一键,则认为这些oplog存在时序关系,也叫依赖关系。我们必须将存在依赖关系的oplog拆分到2个段中。

MongoShake中处理存在依赖关系的方式有2种:

插入barrier

通过插入barrier将batch进行拆分,每个段内进行并发。举个例子,如下图所示:

ID表示文档id,op表示操作,i为插入,u为更新,d为删除,uk表示该文档下的所有唯一键, uk={a:3} => uk={a:1}表示将唯一键的值从a=3改为a=1,a为唯一键。

在开始的时候,batch中有9条oplog,通过分析uk关系对其进行拆分,比如第3条和第4条,在id不一致的情况下操作了同一个uk={a:3},那么第3条和第4条之间需要插入barrier(修改前或者修改后无论哪个相同都算冲突),同理第5条和第6条,第6条和第7条。同一个id操作同一个uk是允许的在一个段内是允许的,所以第2条和第3条可以分到同一个段中。拆分后,段内根据id进行并发,同一个id仍然保持有序:比如第一个段中的第1条和第2,3条可以进行并发,但是第2条和第3条需要顺序执行。

根据关系依赖图进行拆分

每条oplog对应一个时间序号N,那么每个序号N都可能存在一个M使得:

如果M和N操作了同一个唯一索引的相同值,且M序号小于N,则构建M到N的一条有向边。

如果M和N的文档ID相同且M序号小于N,则同样构建M到N的一条有向边。

由于依赖按时间有序,所以一定不存在环。

所以这个图就变成了一个有向无环图,每次根据拓扑排序算法并发写入入度为0(没有入边)的点即可,对于入度非0的点等待入度变为0后再写入,即等待前序结点执行完毕后再执行写入。

下图给出了一个例子:一共有10个oplog结点,一个横线表示文档ID相同,右图箭头方向表示存在唯一键冲突的依赖关系。那么,该图一共分为4次执行:并发处理写入1,2,4,5,然后是3,6,8,其次是7,10,最后是9。

扩展应用场景

例如修改的数据异步刷盘场景,需要提升并发性能。例如个人属性修改,商品属性修改,订单信息修改,评论信息修改等都可以采取以上算法来实现。举例微博评论内容,采取消息队列异步刷盘方式,如果将每个人的信息分为不同分区,并且串行化处理,不太现实,可以将所有评论信息写入到MQ队列,所有的消费者读取一批放入内存,依据userId+articleId进行barrier或者有向无环图分析,逐层落盘或者写入数据库。

 

 

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页