有赞亿级订单同步的探索与实践

1.1 同步现状

当前有赞订单同步流程及业务现状如图所示,采用了 ES+HBase(tip1)架构体系去解决搜索和详情的需求,利用 canal(tip2)将数据库变动写入到 mq 中,而后利用同步系统来解决相关数据同步问题,然后续下文中将叙述有赞订单同步面临的问题及应对方案。java

2、同步

2.1 实现同步基础 - 单表同步

2.1.1 乱序问题

单表同步如图所示:mysql

业务场景中,在这任何一个序号链路中,若是并发出现同一主键的两条消息,同时在 Nosql 中没有作版本控制,都有可能形成消息乱序问题,而相反,若是经过顺序解析 binlog 的同时,为每条 statement sql 执行的结果分配一个顺序 SeqNo,该 SeqNo 保证有序(tip3),再经过 Nosql 中控制乐观锁就能够解决顺序性问题。程序员

2.1.2 HBase 同步

Hbase 同步相对来讲比较简单,Hbase 内部拥有 timestamp 协助控制每一个 qualify 的版本,只要让 timestamp 传入上文所说的顺序 SeqNo,那么就能够保证每一个字段读取出来的数据是最终一致性的。sql

2.1.3 ES 同步

ES 同步针对单表场景能够经过 index 的操做来进行写入,index 能够采用 exteneral 版本也称做外部版本号来进行控制,一样适用上述的 SeqNo 来作乐观锁去解决该问题。数据库

2.1.4 同步过程图

2.2 实现同步进阶 - 多表同步

2.2.1 乱序问题

多表同步基于单表同步的基础上作相关的同步,如图所示:网络

当数据库的两张表(不关心是否在一个实例上,若是不在,还更惨,SeqNo 还不必定可以保证有序)触发了更新操做,假设 t1 生成 binlog 的 SeqNo 小于 t2 生成 binlog 的 SeqNo,若 t1 这条消息因序号链路中的网络抖动或其它缘由形成消费晚于 t2,也就是 t2 的 binlogSeq 先写入 Nosql 中,那么就会形成一个 t1 的数据没法写入到 Nosql 中。多线程

2.2.2 HBase 同步

其实上述多表同步乱序的问题并非绝对的,针对 Hbase 这种自带列版本号的将会自动处理或丢弃低版本数据,同时针对这种状况,设计成每一个 table 表中的字段都会列入到 Hbase 中。举个例子,针对订单的情形存入订单主表和订单商品表架构

场景 1:1

针对订单主表,咱们写入的数据以订单号作 hash,而后以 hash 值: 订单号做为主键下降热点问题,同时定义单 column family,qualifiy 格式为 表: 字段 value 为对应的 value 值,timestamp 为 SeqNo,如图所示:并发

场景 1:n

针对订单商品表,咱们写入的数据一样以订单号生成相应的 rowkey,同时定义单 column family,qualifiy 格式为 表: 字段: 对应记录的 id 值 value 为对应的 value 值,timestamp 为 SeqNo,如图所示:nosql

经过上述写入,可以针对具体到某个字段都有对应的 timestamp 值的更新,为后期写入更新数据可以更新到具体字段级别。

2.2.3 ES 同步

针对上面的同步乱序问题,ES 没有 HBase 这种列版本号,ES 只有 doc 级别的 version,若是上述真的出现 SeqNo2>SeqNo1, 且 SeqNo2 早于 SeqNo1 写入到 ES 中,则就会出现 SeqNo1 的内容没法写入,也就会形成顺序不一致的状况。那如何去解决这个多表同步问题呢?

既然会乱序,那让它有序就行了,数据保证有序不就可以解决这个事情嘛,让整个链路有序也就表明 canal 消费 binlog 数据保证有序且丢到 MQ 中有序,MQ 而后保证顺序投递到 Sync 消费处理程序中,经过消费一条消息而后 ack 告诉 MQ 是否成功,已达到保证全部数据所有有序(若多线程或多机器处理 MQ 中的多个分区都是会存在问题)。如图所示:

如此只要保证 t1 表的数据和 t2 表中的数据在 ES 不互相关联,每一个数据写入的时候按照 update 方式写入(若是不存在须要作一次 create 操做), 这样就能保证全部数据按照顺序执行。

2.3 配置化同步

上文已经讲到数据同步是由一个数据源(这个数据源能够来自于 MQ、Mysql 等)同步到另一个数据源(Mysql、ES、HBase、Alert 等),也就是一个管道的过程。借鉴了一下 logstash 官网,一样处理流程分为 input、filter、output 组件,这些流程称之为 task 任务,如图所示:

经过这些组件,抽象化出每一个组件都有对应的配置,由这些配置来进行初始化组件,驱动组件去执行流程。简单来讲,只须要在页面中配置一些组件,无需开发任何一行代码就能实现同步任务。如图所示:

经过一系列的配置,就能配置出一个任务,针对业务逻辑,能够采用动态语言 groovy 来实行脚本化处理(复杂业务场景能够经过 UDF 函数来作支持),针对 mqinput 拿到的字段而后通过处理,通过过滤 filter 等,能够直接拿到相关的数据进行组装,而后配置化的写入到 ES 中,无需开发任何一行 java 代码便可实现流程自动配置化,针对复杂的需求也可以高效率支持。配置界面如图所示:

2.3.1 性能瓶颈

上述就能解决 ES 多表同步的问题,可是一样会存在一些问题:

  • 性能瓶颈问题
  • 失败堆积问题

性能瓶颈:好比写入量超级大的场景状况下,而 Sync 消费程序只能针对 MQ 中的分区(kafka 的 partition 概念)消费,每一个分区只能有一个线程去执行,消费速率与消费分区成正比,与消费 RT 成反比,尤为是大促场景下就会形成数据消费不过来,数据堆积严重问题。

