原始需求

对跨业务域数据提供联查搜索能力

  • 比如:对退款单提供根据退款单、退款状态、发货状态的联查,其中退款状态和发货状态是跨业务域。
  • 比如:对订单提供根据订单号、订单状态、退款状态的联查,其中订单状态和退款状态是跨业务域。

为什么要上溯需求层面 ?要优化现有方案,容易局限在现有方案的框架里。上溯到需求层面,能够跳出现有方案框架,在更大的范围内搜索解决方案,亦可对现有方案的部分设计与实现的前提和约束有更为清晰的认识。

目标

将多源数据存储 (S1,S2,...,Sn) 的数据同步到具备联查能力的目标数据存储 T,且必须满足:

  1. T 中的不变数据与 Si 中对应的不变数据应当在指定计算关系下保持一致;
  2. 对于每一次同步,T 中的可变数据与 Si 中对应的可变数据在最新状态下保持一致。

在现实场景下,源数据存储 Si 通常是数据库 DB , 而 T 则有不同选择。 比如,有赞选择了 ES 。选择不同的存储,对解决方案的影响也是非常大的。

注意:需求和目标是存在差异的。目标是原始需求在当前环境约束下所能转化的第二层需求。如果跨业务域数据都存储在一张源数据存储中,也就是 源数据存储-目标数据存储 是 1:1 的关系,那么目标就是单数据存储的同步了,解决方案也会简单得多。

但是,在规范的设计理念的指引下,不同业务域的数据通常存储在不同的数据表里,因此才会有多表同步的需求。

制定目标时,需要考虑当前环境约束的影响。如果当前环境约束过强,则应适当考虑能否改造环境约束,使得设计方案有更灵活的空间可供选择。环境约束改变了,同样需求的目标也会发生变化。

设计关键

数据状态变更的每一次最新状态同步必须保持强一致性。

这里的难点在于可变状态的同步。因为不可变状态只需要原样同步即可,且在任何时刻进行均可。

可变状态的同步怎么弄呢 ? 假设状态是不可逆的,且数值是递增的,比如订单状态的数值只能从 99 -> 100 ,不可能从 100 -> 99, 这样,通过数值的比较(而不是时间戳),就可以过滤掉那些过时的状态。解决方案也会相对简单。

现实场景中,状态的逆转往往不可避免,状态数值的递增关系也不一定满足,因此,通用的方案不能以状态数字的递增关系为前提(否则就会削弱方案的通用性),而要以状态变更的原始时间戳为准。

ES 的前提

ES 存储的数据是一个大的 JSON 串,没有字段级别的版本控制,只有针对整个 JSON 串的一个版本控制。 在多表情形下,同步 ES 需要考虑全局版本控制问题。

ES 数据存储: https://elasticsearch.cn/article/6178

ES 版本乐观控制:https://es.xiaoleilu.com/030_Data/40_Version_control.html

多表同步的现有方案: https://tech.youzan.com/you-zan-ding-dan-tong-bu-de-tan-suo-yu-shi-jian-2/

同步ES的基本逻辑

情形一

假设有一个 索引 E 含有 (refund_id, order_no, rp_status, version)

有一个 DB 表 r (refund_id, order_no, rp_status, version)

同步方案:

只要按照 version 字段,同步到 ES 即可。对于 T1 M1, T2 M2 ,如果 T1.version < T2.verison ,将 T2 时刻的记录同步写入。 此时,使用非顺序队列即可。

结论:

理想的情形下, 一个 ES 表完全仅对应一个 DB , DB 含有 version 字段,可以作为 ES 表的 version 版本控制,使用非顺序队列。 做搜索索引,应尽可能符合这种情形。

情形二

现在假设有 t (order_no, shop_name) ,要把 shop_name 同步到 E 中。 shop_name 是不变的。

这时,只要在同步处理消息的时候, 关联 t 表,将 shop_name 读取,写入到 E 即可。 只是增加了一次 DB 访问,仍然可以使用非顺序队列。

结论:

将 DB1 , DB2 同步到 E 中, 以 DB1 为主, 获取 DB2 的不变字段,依然可以使用非顺序队列。DB1 的 version 字段作为版本控制。

需要注意的是,对于分库分表来说, 这里的 version 必须是全局递增的 version ,而不是某个分表的 version 。因为某个分表的 version 是不能满足递增特性的。

情形三

现在假设有 t (order_no, order_status) , 需要把 order_status 同步到索引 E 中。这时候,如果用 version 是不够的。

存在这样的情形:

假设退款单 R001 (T1 M1)→ (T2,M2) 发生了 rp_status = 1 → 3 ; 订单状态 order_status (T1', M1') → (T2', M2') 发生了 order_status = 1 → 5。

现在 T2 时刻的退款单消息 M2 先于 M1 抵达,实时获取了 T1' 的订单状态 1 ;得到了 T2R = (R001, E001, rp_status=3, order_status=1, version =3)

然后 T1 时刻的退款单消息 M1 抵达,实时获取了 T2' 的订单状态 5, 得到了 T1R =(R001, E001, rp_status=1, order_status=5, version=1)

