# 一、挑战/注意事项

【1】双向同步导致数据库主键ID冲突问题 + 如果忘记设计步长导致数据冲突,数据非常难以恢复,看似简单,但是要十分细心;
    ● a) 海外数据库和国内数据设置相同的步长,例如海外存储的ID1、3、5,国内存储的ID为2、4、6
    ● b) 调整自增步长需要KILL现有链接,需要在业务低峰期操作;
    ● c) 国内自增主键会先开始活跃(上云前先配置步长),自增主键不应该含有业务含义(部分系统会用主键统计总量等);

【2】申请的新数据库,DBA是不会在云端部署的,云端发布应用系统之前需要通知DBADB部署到云端;
【3】用户和配置相关数据采用双向同步,原因参考八;
【4】数据同步延迟带来的危害;

# 二、数据出海合规改造/IDC部署

数据出海合规改造是一项复杂而重要的任务,需要综合考虑各种法律、法规和业务需求。通过以下改造措施,可以确保跨境数据传输和处理过程的合规性,并为用户提供更可靠的数据保护:
【1】数据分类和标记: 对业务数据进行分类和标记,明确标识出敏感数据、个人身份信息等受保护的数据。这有助于在数据传输和处理过程中更好地掌握敏感数据的位置和处理方式。
【2】数据加密和匿名化: 采用适当的加密技术和数据匿名化方法,对敏感数据进行保护。加密可以有效防止数据在传输和储存过程中被未经授权的访问者获取,而数据匿名化则可以保护个人身份信息的隐私。
【3】出海数据业务剥离改造: 数据跨境流动许多国家实施数据本地化策略,数据出海时需同时考虑数据输出地和输入地的数据跨境规则。跨境数据传输时需要进行风险识别和相关的数据控制措施,对业务数据进行剥离改造。

DB多IDC部署: 为确保能够满足业务需求,并提高数据库的可用性和容错能力,将出海DB进行多IDC部署方案。如下图所示:

AWS

需要注意的是,各IDC之间同步时,应考虑各国家和地区的法律法规要求,确保同步数据的链路符合当地的数据存储和隐私保护规定。此外,多个DB数据相互同步时,架构会变得非常复杂。为确保各个IDC之间的网络延迟低、数据同步稳定,要关注每条同步链路的延迟、网络链路抖动和数据一致性问题,并且要定期进行监控、测试和演练,以验证整个部署方案的可靠性和有效性。

# 三、业务场景

场景一: 应用出海,数据库是否需要出海,不出海的情况下,由于数据数据合规要求,数据不允许直接跨region访问DB,只能依靠DRC的单向/双向的同步数据,上海到新加坡的延迟在90ms,也就是说这段时间内,两个region之间的数据是不一致的。对于一些海外不需要的功能,其实直接Mock掉,数据库就不需要出海。存在延迟,适合用户延迟不敏感的应用

AWS

场景二: 数据库出海,海外应用只读,国内数据复制到海外。海外代码改造Mock写操作。

AWS

场景三: 海外应用读写,国内和国外数据双向复制,间接实现灾备,代码无需改造。

AWS

成本对比