失败堆积:由于是顺序消费,只要某个分区的某条消息消费失败,后续消息就会所有堆积,形成数据延迟率超高。因此建议用顺序队列的场景除非是业务量没有性能瓶颈的状况下能够采起使用,而怎么去解决顺序队列或者去掉顺序队列呢?

用顺序队列无非就是保证有序,由于 ES 没有 HBase 的字段级别版本号,目前订单采用的是用 HBase 作一层中间处理层,解决该问题,如图所示:

经过借助 HBase 字段级别版本号帮助每一个表保证表内部字段有序,同时 put 写入完数据以后,经过额外字段 version 作 increment 操做,当这两个写入动做完成以后立马 get 操做拿到 HBase 的数据写入到 ES 中,不管并发程度如何,最终至少有一次的 get 请求拿到的版本 version 字段是最大的,用该 version 做为 ES 的外部版本号解决 ES 版本号问题。

用此方案会有好处:

  • HBase 协助管理内部字段版本,同时根据内部操做,协助 ES 拿到对应的版本,且数据能拿到最新数据;
  • 去掉了顺序队列,HBase 具备良好的吞吐,相对于顺序队列拥有更大的吞吐量;
  • 横向拓展增大消费速率;
  • ES 能够采用 index 操做,性能更好。

固然也有弊端:HBase 存在抖动的状况,以及主备切换问题。

由于存在抖动或者准备切换问题,会形成数据不一致,咱们该怎么去解决这个事情呢?

2.4 将来扩展

目前订单同步是经过加载配置文件形式来作的,也就是横向拓展的机器都会去加载同一份配置文件,各个任务经过异常解耦,理论上不会有影响,可是会存在加载任务的重要度的问题。

举个例子:

  • 我须要一台机器临时去消费数据解决线上问题;
  • 有个量级很大但又不是很重要的任务,想不影响其余任务的进行;
  • 要作对比,增量延迟对比或全量对比数据,但又不但愿影响其余数据;
  • 查询日志须要全部机器查看查询(固然,公司有内部日志系统,可直接上去查看) 如此,可让同步系统无状态化,每一个任务的配置加载有任务配置平台来进行配置,指定相关的机器作相关的处理,扩容也能够动态申请扩容,如图所示,能够自由分配机器处理不一样的任务。

3、一致性保障

上文讲了有赞在处理订单的时候怎么讲数据同步到 ES 或 HBase,数据来源于 binlog,写入到 MQ,也就是说处理的来源来自于 MQ。简单一句话来说:咱们不生产消息,咱们是消息的搬运工。“搬运工”的角色能够作一些事情,一样有赞在处理数据对比也是如此,这章讲讲“搬运工”能够作什么:

3.1 数据对比

上述通常状况下不会出问题,那若是出问题了怎么办,须要作数据对比,而数据来源就是咱们刚刚抛弃的顺序队列,顺序队列有个缺点就是堆积,一样咱们也能够利用堆积的特性,让其第一条消息堆积十分钟,那么后续消息基本上也会堆积十分钟,而后就能够消费这个消息进行数据拉取,拿到最新的数据进行数据对比,如图所示:

经过对比结果发送到 alert 中,就能够知道哪些数据不一致,频率多少,这也是一种同步(mq->filter->alert)!

3.2 全量对比 / 数据刷入

上述咱们讲到数据同步到 Nosql 中,可是只是讲了增量的一个过程,涉及到历史数据,就须要对历史数据进行迁移,一样,这也是一种数据同步,后面将会出相关博文怎么去作全量数据同步。

4、Tips

Tip-1:为何采用 ES + HBase 处理搜索和详情?

通常状况下,公司达到必定规模,有相似全文检索的需求或者高频 key:value 的时候,你们会推荐 ES+HBase 的架构体系去完成搜索和详情的需求,而现实中,绝大多数状况下生产环境不会将数据直接写入到 ES 或者 Hbase,你们都会优先写入数据库,不进行双写的操做是由于增长链路影响业务。固然 Hbase 可能还好一点,ES 自己就是非实时查询系统(为何是非实时,有兴趣的能够去看看 ES 读写流程),这种状况下也造就了 ES 和 HBase 的一个准实时系统。针对业务来讲,准实时是能够知足相关需求的,好比商家搜索订单并不要求实时。

Tip-2:为何有赞选 canal 解析 binlog,而不是采用业务消息进行数据同步?

  • 数据表被更改,好比修数据状况,业务消息不会触发,致使没法写入到对应的 Nosql 中形成。数据不一致
  • 顺序性问题没法获得相关保障;
  • 业务消息并不能拿到全部相关的数据进行写入到 nosql 中。

Tip-3:SeqNo 实现方式,为何不用 binlogoffset?

由于 cana 实例与 mysql 实例是 1:N(推荐 1:1), 而大部分业务场景同一种数据通常会落在同一个实例上,canal 就能够经过该台实例的时间与每秒处理的个数相结合。如:timestamp*10000+counter++,而不用 binlogoffset 的缘由是 mysql 的实例挂了话,binlogoffset 可能会乱序。

5、结语

有赞交易订单管理承接了亿级流量的同步任务,面临着众多的需求挑战,从最开始的 mysql 到现在的产品化的同步任务,从单表同步到多表同步,从单索引到多索引,从增量到全量,都有不一样的解决之道,现在新兴搜索中台更是承接亿级搜索和同步流量,若有兴趣,欢迎加入,咱们一块儿共同探讨。

欢迎工做一到五年的Java工程师朋友们加入Java程序员开发: 721575865 群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代! 

相关文章
相关标签/搜索