由于 T2R.version > T1R.verison, 因此 T2R 写入。 然而,此时,order_status 的同步是错误的。

结论:

当 DB1 (主),DB2 (辅) 均要同步到索引 E 时,如果 DB1 和 DB2 所需同步的字段都存在变化,那么,使用 DB1 的 version 字段控制版本号是不可行的。这将导致 DB2 的字段同步变更存在错误。

此时,就同步 ES 来说,应当尽量避免这种情形。在设计方案的时候:

  1. 避免将两个表的可变状态同时同步到一个索引里。

  2. 在业务层就能做到把 DB2 的可变字段冗余到 DB1 里。不过,这样会增加 DB1 设计和业务更新的复杂度,且事先也不会想到这种冗余方法。

现有解决方案

要解决多表同步的问题,有两种现有方案:

使用顺序队列

上述的问题引发的原因是, DB1 的后更新的消息先抵达先处理。 使用顺序队列,使得 DB1 的先更新的消息始终先处理,这样,就不会导致 后更新的消息获取到过时的 DB2 的字段状态了。 使用顺序队列的原理是,设置 DB1 的主键 ID 作为顺序队列的排序 key 。 顺序队列的优势,是让业务方处理容易了;但顺序队列的并发吞吐量取决于队列分区数,且容易因为一条消息处理出错而阻塞后续的处理。

使用非顺序队列

使用非顺序队列,需要中间存储,自定义的全局版本号 G 和 一个全局的 存储 S

理想情况下,无论谁先到达,都应该写入 最新的数据。那么,这个全局存储 S 和 全局版本号 应该具备什么特征呢 ?

  1. 全局存储 S,应当含有同步 ES 所需的所有字段;

  2. 每次通过全局存储 S 和 全局版本号 G 的处理 GlobalHandler, 总能拿到所有字段的最新值 FVnew;

  3. 每次通过全局存储 S 和 全局版本号 G 的处理 GlobalHandler, 总能拿到递增的全局版本号 G;

  4. 以 G 为 ES 的版本号控制,总能将递增的 G 和 最新值 FVnew 写入。

实现思路:

  1. 第一点相对容易,梳理一下所需要同步的字段即可。举上为例:<refund_id, order_no, rp_status, order_status, G>

  2. 第二点:全局存储 S,具备字段级别的过滤能力,能够根据时间戳过滤掉状态过时的字段值;也就是说,对于要同步的字段 Fi (i= 1,2,..., N), 如果 Fi.timestamp_t1 > Fi.timestamp_t2 ,则 Fi_value_timestamp_t2 被丢弃。每次写入 S 之后,再取出 S 的最新值。 这正是 HBaseFilter 的功能。

    比如说, M2 先抵达,T2 rp_status = 3 被同步到 S ,然后取出最新的值写入 E; 接着, M1 抵达,由于 rp_status = 1 的时间戳 T1 < T2 ,因此, rp_status = 1 会被 S 过滤掉,不起作用,然后取出最新的 S 写入 ES ; 接着 M3 抵达,将 order_status = 5 写入 S,再取出 S 的最新值写入 E。

  3. 第三点: 全局版本号 G 的计算,保证消息写入的递增性。 同一个表 比如 DB1 的时间戳是有先后的,比如 T2 > T1 , 但是 不同表的时间戳是没有先后的,比如 DB1 T2 与 T3 是无法确定谁大虽小的。

假设 Gf 是消息所带时间戳 T 的函数,Gf = G(T,Ginit) ,Ginit 是全局版本号 G 的初始值,那么 Gf 应当满足什么条件呢 ?

首先 G1 = Gf(T1, Ginit) , G2 = Gf(T2,G1) ,此时 应始终满足: G1 > Ginit , G2 > G1。

即:Gm = Gf(T1, Ginit) > Ginit, Gf(T2, Gm) > Gm 。

也就是说,对于任意的 t 及 Gm, 都有 Gf(t, Gm) > Gm。一个简单的实现是,Gf(t, Gm) = a * t + Gm + b, a 和 b 为常数。

考虑 t = 0 , 则 b > 0 。 取 b = 1. Gf(t, Gm) = a * t + Gm + 1

方案对比:

  1. 顺序队列方案更简单,只需要一个任务,所有同步逻辑都在这个任务里,且流程更符合自然思维;但顺序队列方案容易阻塞,吞吐量有瓶颈。适合中小型业务量。

  2. 非顺序队列方案,吞吐量更优,不会因为某个消息消费阻塞;不过方案也更复杂一点,需要多个任务,额外全局存储,且同步逻辑较为分散,不容易直接理解。适合大型业务量。

【未完待续】