AWS出口流量 数据库成本 DRC机器(共享) 代码改造
应用出海 业务请求流量(从Internet到公网会收0.09$/GC的费用,从国内到AWS是不收费的,只有出收费
数据库出海,只读 RSD机器 + 服务费用(RDS 1004元/核/月,目前为低配4核16G集群,多个应用根据流量占比分摊,目前DRC复制费用比较低) 单向复制 改造写请求
数据库出海,读写 海外到国内复制流量,出Internet费用是0.09$/GC RSD机器 + 服务费用(RDS 1004元/核/月,目前为低配4核16G集群,多个应用根据流量占比分摊,目前DRC复制费用比较低) 双向复制

# 四、数据库出海方案 DRC

DRC提供了跨公网MySQL数据库复制服务,公网加密传输Binlog,下面是一个单向的复制链路,双向链路会同时包含一个反向的链路,从海外同步回国内。

AWS

【1】Replicator:部署在国内,实现了MySQL复制协议,伪装成MySQL服务,向国内的MySQL请求binlog,将请求的binlog保存到本地磁盘。
【2】Applier:部署在海外,向国内的Replicator请求binlog,根据原生的JDBC协议,写入目标MySQL,从Proxy拿到的是二进制数据,并将其转化为SQL语句。
【3】Proxy:代理MySQLRedis等服务,使用TCP层进行公网数据传输。

关键指标:复制延迟与物理距离成正比,延迟正常情况下相对稳定,极端情况下回到分钟级。VPC私有网络,业务自运维。

上海 - 阿里云 上海 - 新加坡 上海 - 法兰克福 上海 - SIN VPC
平均延迟 15ms 90ms 280ms 90ms

稳定性保障: 网络异常处理
【1】发送接收皆有流控,通过Netty实现。当发送的buffer到达高水位的时候,写就会关闭。当消息有积压的时候Applier就会关闭读操作;
【2】应用层心跳检测,ApplierReplicator10s发送一次心跳包,30s未收到就会断开连接。2条供应商链路互为主备,外网的出口配置了两个供应商;

AWS

稳定性保障: 循环复制处理
【1】业务数据库初始化新建事务表gtid_executed;
【2】DRC复制先写事务表,反向复制链路过滤事务表开头事务;

将海外数据复制到国内,如果国内的Replicator区分不出该数据是否为海外复制数据,就会将该数据重复复制到海外,就会造成数据冲突。如下通过gtid_executed事务表模拟slave操作,解决循环复制问题。同时,也记录的未点信息,记录已经写入的事务ID
AWS

稳定性保障: 双向复制ID数据冲突处理
【1】自增ID避免冲突:第一种是引入分布式唯一ID,第二种是自增固定步长的ID。

auto_increment_increment=2^N(N=1,2,3...)
auto_increment_offset=M(M=[1,2^N])
1
2

【2】产生冲突:多个机房同时修改同一条数据,目前是以时间戳新的数据为准或者用户手动处理。

# 五、表结构变更

【1】根据DRC复制关系,公有云关联到国内DB,并行变更,还是存在先后顺序,海外的binlog存在新增的字段,在国内binlog回放的sql时候,国内的数据表结构还没有该字段,如果不处理的话,会报该字段不存在的错误。目前我们国内执行该binlog的时候,会将该字段去掉,去掉的前提是新增字段的值与默认值相等,如果不相等就需要业务介入,否则会造成数据不一致,因为当国内同步新增了该列后,会用默认值填充。
【2】表结构变更完成,为了保证数据一致,应用发布新代码。

AWS

# 六、一对多复制

【1】公有云单集群部署(分库),节省成本

AWS

【2】公有云多机房复制。

AWS

高阶功能支持:上海到公有云单/双向复制支持针对性出海:支持表过滤、行过滤、列过滤,因为数据合规的要求,所以比较敏感的数据,列入UID等数据可以通过行过滤实现。如果通过UID判断数据是否出海,效率不高。因为Replicator接收到的是二进制数据,需要将二进制数据转换后获取UID,在通过UID调用远程IDC判断用户属性效率比较低,TPS超过200延迟就比较严重,所以推荐UDL

# 七、数据库访问组件 Dal Cluster

如何保证海外的数据库访问,不会访问的国内。
【1】国内/海外下发不同配置(链接串):应用多IDC部署的场景下,就出现了不同IDC环境下配置文件不同的情况,此时也需要对配置中心的配置文件进行调整:接入子环境,引入多IDC配置文件,支持不同IDC不同的配置场景。

AWS

【2】中心化配置管理:中心化配置,业务无感知。数据库禁止跨海访问,否则应用启动报错。发布完成之后,可以去 Dal Cluster 管理端确认配置是否发布。

业务申请海外数据库 --> DBA部署海外数据库 --> Dal Cluster 新建海外配置 --> Dal Cluster 发布海外配置 --> Dal 客户端获取海外配置
1

# 八、数据库出海流程

【1】业务出海:1)数据库出海;2)应用出海;3)流量分发;
【2】数据库出海:涉及业务方、信安、DBA和框架组。

数据库出海,流量在国内 --> 应用出海,流量在国内 --> 公有云,灰度流量[GateWay] --> 完成灰度,流量切分完成
1
AWS

注意事项:
【1】若出海的数据分散在多个库,先汇总到一个集群,统一出海;
【2】双向复制时,需要做好流量切分,避免数据复制出现冲突;
【3】保留流量切分到上海的能力,防止复制中断影响业务;
【4】如果相同的数据存在多个更新场景,在并发的情况下还容易产生数据冲突的问题,也需要通过单元化部署避免;

