1. 引言
微服务架构时代,研发人员一般会基于领域进行服务划分与编排,服务通常会被划分为基础服务与业务服务。其中,基础服务会更抽象一些,一个基础服务会对应一个数据库模型。微服务架构有其自身的优缺点,而其缺点之一就是每个服务的数据都是保存在独立的数据库中,数据与数据之间相互隔离。
比如研发任务需要某一个人今天下单的所有详情信息,它可能就需要用户信息,订单信息,账户信息等,会涉及到多张表的数据聚合,有的时候还可能需要对数据做下转换才能满足业务需求。
1.1 问题与挑战
比心目前主流的服务架构方式也是微服务架构,业务在针对多表查询和聚合上也存在着诸多痛点,归纳下来主要包含两个方面:功能和性能。其中,
功能方面:
- 无法实现全文检索。根据内容中字段进行全文检索,现有数据库无法支持;很多时候由于对一些字段的不确定性,业务方会通过json形式将一些字段进行统一保存,这就造成了字段的不可直接检索。
- 无法新增业务场景字段信息。很多时候在某些场景下,业务会在查询时生成新字段属性,并给予新字段做排序等操作,这个是无法实现的。
性能方面:
-
多表联合查询性能差。在某些场景下,需要对多个表数据进行关联查询,因此一些join语句就不可避免地出现,join很容易出现全表扫描的情况,影响DB性能,在大数据情況下join语句是禁止存在的。
-
多种业务查询场景下,无法保证字段都存在索引,导致查询性能差。很多场景下(如内容运营平台) 需要对很多字段进行检索查询,如:同时对内容的用户昵称、动态ID、创建时间、加精、标签等条件,而这些字段在对应的数据库表中可能没有相应索引。
-
多维度排序支持成本高。很多时候数据查询会遇到多维度排序问题,如评论列表会按照点赞数据先进行排序,点赞数相同的再按照创建时间进行排序,一般点赞数据和评论创建时间都是独立成表的,支持的性能成本较高。
针对多表查询的痛点,通常业务的解决方式就是创建冗余表,业务通过建立一张冗余了若干张表字段的宽表,这种方式费时费力,复用性差,每接一个新业务都需要建立这样一套新的冗余表。对业务来说,当宽表中字段发生变更时,就需要修改对应表的结构以及代码逻辑,这种方式在成本及研发效率上是极其不友好的。当然,业务上还有另外一种相对较通用的解决方式,也就是通过创建独立的数据同步服务,也就是宽表服务,宽表服务的能力包括提供多表多字段聚合,数据即时同步转换等能力。宽表服务相对于mysql创建冗余表,其本身会存在着诸多挑战,主要包括:
-
如何保证DB数据同步到存储层过程中,数据不丢失,尽可能地实时。
-
数据同步服务中会运行多个业务的数据同步任务,如何保证多任务之间的资源相互隔离。
-
数据同步服务在面对多种业务场景下,如何满足不同业务对数据加工改造的需求。
-
如何保证调整业务任务配置后,重新同步全量数据的过程中,不影响业务的数据查询功能。
针对上述问题特点,以及面对的挑战,我们在数据同步领域以及数据聚合方面给出了一些解决方案(具体在第四章中Nike宽表服务),保证服务的灵活性以及数据的一致性,希望这些思考和实践经验能对大家有所帮助或者启发。下面我们先讲解业界的宽表架构,然后开始逐步讲述我们宽表架构的演进过程。
1.2 业界宽表架构介绍
宽表架构是一种解决微服务应用中跨服务数据关联问题的有效方法,它可以提高数据查询的效率和灵活性,减少数据冗余成本。宽表架构的核心是数据服务,它负责将多个基础服务的数据按照预定义的规则进行聚合和转换,形成一个包含多个维度和指标的宽表,存储在一个高性能的数据产品中,如Elasticsearch。数据服务可以根据业务需求提供多种查询接口,支持复杂的过滤、排序、分页、聚合等操作,满足不同场景的数据分析和展示需求。
图1 和图2 展示了一个宽表服务在业务场景中所处的位置以及宽表数据流向,其中包含了四个基础服务:用户服务、订单服务、商品服务和支付服务。这些服务的数据分别存储在不同的数据库中,如果要进行跨服务的数据查询,就需要进行多次网络请求和join操作,这会导致性能下降。为了解决这个问题,引入了一个数据服务,它将这四个基础服务的数据通过配置文件定义好关联关系,并定期或实时地将数据同步到Elasticsearch或kafka中,形成一个包含用户、订单、商品和支付信息的宽表。这样,业务就可以直接通过数据服务查询或消费宽表中的数据,无需再进行跨服务的join操作,提高了查询效率和灵活性。
图1 宽表数据架构
图2 数据流向示意图
2. 比心宽表架构现状
当前比心对宽表服务的业务功能性需求和非功能性需求主要有以下几点:
2.1 业务的功能性需求
- 支持多库多表的关联关系配置。
- 支持数据库中数据的聚合和转换。
- 支持数据同步到Elasticsearch,kafka等数据中心。
- 提供通用的数据查询能力或工具。
2.2 业务的非功能性需求
- 保证数据的一致性,避免出现查询时数据不一致的情况。
- 每个业务之间的同步任务要独立,资源使用时相互隔离。
基于上述业务的功能和非功能需求,我们推出了第一版的宽表服务:雅典娜数据服务。
3. 宽表服务1.0-雅典娜
雅典娜(Athena)数据服务,主要的职责能力就是将多数据源的数据抽取后,聚合转换并存储到数据服务中,供业务查询消费。
**
**
3.1 雅典娜的系统架构
宽表服务的主要功能点包括:多表之间的关联关系配置,数据库中的存量数据同步和增量数据同步,数据同步过程中数据的二次转换,以及聚合后的数据存储等。针对宽表服务的主要能力,雅典娜系统是如何架构的呢?
- 雅典娜提供控制台,协助业务配置多表之间的关联关系,以及同步规则等。
- 雅典娜借助于Flink离线同步服务,将任务关联的数据库中存量数据快速的同步到自己的缓存系统中,用于后期的数据聚合使用,降低数据聚合时对业务数据库的压力。
- 雅典娜对数据库中变更数据的同步更新能力,主要是通过监听并消费 maserati 服务发出的kafka消息,并且为了实现任务之间的资源隔离,每个任务都运行在一个独立的增量同步服务中。
- 为了支持业务对表字段的数据转换,雅典娜在增量同步模块中通过提供自定义逻辑的方式,协助业务在每个任务中二次处理同步的数据。
- 雅典娜提供多输出源能力,支持将聚合后的数据同步到Elasticsearch,kafka等输出源中,供业务查询消费。
图3展示的就是雅典娜服务的功能逻辑结构,包含各种功能,配置以及同步引擎。
图3 雅典娜功能逻辑结构
图4 展示的是雅典娜的整体架构,从图中可以看到,雅典娜分为多个核心模块:Controller(控制台),Config(配置),HBase(数据缓存),Flink(存量数据同步),inc(增量数据同步),数据同步的输出模块等。那么雅典娜的架构是如何解决服务的稳定性和数据一致性问题的呢?
图4 雅典娜系统结构
3.1.1 稳定性
为了更好的保证数据同步的稳定性,以及数据同步中对业务数据库的反查影响,雅典娜架构上做了以下工作:
-
任务部署互相隔离
雅典娜的每个任务都对应一个独立的增量同步服务,diff服务以及缓存表数据,这样能从物理上避免服务之间的数据同步资源竞争。
-
HBase缓存表的全量数据
雅典娜将HBase作为所有主表的“从表”,数据变更时,反查的数据都来自HBase,避免数据聚合时反查业务主表,影响业务数据库性能。
3.1.2 数据一致性
雅典娜服务逻辑包括监听消息,数据抽取,数据聚合等逻辑,为了提高消息处理的速度,代码逻辑上会存在并发操作,那对于业务频繁更新的数据如何保证更新顺序和消费顺序一致呢?答案就是雅典娜在监听到消息变更后,会基于变更数据的主键id值,通过Math.abs(hash(id))取余的方式找到对应的缓存队列,将数据存放到队列中,然后线程按序消费队列数据,保证数据更新和消费的顺序性。
3.2 现有架构存在的痛点
3.2.1 稳定性
-
数据存在丢失
在增量同步服务和DB数据之间存在一个 HBase 缓存服务,缓存服务主要是存储binlog日志、kafka数据流。实际使用时,业务数据容易出现数据丢失的情况。
-
Flink集群不稳定
雅典娜服务的全量同步能力依赖Flink集群来抽取全量数据,而测试环境的Flink集群不太稳定。
-
多业务查询相互影响
ES数据查询没有针对到业务级别、索引级别做限流熔断,导致个别业务会拉高其他业务查询的RT。
3.2.2 资源成本
-
机器资源存在浪费
雅典娜每个任务都运行在独立的服务上,每个服务三个环境共需要4台机器,实际运行下来,大部分雅典娜宽表任务的流量很低,机器资源使用较少。
-
人工运维成本高
对于业务特殊的数据处理要求,会需要平台方帮忙开发数据转换逻辑,并重新发布上线;雅典娜服务中每个业务任务就是一个独立的服务,在漏洞包修复发版的场景下,处理的成本较高。
-
业务查询成本高
没有提供大众化的查询语言,比如SQL ,需要平台方帮助业务方查询数据等。
4. 宽表服务2.0-Nike
雅典娜宽表服务在运行一段时间后,还存在一些痛点需要解决,在当前架构上改进的成本较高,需要继续演进新的服务架构,为此,在借鉴了雅典娜服务的优点,重新调研了业界关于宽表的架构及功能体验,开发了新的宽表服务Nike。
4.1 雅典娜痛点的解决
在保留宽表服务的核心能力基础上,针对雅典娜服务在稳定性,资源成本等方面的痛点,Nike 做了以下工作来解决上述的痛点:
- Nike 使用了阿里开源的DataX离线同步工具,在保留原有功能的基础上,提供了更稳定的工具,并且支持基于条件的SQL同步工具。
- Nike 架构上去除了雅典娜中间层的缓存服务HBase,通过限流,索引的方式在保证业务数据库稳定的基础上,降低数据发生不一致的可能性。
- Nike 基于服务维度对查询的请求做了限流能力。
- Nike 通过机器分区的方式,将不同业务的任务放到不同的机器上运行,在保证资源不会存在争夺的情况下,提高资源的利用率。
- Nike 提供更多的运维工具,包括数据差异对比,数据自动订正等,降低了开发方的运维成本,也提高了业务使用的便利性。
- Nike 提供SQL的查询方式,供业务在页面直接查询ES数据。
4.2 Nike系统架构
宽表的功能点以及数据同步流程,我们在雅典娜服务章节已经阐述过,图5 展示的是Nike 的系统结构图,其中Nike的核心模块包括:
- 全量数据同步模块(full)是对数据库中的数据做存量同步,主要利用了DataX这个离线数据传输工具,将数据库中的数据分批同步到数据中心。
- 增量数据同步模块(inc)是实时同步数据库中变更的数据,增量数据同步是通过订阅kafka消息,根据消息的内容去源端数据库进行查询,然后对查询到的数据进行聚合操作并存储到目标端,保证数据的实时性。
- 差异比较模块(diff)是补偿的工具模块,其通过滑动窗口配置,定期比对数据库数据和已经落到ES中的数据的差异,针对不一致的数据提供发现订正能力。
- 中控台模块(controller)是控制台模块,其为业务提供数据订正,分区迁移,全量同步等自动化工具。
图5 Nike系统结构
4.3 技术挑战及解决方案
我们在第一章中,提出来了四个挑战,下面我们将重点讲述,Nike是如何解决这些问题的:
4.3.1 数据一致性
-
ES 版本控制
数据从DB到ES存储会经过多个环节,包括业务逻辑的并行执行,为了保证数据更新的有序性,Nike借助于ES为每条数据提供的版本号属性,将数据产生时的时间戳作为ES版本号,保证查询到的数据是最新修改的值。
-
diff 定时检查更新机制
无论架构上多么的严谨,依然可能会存在某些问题导致数据不一致的情况,为了保证数据在异常情况下也能尽可能的同步,为此,Nike提供diff服务,用于定时对比DB数据和ES数据的差异,提前发现不一致的数据并快速修复。通常上,接入宽表的业务对数据有下述三种场景的要求:
-
- 对一些数据不是太敏感但是查询条件复杂的,需要快速查询的业务,其实无需数据的强一致性。
- 对数据敏感,并且数据一致性要求高,但是能容忍一小段时间的数据不一致。
- 对数据敏感,并且不能容忍数据不一致的情况。
-
那么,怎么解决以上三种问题的呢,事实上,有些情况,我们不知道意外的发生,所以对于意外的bug(或者网络等情况)导致的数据不一致的情况我们并不能很好的发现,所以,我们需要一个数据对比服务,在数据对比服务中,基于某段时间,我们提出三种级别的数据对比:
-
- 对比这段时间的数据数量。
- 对比这段时间的数据数量和数据版本。
- 对比这段时间的数据数量,版本,以及数据。
-
以上三种对比维度,可以满足绝大多数的业务对于数据的要求。
-
kafka消息回溯机制
由于我们采用了kafka作为binlog的存储,所以我们可以借助kafka的消息回溯的功能,在全量数据同步后,会让增量的消费从全量数据同步开始前一分钟开始消费,等增量消费到最新offset后,数据就一致了。
图6 消息回溯时间线
4.3.2 稳定性
-
多分区执行
Nike服务将多个任务放到同一台机器上执行,保证机器资源的合理使用,为了防止任务之间相互影响,Nike支持机器分组分区的能力,每个机器分组是单独的分区,每个任务可以关联一个分区,对于相同业务的任务是分配到同一个分区上执行。如果配合上Nike自带的监控功能,可以随时调整任务所在分区,以保证任务的高效执行。
-
增量和全量任务执行隔离
全量任务就是将数据表中数据全部同步一份,这种情况发生的频率较少,通常发生在新的任务上线或者业务变动了输入输出字段属性等情况,这时就需要执行全量任务同步数据,全量任务的数据量通常是较大的,需要长时间的占用资源,为了保证全量任务不影响增量任务的执行,Nike将全量任务的执行独立出来,隔离全量任务和增量任务的资源。
4.3.3 工具支持
-
数据订正
DataX提供条件离线更新能力,通过条件离线更新数据,业务选填参数后,自动开启数据订正,就可以完成错误数据订正。
-
插件更新
业务通过自定义插件的方式,对数据字段做再次的解析修改转换,满足了不同业务的差异化需求,而宽表服务也会每分钟定时更新插件。
-
SQL查询
接口查询方式以及ES原生client查询方式,业务对于这种查询方式使用不方便,为此,Nike提供SQL查询方式,SQL是大众化的编程语言,业务使用上会更方便。
4.3.4 版本概念
对于Nike服务来说,如果业务方需要修改字段,修改完后,会创建一个全新的索引,然后全量同步一份新数据到新索引中,原先的数据依旧在之前的索引中,如果业务方对于修改的字段验证没问题后,会把索引切换到新索引中。
采用这样的方式,我们把业务自行修改逻辑后风险降低到了最低,业务验证完新逻辑后,才会把数据切换过去,在没切换前,业务修改的逻辑不会对现有数据造成影响。
4.4 最佳实践
4.4.1 ES最佳实践
- 如果你的字段通常用于排序、聚合和term-level查询,比如term查询,请将其设置为keyword类型,这将提升你的查询性能。
- 如果你的字段需要range操作,请将其设置为数字类型(long,integer)。
- 如果你的字段需要进行字符串匹配,请将其设置为text类型。
笔者在这里粗略写下原因,想要具体了解可以锁定中间件团队后续ES的原理文章。
ES为了让数字类型更好的支持range查询,里面底层实际上使用了一个bkd tree的数据结构,如图7所示。
图7 bkd tree的数据结构
它是一种特殊的k-d树,可以索引多维点数据。它可以通过快速找到相关的文档ID来提高数值类型的范围查询的效率,但是提升了范围查询的代价就是它不按顺序存储文档ID,所以不能使用跳表来合并结果。它还需要比倒排索引更多的内存和磁盘空间。
Nike 服务已经通过图形化的方式简化业务任务的配置流程,下面介绍下如何在Nike平台快速地配置一个宽表任务。
4.4.2 基础规则配置
宽表任务配置主要分三步:基础信息配置,插件配置,表规则配置,最后提交任务,等待管理员审批后,开启全量数据同步,以及增量数据同步。
图8 宽表任务的基础信息配置
图9 插件maven坐标配置
图10 关联表配置
图11 规则条件配置
图12 XML 配置关系
图13 完成任务配置
4.4.3 全量数据同步
图14 全量同步页面
4.4.4 增量数据同步
图15 增量同步页面
5. 总结和展望
Nike 宽表服务目前已经服务多个业务线,接入了40+任务,运行了1年多时间。Nike 完善的监控和流控能力,以及自动化的运维工具为业务提供较好的使用体验。当然,Nike还有很多需要改进的地方,比如支持多种数据源的接入转换成ES数据存储;提供更好的自动化运维工具,如调试工具等。
5.1 后续规划
-
新增调试功能,为了让业务方更好的使用平台,无需找管理员排查问题。
-
新增对kafka数据输入源的处理功能。
-
支持更多种ES类型接入。
-
新增业务方自己的ES集群接入。
随着越来越多的业务接入Nike平台,我们相信Nike将能够更有效地为这些业务创造价值。我们将始终遵循为业务同仁提供更实用的工具产品的原则,帮助更多的业务同仁提高研发效率并增强服务的稳定性。