数据同步平台技术演进与实践

1.前言

去哪儿机票售后为用户提供退票、改签、航班变动、行程服务、疫情政策等服务的业务。 业务场景中有复杂的基于订单、客票、PNR、行程、航司等各种维度的查询诉求,这些数据分散存储在不同的mysql数据库表中,难以为复杂业务查询场景提供服务。为解决复杂查询场景,我们设计了一套将数据从一个数据源聚合导入到另一个数据源,提供同构或者异构、低延时的、最终一致性的数据同步系统。

去哪儿数据同步平台是把数据从mysql同步到es中,并且提供统一的查询网关的服务。随着业务的发展,es主备集群高可用性存在问题,单一节点故障后难以恢复和补数;同步链路不合理,索引间存在相互影响;公司要求升级es集群版本等问题凸显。本文将与大家详细探讨数据同步平台ES多版本迁移支持、数据同步平台高可用和数据一致性的设计与实践,希望对大家能够有所帮助或启发。

2.数据同步平台介绍

数据同步平台总体架构入下图1所示。系统是典型的管理系统架构,由manager、otter manager(web管理)和otter、dts数据聚合平台、crab网关(三个工作节点)组成。数据同步平台通过crab网关对外提供查询和写入两个核心功能:查询功能是由业务线接crab-client客户端对es索引进行查询,查询功能以appcode为维度配置了限流、鉴权和es集群查询分流比例;写入功能主要是通过otter把binlog写入到kafka,经dts数据聚合处理后通过crab网关写入ES主备多个集群。

 

图1

  • manager :  管理工作节点(otter、dts、crab)运行时的配置,主要包括读写鉴权、ES集群分组、ES索引维护、ES主备集群查询流量分配、dts数据聚合node管理和其他管理功能。
  • otter :  基于阿里巴巴开源分布式数据库同步系统改造,主要解决了同步配置页面化、Databus生产者单点的问题。同时我们扩展实现了Kafka类型的DataSourceMedia,在otter Load阶段发送消息给Kafka。
  • dts : 设计思路参考otter的模式,主要包括Node、补数任务和数据库反查组件。Node是由SFTL四个阶段组成,S是从kafka消费数据,F是过滤数据,T是重新组装数据(主子表关联、Array处理),L是把数据通过Crab-client写入ES网关中。
  • crab : 是ES集群的读写网关,提供统一的鉴权、熔断限流、流量分配等功能。
  • 基础设施: 公司内部mysql三种架构(单节点mysql、mmm和pxc),es group是指一个index存在于多个es机群中,逻辑上划分为组,为读写提供主备高可用服务。
  • watcher:公司内部监控报警平台,为系统提供全链路的监控和报警。

3.背景

数据同步平台目前接入了10+业务线、14+个es index。2021Q2数据组要求统一升级es5.x到es7.x,同时数据同步平台高可用和稳定性问题凸显,主要表现在以下四个方面:

  • ES主备(任一)机群故障后,整个同步链路不可用,且故障难恢复。
  • 数据库IP迁移后,otter无法自动切换数据库。
  • 全链路监控和降级熔断不清晰,难以发现故障,排查问题难。
  • 同步链路隔离和降级不合理,一个索引同步故障影响了其他索引的同步
     

基于上述系统痛点,制定了以下两个目标:

  1. 数据同步平台系统具备灵活的缩扩容能力,把es集群平滑缩扩容迁移,升级es5.x到es7.x。
    这个目标不仅满足数据组需求,还提高了数据同步平台的高可用能力,单一es集群故障时可以灵活的配置上下线恢复故障。
  2. 同步全链路高可用优化
    梳理全链路的流量治理和熔断降级机制,建立同步各个阶段的高可用方案,隔离不同索引同步间的相互影响。

4.技术演进实践

4.1.es集群7.x升级和热拔插

crab是es集群的读写网关,核心架构入下图2所示。manager配置es集群分组和查询流量控制,crab定时拉取配置信息,对外提供读写操作。7.x升级和热拔插目标可以拆解为crab网关支持es5.x和es7.x并行、es集群热拔插、es集群灵活补数三个任务项。

 

图2

4.1.1 es5.x和es7.x并行

es5.x和es7.x在版本上存在差异,主要包括es rest client兼容性,REST APIs兼容性,es查询返回值的三部分:

  • Elastic rest client

es客户端目前主要是以下四个,从es5.x和es7.x多版本支持上看 Elastic java low level rest client兼容所有版本es,综合比较后我们选择使用Elastic java low level rest client。

客户端

版本

客户端兼容

优势

劣势

Elastic transport client0.9~7.x

客户端的主版本号必须和ES集群的主版本号 保持一致,次版本号可以不一致, 但是次版本号不一致可能导致一些API不兼容

官方7.x标记为Deprecated,8.x移除不再支持。