# 九、数据同步方案

方案一:手工触发数据迁移,将海外数据迁移到AWSDB。然后将海外流量从上海机房切换到AWS;

AWS

备注 : 灰度过程中一般会分为多个批次进行,每个批次对应的数据和流量,都是重复上述操作;

优点: 流程简单,容易操作,因为操作失误而导致出错的几率极低

缺点1. 增量数据可能丢失:数据迁移的操作,是需要一定的时间才能完成的,而在这一段时间内,可能有用户写入新数据或者修改数据,若修改时间点正好是这条数据已经完成迁移但又在流量切换之前,导致AWS上的数据不是最新数据,即增量数据在AWS上丢失了。问题场景的时序如下:

AWS

【1】用户A下单,订单ID=10001,订单状态为S ,此时流量和数据都只是在上海机房;
【2】执行数据迁移,流量仍然指向上海机房,迁移完成后上海机房和AWSDB都有ID=10001的订单,且订单状态都是S;
【3】用户进行退票操作,由于流量指向上海机房,所以操作后上海机房的DB中订单状态被值为C,而AWS上订单状态还是S;
【4】进行流量切换,将用户A的流量切换到AWS,双边的数据各自保持不变,仍然不一致;
【5】用户进行查询操作,由于流量指向AWS,读取AWSDB的数据,看到订单状态S。退票操作差生的S-->C的增量变更丢失了;

缺点2. 数据迁移的分批策略需要与流量切换的分批逻辑保持一致:分批多次切换的过程中,每次切换都涉及流量切换和数据迁移,二者的分批逻辑必须保持严格一致。再加上我们的数据多样化,会有多种切换维度和策略,会导致数据迁移工具的实现难度和工作量很大。

缺点3. 无法回切流量到上海机房:数据单向同步到AWS,即灰度过程中AWS上会有全量的数据,但上海机房的AWS数据,会随着切换比例逐渐减少,上海机房将无法处理历史数据的变更,也就无法支持全部的AWS流量。当云上的应用出现流程或者环境问题时,只能是尽量快速解决AWS上的问题,而不能将AWS流量回切到上海。由于上云项目涉及的应用和开发组非常多,大家对公有云的运维经验较少,上线初期出现问题的几率较高,解决问题的速度也可能比较慢,无法将流量回切上海,带来的风险和影响较大。

改进方案一:实时同步数据到AWS
灰度过程中,启用数据同步,将上海的AWS数据全部同步到AWS机房,时间跨度是从灰度开始一直持续到灰度结束。由于增量数据会持续的同步到AWSAWS上始终是全量的最新数据,避免缺点1的问题;数据同步是同步所有的海外数据,不依赖与流量切换的分批维度,可以直接使用公司通用的数据同步工具,避免缺点2的问题。

AWS

缺点: 由于数据同步只是单向的从上海到AWS,仍然无法保证上海是全量的最新数据,缺点3的问题仍然存在,即无法回切流量到上海机房。

改进方案二:双向实时数据同步
灰度过程中,同时启用两个方向的数据同步,不仅将海外数据同步到AWS,也将AWS的海外数据全部同步到上海机房,时间跨度是从灰度开始一直持续到灰度结束。这样,上海机房和AWS机房都有全量的海外数据,可以随时将海外流量切换到AWS,也可以随时回切流量到上海,避免缺点3

AWS

缺点 : 双向数据同步可能产生数据冲突,必须对数据写入逻辑进行严格控制,避免冲突。

# 十、存量数据清洗

清洗期间用户需要和DBA一起关注数据库及服务是否有异常。

清洗作业的实现流程为: MySQL数据表 →BIhive数据同步表(截止至前一天的全量数据) → BIudl映射job → 新建的udl映射结果hive表 → DataX通过QMQ推送所有有udl的数据 → udl清洗工具服务消费QMQ → 更新MySQL数据的udl字段 → 触发DRC同步至海外

AWS

【1】清洗Mysql数据库存量数据的UDL,建议按照先业务主表,后业务子表的顺序进行。
【2】整理待清洗的数据库中需要清洗的所有表的基本状况,为后续配置清洗策略做准备。主要包括以下几点:
    1)需要清洗的表的清单;
    2)每个表的当前存量数据量;
    3) 业务主表和子表分类;