多表同步 ES 的问题的更多相关文章

  1. Logstash学习之路(四)使用Logstash将mysql数据导入elasticsearch(单表同步、多表同步、全量同步、增量同步)

    一.使用Logstash将mysql数据导入elasticsearch 1.在mysql中准备数据: mysql> show tables; +----------------+ | Table ...

  2. 京东云开发者|mysql基于binlake同步ES积压解决方案

    1 背景与目标 1.1 背景 国际财务泰国每月月初账单任务生成,或者重算账单数据,数据同步方案为mysql通过binlake同步ES数据,在同步过程中发现计费事件表,计费结果表均有延迟,ES数据与My ...

  3. SQL SERVER 数据库表同步复制 笔记

    SQL SERVER 数据库表同步复制 笔记 同步复制可运行在不同版本的SQL Server服务之间 环境模拟需要两台数据库192.168.1.1(发布),192.168.1.10(订阅) 1.在发布 ...

  4. ETL全量多表同步简述

    ETL全量多表同步简述 1. 实现需求 当原数据库的表有新增.更新.删除操作时,将改动数据同步到目标库对应的数据表. 2. 设计思路 设计总体流程图如下: 1.获取同步表名如下图: 2.循环迁移数据如 ...

  5. ETL全量单表同步简述

    ETL全量单表同步简述 1. 实现需求 当原数据库的表有新增.更新.删除操作时,将改动数据同步到目标库对应的数据表. 2. 设计思路 设计总体流程图如下: 注意点: 1.数据库合并时,选择正确的数据源 ...

  6. ETL增量单表同步简述_根据timestamp增量

    ETL增量单表同步简述 1. 实现需求 当原数据库的表有新增.更新.删除操作时,将改动数据同步到目标库对应的数据表. 2. 设计思路 设计总体流程图如下: 步骤简单说明: 1.设置job的执行属性,如 ...

  7. ETL增量单表同步简述_根据dateTime增量

    ETL增量单表同步简述 1. 实现需求 当原数据库的表有新增.更新.删除操作时,将改动数据同步到目标库对应的数据表. 2. 设计思路 设计总体流程图如下: 步骤简单说明: 1.设置job的执行属性,如 ...

  8. 使用logstash同步Mysql数据表到ES的一点感悟

    针对单独一个数据表而言,大致可以分如下两种情况: 1.该数据表中有一个根据当前时间戳更新的字段,此时监控的是这个时间戳字段 具体可以看这个文章:https://www.cnblogs.com/sand ...

  9. 官方使用logstash同步Mysql数据表到ES的摘抄

    官方文档地址:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html#plugins-inputs-jdbc ...

随机推荐

  1. 关于node中两个模块相互引用却不会死循环的问题

    关于node中两个模块相互引用却不会死循环的问题 node中是通过require来导入加载模块的,require有两个作用: 1.加载文件模块并执行里面的代码 2.拿到被加载文件模块导出的接口对象 现 ...

  2. Android 修改应用程序字体

    在网上搜索了相关资料,研究了两种算是比较快速的改变程序字体的方法,好,先来介绍着两种方法. 首先第一种方法是重写控件(以Textview为例): 1.Android在写程序的时候谷歌早已将所有字体都默 ...

  3. 用Python绘制全球疫情变化地图

    目前全球疫情仍然比较严重,为了能清晰地看到疫情爆发以来至现在全球疫情的变化趋势,我绘制了一张疫情变化地图,完整代码共 230 行,需要的朋友在公众号回复关键字 疫情地图 即可. 废话不多说,先上图 下 ...

  4. 前端学习笔记-H5

    H5常用标签及其属性: <a>标签做超链接: <p>段落标签,自带段间距和换行样式: <div>块标签,表示一块内容,没有具体语意,区别与p标签,块与块间没有间距: ...

  5. [PHP]听说随机数mt_rand()比rand()速度快,闲的无聊测试了一下!

    废话不说上码 //microtime() 函数返回当前 Unix 时间戳的微秒数.//当设置为 TRUE 时,规定函数应该返回一个浮点数,否则返回一个字符串.默认为 FALSE. <?php h ...

  6. windows下部署.netcore+docker系列一(安装linux (ubuntu18.4))

    1 下载 虚拟机和 linux 系统 版本是 ubuntu 链接:https://pan.baidu.com/s/1jTxdysoyOhSWD-Ea-7JIbg 提取码:iiad 2  首先要安装 虚 ...

  7. 数字签名---RSA算法

    保证信息在传输过程中的安全性:             保密通信.密钥交换.数字签名.   RSA算法 Diffie-Hellman算法 DSA算法 保密通信 √ × × 密钥交换 √ √ × 数字签 ...

  8. Java 添加、隐藏/显示、删除PDF图层

    本文介绍操作PDF图层的方法.可分为添加图层(包括添加线条.形状.字符串.图片等图层).隐藏或显示图层.删除图层等.具体可参考如下Java代码示例. 工具:Free Spire.PDF for Jav ...

  9. C语言字符数组超细讲解

    看到标题,有不少朋友会想:字符数组不也是数组吗?为什么要单独拿出来讲哩?莫非它是朵奇葩? 哈哈,确实,一起来认识一下这朵数组界的奇葩吧! 一.字符数组的定义.引用.初始化 大家好!我是字符数组,看我的 ...

  10. c++<ctime>中常用函数

    先说一下c++标准库并没有提供所谓的日期类型,而是继承了c的日期类型 <cmath>里面有些常用的函数,比如计时函数clock().获取系统时间的函数time(),下面就具体的介绍一下 1 ...