具备高级api,代码编写量小es版本对于client版本不同
Jestclient1.x~6.x官方提供兼容列表支持到6.x灵活、简单高级的api需要验证跨版本,官方未支持7.x
Elastic java high level rest client5.5~7.x客户端的主版本号必须和ES集群的主版本号 保持一致,次版本号可以不一致, 但是次版本号不一致可能导致一些API不兼容具备高级api,代码编写量小es版本对于client版本不同
Elastic java low level rest client5.x~7.x兼容所有es版本最小依赖、针对可用节点负载均衡、原子操作、兼容所有版本es只提供了简单的api

  • REST APIs

crab网关支持的REST APIs主要包括Serach APIs、Document APIs和Script APIs

Search APIs主要支持Search、Scroll对ES进行查询操作。主要区别如下表格。es7.x功能实现需要在manager中配置ES的版本,crab在构建请求时识别es的版本后构建不同的endpoint进行适配。

es5.xes7.x备注
/index/type/_search/index/_search

es5.x支持自定义type

/index/type/_search/scroll/index/_search/scrolles7.x中只支持默认type(_doc),8.x中将移除type

Document APIs主要支持Index、Update和Delete,crab网关insert和update操作都统一使用upsert操作实现。

es5.xes7.x备注
/index/type/rowkey/_update/index/_update/rowkey主要区别是rowkey和_update操作的前后顺序拼接

  • Query DSL和Scripting

Elasticsearch提供了基于JSON的完整查询DSL(Domain Specific Language)来定义查询。es5.x和es7.x版本支持的dsl存在差异,针对此场景汇总crab中支持的DSL语句发现match有以下区别;

es nested类型存储的是父子(1:n)表,更新的时候需要利用script解决,es7.x不再支持 file的形式指定script,需要构建请求时区分es版本解决。

es5.xes7.x备注
scriptscriptParam.put("file", SCRIPT_FILE_NAME);scriptParam.put("source", SCRIPT);

es5.x是通过保存script脚本形式处理

es7.x在请求中传输script脚本

dsl