WARNING

业务主表同时具有以下特征:有订单号字段;数据库中的其他表都能够通过某个业务ID字段关联主表,且每一行数据都能在业务主表找到唯一对应数据。
业务子表:不满足业务主表的特征的表,可能有订单号,或者没有订单号但能通过某个业务ID字段关联主表。子表和主表必须为同一个数据库中。

    4)每张表的订单号字段,或者能关联到业务主表的业务ID字段,必须为Long类型单字段。如果子表的业务ID字段在业务主表中字段名称不同,需要指明其在主表中对应的字段名;
    5)下游otter消息消费方,可以通过Otter Portal根据数据库查看QMQ Topic等信息;

AWS

    6) 其他特殊需求,如业务系统对时间戳变更的要求;

方式一:使用Hive数据清洗业务主表

编辑hive数据UDL抽取规则

AWS

规则字段含义解释如下:
【1】名称:任意填,区别已有的即可。
【2】描述:业务表的描述。
【3】责任人:可不填,默认为自己。
【4】库名:数据库名,全小写,shard的数据库例如xxxshard01db,则填xxxsharddbmysql数据库对应的hive数据库名即为:ods_fltairtickets_mysql_你填写的库名ods_fltairtickets_mysql_ods_fltairtickets_mysql_。由于少量数据库在hive中的名称和mysql不一致,需确认数据库名。
【5】表名:数据表的名称,全小写。
【6】主键:mysql表的主键字段,全小写,多列的联合主键,则使用逗号分隔,必须全部列出。
【7】是否shard表:如果是shard数据库,必须点击打开。
【8】orderid:表中的orderid字段名,全小写,默认为orderid
【9】orderid中间表:子表中没有订单号字段时必填,填有订单号主表名,全小写,子表和主表必须为同一个数据库中。
【10】orderid中间表关联字段:关联子表和主表的业务ID字段,必须为Long类型单字段,全小写。如果业务ID字段在子表和主表中名称不同,则填写主表中字段名,然后在保存规则之后。
【11】是否关联productorder:如果待清洗的表中含有X单订订单相关数据,则需要点击打开选项。

确认对应hive数据表是否存在,分区类型、数据一致性,如果不存在或者数据有缺失,需要建表及重新抽取。hive数据表有全量分区表和增量分区表两种类型。全量分区表,使用前一天的分区数据即可,分区理论上包含全量历史数据。增量分区表,需要使用全部分区的数据,理论上合并起来即包含全量历史数据。无论哪种分区表,都需要确认hive数据datachange_lasttime最小值和生产mysql对应表datachange_lasttime最小值是否相同,如果不同说明hive数据缺失部分历史数据,需要补数据。

如有特殊需求,可手动修改生成的zeus任务脚本。例如shardid偏移、增加时间范围限制、过滤条件、使用自定义消息Topic等。

配置文件:UDLSetterConfig.json
consumerGroups : 消费组logicDbName为数据库名, topic为自定义消息主题(在前面step3中手动修改),没有可不填。 示例:

"consumerGroups": [
        {
            "logicDbName": "FltOrderDB",
            "topic": ""
        },
        {
            "logicDbName": "fltchangeordersharddb",
            "topic": ""
        }
    ]
1
2
3
4
5
6
7
8
9
10

tableUDLConfig :表的UDL清洗配置列表;
logicDbName: 必填string, 数据库名,必须与dal.config中严格一致;
tableName: 必填string,表名不区分大小写;
writeColumnType: 必填string,清洗的类型,清洗UDLUDL,清洗UID填写UID
writeColumnName: 必填string,清洗的列名,不区分大小写,清洗UDL时一般数据库命名统一为Userdata_Location,清洗UID时填UID列名;
predicateColumnName: 必填,string,表中的订单号字段或者关联到主表的业务ID字段,不区分大小写;
noChangeLastTime: true/false,是否不允许更新时间戳字段,不填默认可以更新;
lastTimeChangeType: int, 0表示更新为当前时间,1:表示更新为原时间戳加10ms;
ignoreError: true/false,是否忽略异常数据继续消费,默认false遇到异常数据立刻终止消费;
forMongodbUpdate: true/false,是否消费只用于Mongodb清洗,清洗Mongodb时填true

示例:

"tableUDLConfig":[{
   "logicDbName": "HuWeiNotDB",
   "tableName": "o_phones",
   "writeColumnType": "UDL",
   "writeColumnName": "Userdata_Location",
   "predicateColumnName": "OrderID",
   "noChangeLastTime": false,
   "strategyId": 2
}]
1
2
3
4
5
6
7
8
9

全局清洗控制参数:对所有库所有表生效。
maxBatchCount: 消费拉取的最大批次数,默认为0表示不限制;大于0则拉取指定批次消息处理后即停止,用于验证;
pullBatchSize: 每批次拉取的Q消息数量;
sleepTimePull: 消费Q消息拉取有消息时停顿时长,单位毫秒;
expectedWorkerCount: 消息拉取线程数,默认1,建议不超过3;
stopSignal: true/false, 强制停止清洗信号,中止清洗时修改为true即可;

数据库维度清洗控制参数controlParameters:缺省使用全局参数;
logicDbName: 必填string, 数据库名,必须与dal.config中严格一致;
sleepTimePull: 消费Q消息拉取有消息时停顿时长,单位毫秒;
expectedWorkerCount: 消息拉取线程数,默认1,建议不超过3;
stopSignal: true/false, 强制停止清洗信号,中止清洗时修改为true即可;

WARNING

数据清洗服务的运行情况,可查看Hickwall面板

说明:对于以上流程,业务子表原则上也适用。考虑到所有hive数据表重新全量抽取补齐数据的工作量较大,子表也可以使用方式二清洗。

方式二:使用Job关联主表获取主表UDL清洗子表

前置条件:业务主表已经使用方式一清洗完成。此时子表可直接在同一个库关联主表,直接获取udl清洗。我们提供一个QSchedule任务,根据配置扫表子表和主表,更新UDL的功能。

根据指定的订单号或UID清洗UDL;考虑到部分场景下,例如异常数据清理,少量订单补数,需要根据一批订单号或者uid清洗udl,我们提供了一个SOA服务,可根据请求中传入的订单号或uid,清洗某个表或者全部表中与此订单或uid关联的数据的udl

请求参数说明:
【1】LogicDbName:逻辑数据库名,须与dal.config配置的连接串的数据库名相同。
【2】TableName:表名,不区分大小写。
【3】OrderIdList:订单号列表,与UidList至少其中一个非空。
【4】UidListuid列表,与OrderIdList至少其中一个非空。
【5】updateEmptytrue表示允许将数据中的udl值更新为空值(用于清理不该落地udl的脏数据),默认为false

说明:如果LogicDbNameTableName传值均为通配符”*“,则表示需要清洗所有表中的数据,表的范围依据qconfig配置确定,配置参数见下面配置文件说明。

配置文件说明:配置文件dal.config(指定单个表清洗时需要)配置文件:UDLSetterConfig.json
tableUDLConfig:表的UDL清洗配置列表;
logicDbName: 必填string, 数据库名,必须与dal.config中严格一致;
tableName: 必填string,表名,不区分大小写;
writeColumnType: 必填string, 填UDL
writeColumnName: 必填string,udl的列名,不区分大小写,一般数据库命名统一为Userdata_Location
predicateColumnName: 必填string,表中的订单号字段名,不区分大小写,务必确认该字段有索引;
strategyIdint固定填2,表示依据订单号清洗。

(指定全部表*清洗时需要)配置文件:AllTableUdlConfig.json
tableUDLConfig:表的UDL清洗配置列表;
logicDbName: 必填string, 数据库名,必须与dal.config中严格一致;
tableName: 必填string,表名,不区分大小写;
writeColumnType: 必填string,填UDL
writeColumnName: 必填string,清洗的列名,不区分大小写,一般数据库命名统一为Userdata_Location
predicateColumnName: 必填string,表中的订单号字段或者关联到主表的业务ID字段,不区分大小写,务必确认该字段有索引;