"query": {
         "match": {
             "addr": {
                  "type": "phrase_prefix", ...
"query": {
          "match_phrase_prefix": {
                "addr": {
                       ...
6.x及以后已经不支持在math里面使用type

match

match_phrase

match_phrase_prefix

        

  • es5.x和es7.x返回值区别

es5.x和es7.x返回值主要差别在hits上,es5.x可以直接返回查询命中数量,es7.x返回数量是个结构体。如下表。解决方案是es7.x的查询endpoint默认添加参数 ?track_total_hits=true,返回值的relation就是eq,获取到返回值后,提取查询名字数量,修改为es5.x结构。

es5.xes7.x备注

{

  "hits": {
    "total": 0,
...
 

{

  "hits": {
    "total": {
      "value": 10000,
      "relation": "gte"
...

es5.x返回结果中字段total是精确命中数量

es7.x返回结果total是个结构体,

含义是命中的数量relation(大于、等于)value

4.1.2es集群热拔插方案

es集群热拔插是解决es7.x升级的平滑上线和单一es集群故障时机群切换的方案。详细切换步骤明细可见下表格。

方案

备注

1新建es7.x集群通过reindex进行全量数据导入
2manager新增es7.x集群配置

新增es7.x配置,读流量配置为0,配置不可写。

3线上发布新版crab和dts验证线上无影响,可正常支持老机群5.x
4es7.x分组配置把新机群添加到对应的index集群分组中
5es7.x集群配置写入验证写入和补数可正常写入7.x
6es7.x集群补数补reindex时间和步骤4时间段的数据
7es7.x集群数据验数验证新老机群数据对比一致性,包括时间段内数量,新老集群抽样数据对比,全量数据量对比等。
8es7.x查询配置流量验证业务查询正常,小流量逐步验证。

        es7.x上线从时序上核心5个阶段,如下图3:

         

 

图3

  • 1.执行reindex阶段:执行reindex将老集群全量数据写入到新集群中,此时新集群中包含的数据是时间段1内的全量数据。
  • 2.crab写入阶段:执行步骤5,此时新产生的数据可以写入新集群,新集群中缺失时间段2的数据。
  • 3.后置diff补数:从反查组件中查询全量数据,通过crab网关写入组件写入时间段2缺失的数据和时间段3遗漏的数据。
  • 4.验证数据:验证新老集群时间段数据和抽样diff新老集群数据。
  • 5.可查询阶段:diff一致后新集群可以切换查询。

4.1.3es集群灵活补数

    针对补数有全量、部分补数的需求,我们制定了reindex、canal移动位点、diff补数定时任务三个补数方案方案。

  •  reindex:ES提供了_reindex这个API供索引进行重建,这种方式可以全量数据重建,具体代码如下:
curl -H "Content-Type: application/json"   -XPOST http://ip:port/_reindex -d'{
    "source": {
        "remote": {
        "host": "http://ip:port"
        },

            "index": "order_info_beta_tts8"
     },
     "dest": {
          "index": "order_info_beta_tts8"
     }
}'

reindex补数方案适用于上图时间段1的补数,可以把集群1索引的数据全量导入到指定的新集群。

  • canal移动位点:通过修改canal的位点信息,重新拉取binlog方式进行同步补数,由于binlog保存时长的问题,位点只能重新同步最近的数据,修改方式如下图4:

 

图4

canal位点方案适用于时间段近、集群读写qps不高的场景,可以用于时间段2的补数方案。

  • diff补数定时任务:通过反查组件,反查数据库全量数据方式进行es数据重写。

 

图5

diff补数方案是精确补数方案,由业务方实现反查组件,数据同步平台调用获取数据后通过es网关获取索引中的数据,对比不一致的通过crab写入组件写入对应的索引集群。diff方案可以实现精确补数,缺点是业务接入方有业务开发量。目前线上支持国内机票、国际机票等核心业务。


4.2数据一致性和高可用

要介绍数据同步平台的数据一致性和高可用,首先以国内机票为例,核心同步信息有订单信息(1)、乘机人信息(n)、航段信息(n)、退款记录(n)等,同步到ES索引结构如下图6所示。订单信息创建了单个文档,乘机人、航段和退款记录等数组以nested data type内部文档存储。

 

图6

数据同步流程是由otter内置canal订阅binlog 后经过SETL四个流程后,写入到kafka。dts消费kafka数据,经过SFTL四个流程后写入crab,最终写入ES集群中,如图7所示。

 

图7

  • 数据顺序和最终一致性的保证:

数据最终一致性的保证是由三个层面:1.数据同步的时候链路上同纬度数据是顺序性的;2.数据在链路写入失败时,进入失败重试队列;3.针对重要集群建立diff补数任务。
1、按照单维度数据的同步顺序性(otter → kafka → dts → crab → es)

 otter Load阶段写kafka时partition,要保证同业务分配到同一个partition。例如国内机票业务发送kafka的partition key是库名 + order_id,这样可以保证一个订单下的所有binlog被发送到同一个partition内。

dts消费kafka的时候是单线程的,处理数据也是有顺序的。通过dubbo接口写到crab网关时,也是顺序写入es中。 

2、数据在链路写入失败时,进入失败重试队列

dts到crab和crab处理数据过程中都可能因为各种原因失败异常,系统捕获到异常后将数据写入retry kafka队列,dts消费retry数据后,通过数据库反查组件,查询最新数据写入crab网关保证数据最终一致。

3、针对重要集群建立diff补数任务

针对国内机票和国际机票等重要索引,建立了最新一分钟数据diff补数逻辑,定时任务通过反查组件,查询全量一分钟数据,跟es数据diff比较,不一致的数据写入crab网关保证数据最终一致性。

  • 数据同步链路的高可用:

数据同步链路的高可用(同步顺序:otter → kafka → dts → crab → es)如下:

otter:otter以Pipeline(可以理解成一个es索引的同步)运行在不同的otter node中,实现了索引间的隔离。同时内置canal以主备模式,S和L阶段以主备模式运行在多个Node中,实现了单索引的高可用。

kafka: 以每个索引一个topic,每个topic有多个partition,每个partition有多个副本实现了高可用。

dts:以kafka的topic为维度生成了多个Node消费数据,以线程隔离的方式实现了索引间的隔离。多个consumer实现了单索引的高可用,一个consumer销毁后其他consumer可继续消费数据。

crab:以调用方appcode+索引维度创建了Hystrix线程池,不仅隔离了索引间的影响还保证了单索引的承载能力。

es:一个索引保存在多个es集群中,由管理系统配置查询分流实现了索引的高可用。

由于数据同步平台出现过kafka磁盘空间满、zk故障等异常case,此时可以通过4.1.3es集群灵活补数方式对集群进行部署和恢复服务。

  • 数据同步的优化:

1、以国际机票生单为例,一个操作是会产生多个主子表的binlog,多个binlog会写入到同一个partition,dts在消费时可以根据业务特性,以服务单单号为key,一批内只保留最后一个binlog执行反查和写入,即一批binlog中同一个服务单单号不管有几个binlog,最后执行反查和写入的只有一次。

2、otter数据库主备切换并未开源,根据公司主流mysql是pxc架构,我们定制实现了数据库主备切换功能。

3、otter S阶段默认拉取10000条数据,在大表有DDL操作时会打满网卡造成故障,我们根据拉取条数变化动态调整拉取设置。

5.总结和未来规划

通过以上方案,顺利完成了预期目标。另外es集群读写性能有了量级的变化,国内机票写入耗时37降低到7.7、国内机票查询耗时由65降低到30。如下监控:

该方案已经实现了数据同步平台的全链路监控和全链路高可用。未来会在数据聚合层数据聚合结构化配置、智能运维(故障节点自动迁移)等方面做更多的投入。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值