如果待清洗的为子表,表中无订单号字段,则需要额外配置下列字段:
mainTableName: 主表名,主表中必须有orderid字段,对于shard库主表须为依据orderid字段shard分片;
predicateColumnNameInMainTable: string表中的订单号字段或者关联到主表的业务ID字段,不区分大小写,务必确认该字段有索引;
allShard: true/false,是否依据orderid全分片查询主表业务ID字段并全分片依据业务ID更新子表udl,对于不是按订单号分片的表,此配置必须填true,否则无法计算分片;

# 十一、海外订单号数据库独立部署

目的:海外订单号中存放Location信息,通过订单号就能确定是那个Region的订单,方便保障订单的处理。 产生订单号的数据库,在海外独立部署,和国内订单号数据库不关联。订单号分配逻辑保留海外的可扩展性。

订单号分配基本原则:
【1】全局唯一性:不能出现重复订单号;
【2】趋势递增:有序的主键,保证写入性能;
【3】单调递增:下一个ID一定大于上一个ID;
【4】信息安全:避免让竞争对手获取单量;

数据库多IDC扩展性: 引入RegionCode插入用户数据时增加记录机房标识RegionCode。根据RegionCode确定数据所在Region,使得常用的数据查询或业务处理操作可以在单个节点上执行,以达到数据单元化处理和数据合规策略动态调整的效果,从而避免跨节点带来额外性能消耗和数据跨境合规问题。

# 十二、数据同步延迟治理

【1】数据同步延迟监控:目前主要是凌晨BI数据同步延迟(1、大批量的清洗数据,要求降频,负责延迟会上去;2、大事务,会导致延迟上去 );
【2】DB表结构发布,海外和国内保持表结果一致;
【3】数据同步延迟治理(区分关键和非关键);

AWS

同步延迟监控

AWS

如上图所示,例如同步链路DRC同步延迟时间:SIN<—>FRA:160ms+

# 十三、数据冲突案例分析

对于DRC场景,如果存在双边都有数据修改,DRC会类似延长事务的时间,破坏了事务的单一性操作。和单机版的事务等待不同,DRC场景下,其他侧的操作会破坏失误完整性。可以想想扣库存存在的问题,以及如何解决。

时间点 上海 海外
1:10:50 Orderid=7894322249 海外生产
1:10:51 该新加坡侧订单回传到上海
1:10:53 修改状态 update order_table set status = 'P' where orderid=7894322249
1:10:53 上海将手机号修改为加密手机号 update order_table set phone = '156xxxx9932' where orderid=7894322249
1:10:53 海外对status修改后传到上海,但发现时间戳小于上面phone修改的时间戳,所以放弃本次修改,数据一致性发生变化
1:10:53 上海的订单信息同步回海外,此时订单的status状态被重新置空

冲突解决:和merge replication冲突处理类似
【1】因为该数据属于海外的,因此上海如果需要对数据进行修改,转发至新加坡侧代理进行处理。
【2】对于上述案例,可以让上海这边避开热数据,不需要立即执行,可以放在夜间统一处理,或者放缓几十分钟处理。(但不建议,因为问题还是存在)
【3】根据字段的重要性,执行冲突检测以及定制化冲突自动处理。例如上述案例,应该保留status的修改,放弃phone的修改。
【4】数据幂等性校验。

DB同步冲突问题2: 在生产环境数据同步开启后,突发了网络不稳定造成DRC同步链路阻塞情况

AWS
AWS

如图所示,在监控到DRC同步链路不稳定时,触发了DRC同步冲突告警。

原因: 通过对DB数据的排查发现SINFRA对同一订单进行的更新操作,因为网络延迟导致同步时发生了DRC冲突,导致其中一个更新操作被丢弃,从而影响到了后续订单流程。
解决方案: 修改订单更新逻辑在同IDC内执行。双写发生同步延迟问题必然会遇到一致性冲突问题,长期方案还是单元化,避免出现跨Region操作同一条数据的情况。

# 十三、RDS数据库问题

【1】对云上产品熟悉度不够:RDS维护一个支持范围的配置和版本列表,所以需要经常升级,否则强制升级(因为云上不支持)。同时,磁盘扩容有6个小时冷却期,第二次扩容需要等6个小时(这些时间导致磁盘已经打满)。
【2】云上产品BUGDMS在多并发导数据时,存在导数据不全情况,操作后需要验证,并开启Data Validation(教训:不能太相信别人的产品,需要进行校验)。

(adsbygoogle = window.adsbygoogle || []).push({});