导读

在大数据时代,金融行业面临着日益增长的数据量和复杂的查询需求,尤其是跨库、跨集群的场景。在这种背景下,如何在保证数据一致性、高可用性的同时,实现业务的快速扩展与高效查询,成为了企业数字化转型的关键挑战。本文将深入探讨如何通过 TiDB 分布式数据库,结合自研的轻量级数据路由组件,解决海量数据管理问题。通过按时间维度拆分集群、动态数据路由与高效结果集归并等技术,企业能够实现数据无缝扩展,提升查询效率,并优化资源利用率。同时,本文分享了具体的技术实现,包括如何在多集群环境下进行数据路由、事务管理及跨集群查询,帮助企业在确保稳定性的基础上,支持更高的并发和更复杂的查询需求。


需求背景

某头部银行客户交易明细查询场景服务于全行对私、对公、海外客户,服务的时间范围覆盖了 2014 年至今超 10 年的超长跨度,致使原有保存在关系型数据库、NoSQL 数据库中的整体数据规模达到了可观的单副本 500TB,并且日均增量数据已超 1 亿。不仅如此,业务部门希望在保证服务质量、服务周期的前提下进一步提升差异化和业务敏捷能力,最终选择了具备高可用、高并发、易扩展、透明分布式(全局二级索引)、HTAP 等关键能力的 TiDB 分布式数据库作为其新的数据底座。

由于 TiDB 的设计哲学是在标准硬件基础上通过多副本+Multi Raft 分布式一致性协议实现可用性和性能间的平衡,且副本数至少 3 个起步,部分高等级业务甚至需要 5 副本提供更高的可用性。所以,单副本 500TB 迁移至 TiDB 后即使采用了多层压缩,因副本数的变多整体容量势必会有一定的增长(该场景 5 副本经过压缩约为原始数据的 2 倍)。面对如此大数据规模、面向全渠道/数亿客户、TPS 超过万级/访问延迟毫秒级、并且混合了客户号/账号/机构信息多维度访问的重要业务系统,为了在 SLA、扩展性、整体成本等方面寻求最佳平衡点, 整体数据架构采用了最贴合业务特征的方式做水平拆分,即按交易时间维度拆分为多个物理集群,不同集群可根据 SLA 等级对应不同的资源规格和副本数,并通过应用层的数据路由、联邦查询组件实现跨库背景下的 SQL 访问快速定位、结果集归并、路由策略管理等核心功能

本文的后续内容将聚焦在路由组件的设计与实现,以及与应用框架和 TiDB 的高效整合。

整体设计与选型策略

该案例的跨库访问场景包括基于时间范围的交易明细查询(分页/支持跳页,非分页)、收支记录查询(分页/非跳页)、收支汇总查询,以及基于交易流水号等非时间条件的单笔/多笔查询、单笔/多笔修改、单集群多表 DML 等,可以说涵盖了银行领域该类场景的所有业务。

梳理后共包括以下几类访问模式:

  • 按时间路由-分页追加归并 :对应各类分页查询场景,需要根据查询时间范围确定涉及的集群范围,再结合分页控制类信息(如起始记录数、每页记录数),定位当前分页所在的集群,并对跨集群场景下的结果集进行追加归并。除此之外,该类型还会涉及某分页数据散落在两个相邻集群、WEB 端常见的跳页(即需要动态感知查询范围所涉及的所有集群的数据分布)、以及非时间维度分页等更为复杂的场景。最后这种场景因为排序、分页的过滤规则与多 TiDB 集群的拆分规则不一致,需要更为巧妙的设计以降低多集群磁盘、网络 IO 的放大和稳定性、性能的影响;
  • 按时间路由-汇总归并 :对应汇总查询场景,如实时收支分析、月度/年度收支统计等,需根据查询时间范围确定涉及的集群范围,并将多个集群的查询结果在组件的结果集归并模块中按分组条件进行汇总归并;
  • 轮询路由-追加/汇总归并 :对应单笔/多笔查询、修改场景,由于输入参数能够定位数据的只有非交易时间类字段,如交易流水号,所以宜采用由近到远的方式依次轮询各个集群,直到所有记录都完成遍历,并根据业务类型选择结果集的归并方式,如查询类采用追加、修改类采用汇总。

在场景分类的基础上,还需要结合集群间数据生命周期管理策略的要求进一步细化相关设计:

  • 集群拆分和容量规划 :如“需求背景”章节所述,TiDB 多副本整体数据规模接近 PB 级,并且访问频度、SLA 等级随数据热度的降低也会显著下降,所以,按照热、温、冷水平拆分了三类集群,并在分片规则上引入了“相对时间”、“绝对时间”的概念。比如,热集群以开区间的形式存储了相对时间为最近一年的数据,即[now,-365),温集群以半开区间的形式存储了相对时间一年前到绝对时间为 20xx 年的少量数据,而冷集群则以固定区间存储了更长时间范围的数据。这样做的好处是只有热、温集群之间涉及数据转储 ETL 作业,简化了管理和运维成本,同时最为重要的热集群能保证容量相对稳定,而温集群则预留了足够的空间使整体方案具备较好的扩展性。如图 1 所示;

图 1-集群拆分方式

<center>图 1:集群拆分方式</center>

  • 集群间数据冗余设计 :热、温集群间的 ETL 作业会遵循“热集群导出[交易日期 < (now - 365d)]数据-->温集群导入-->热集群清理[交易日期 < (now - 365d)]”的时序,也就是数据转储是有时延的、并且存在因异常导致时延增大的可能。如果热、温集群“开口部分的相对时间”卡的过死,会出现 ETL 导入完成前客户查不到数据的“幻象”。所以,在设计上引入了名义时间、冗余时间、实际时间的概念,比如热集群名义最小时间(lowerValue)为[now - 365d],考虑到上述的时延因素,将冗余时间(overlapping)设定为 1 天,即该集群实际存储并提供服务的最小时间为[now - 365d - overlapping],相应的,温集群提供服务的最大时间也为该值,如图 2 中的绿色虚线。不难看出,只要冗余时间设定的相对合理、稳妥,就可以覆盖 ETL 产生的各类边界场景。

图 2:集群冗余设计
<center>图 2:集群冗余设计</center>

综上,考虑到该案例在业务上同时包含客户号/账号/机构、以及时间/非时间多种组合维度,在技术上对于数据分片的灵活性、数据冗余也有特殊需求。所以,传统的数据路由类组件(如 ShardingSphere)难以满足定制化需求,并且代码规模和维护成本也较高,最终采用了自研轻量级数据路由 SDK 组件的方式。

核心技术实现

该自研组件的设计理念与 ShardingSphere 低业务侵入的方式有所不同,本质上也是为了在“工期紧、任务重”的客观情况下寻求最优解。所以,整体思想是在现代化 Java 类应用经典的基于数据库连接池+ORM 框架+Spring 事务控制的基础上,通过少量编码(10 行左右),以 Builder 方式构建参数对象、并通过 Java 静态方法执行实际的 SQL 逻辑,再结合 TiDB 的 HTAP 能力做集群内的混合处理。

图 3:代码逻辑架构

<center>图 3:代码逻辑架构</center>

整体逻辑架构包括了应用框架、业务代码、以及以 jar 包形式集成在应用框架和业务代码中的数据路由 SDK。各部分简要说明如下:

应用框架

基于现有开发框架(如 Spring Boot)增加多数据源配置,包括多个集群的 Spring Bean 定义、以及继承 spring-jdbc AbstractRoutingDataSource 抽象类并实现 determineCurrentLookupKey 方法以提供多数据源的切换能力;

业务代码

主要变化包括 ORM 框架(如 Mybatis)的 SQL 语句部分按规则预留供路由组件改写的动态参数,以及调用方式改为通过路由组件提供的入口方法,对于仅访问热集群或无需改写 SQL 参数的场景,路由组件会调用业务侧上送的回调函数直接执行原始的 ORM 操作。对于跨集群的场景,由路由组件基于反射技术调用上送的 Mybatis mapper 中对应的业务 SQL,并将执行结果处理后返回调用方;

路由组件

  • 配置管理

    • 参数配置 :基于应用框架(如 Spring Boot)配置文件的定制化配置,比如是否启用、默认数据源(热集群)BeanName、动态数据源事务管理器 BeanName、跨库 DML 重试次数和间隔、SQL 执行时间打印标志、非交易时间类特殊查询场景的启用阈值、路由配置热更新配置等;路由配置:主要配置项包括每个集群的上下界的名义日期、日期类型(相对 or 绝对)、基于日期排序的集群顺序(为了按日期正序或倒序遍历)、版本信息等。配置信息会在应用启动时加载到 JVM 内存中,并通过版本信息来触发配置表变更时的热更新。表结构如代码 1 所示;
    • 路由配置 :主要配置项包括每个集群的上下界的名义日期、日期类型(相对 or 绝对)、基于日期排序的集群顺序(为了按日期正序或倒序遍历)、版本信息等。配置信息会在应用启动时加载到JVM内存中,并通过版本信息来触发配置表变更时的热更新。表结构如代码 1 所示;
    create table route_config
    (
        DATASOURCE_NAME  varchar(20) not null PRIMARY KEY COMMENT '数据源名称',
        LOWER_VALUE      varchar(8)  not null COMMENT '下限日期',
        UPPER_VALUE      varchar(8)  not null COMMENT '上限日期',
        LOWER_VALUE_TYPE int         not null COMMENT '下限日期类型: 1-相对值, 2-绝对值',
        UPPER_VALUE_TYPE int         not null COMMENT '上限日期类型: 1-相对值, 2-绝对值',
        LOWER_OVERLAPS   int         not null COMMENT '下游集群重叠日期天数',
        UPPER_OVERLAPS   int         not null COMMENT '上游集群重叠日期天数',
        STATUS           int         not null COMMENT '配置项状态: 1-正常, 2-失效',
        EFFECTIVE_DATE   varchar(8)  not null COMMENT '生效日期',
        EXPIRE_DATE      varchar(8)  not null COMMENT '失效日期',
        ORDER_BY_DATE    int         not null COMMENT '配置项日期排序',
        VERSION          int         not null COMMENT '版本号'
    ) comment = '路由配置表';
    ```

<center>代码片段 1:路由配置表</center>

  • 动态路由解析 :采用两段式路由的机制。第一段先通过请求参数中的业务类型、查询的日期范围,结合动态计算的每个集群当前的上下界时间,确定当前请求涉及的集群范围。第二段是进行更细粒度的路由,对于只涉及热集群的场景,直接透传返回并在不改写参数的情况下回调原始 SQL;对于多集群场景,则会涉及业务类型、是否按时间排序、正序/倒序、是否跳页多个维度的组合。如表 1 所示:

动态路由解析

  • 多数据源 SQL 执行

    • 透传回调 :当路由解析结果只涉及热集群时,直接透传返回、并在不改写 SQL 参数的情况下回调原始 SQL;
    • 多数据源执行 :按路由解析排序后的结果在多个集群依次执行 SQL,执行前涉及 SQL 参数的改写(包括日期、分页,即业务参数到集群级物理参数的改写)、基于 ThreadLocal 切换数据源,并通过反射的方式执行实际的 SQL 方法。非查询类场景还涉及事务管理,同时,考虑到业务操作幂等性的特点,为降低整体复杂度和维护成本,对于个别的跨集群 DML 操作并未引入应用侧的分布式事务。最后会按集群维度将结果集追加合并;
  • 结果集归并:

    • 分页查询场景 :对于按交易日期排序的简单场景,只需将多个集群的结果集按顺序追加归并即可。对于非交易日期排序的复杂场景,则需要根据排序字段、各字段的正序/倒序规则,对集群内局部有序的所有结果集进行整体重排序,算法模型采用的是稳定性较好的插入排序;
    • 聚合查询 :对于可以汇总归并的算子,如 sum、count、max、min 等,会将多集群的全局结果集按照分组字段进行汇总或比较;对于avg类算子,则需要先将多个结果集的 sum、count 结果分别累加,再用“total sum / total count”得到最终的 avg 结果;
    • 单笔/多笔查询或修改 :单笔类操作由于只涉及一个集群,所以无需二次归并;多笔场景采用上述类似的方式做追加或汇总归并。

总结与展望

通过该组件与 TiDB 分布式数据库的有效结合, 可以实现近乎无容量上限的超大规模数据管理 ,尤其是对于重要程度高、吞吐量大、业务敏捷性强、数据冷热特征明显的业务系统。不仅能够支撑面向内外部客户业务无损的多维度、不受分片键制约的灵活高效访问,还可以有效控制和平衡单集群的负载、容量、资源利用率、稳定性等关键指标,在不增加过多复杂性的前提下实现更强的整体扩展能力。

当然,组件的现有功能更多是聚焦在当前的客户场景,未来可以按需在功能性、易用性、高性能等方面进一步优化和提升。以实现在无损支撑现有业务功能的基础上,大大提升系统在数智化背景下的业务扩展、迭代能力。


PingCAP
1.9k 声望4.9k 粉丝

PingCAP 是国内开源的新型分布式数据库公司,秉承开源是基础软件的未来这一理念,PingCAP 持续扩大社区影响力,致力于前沿技术领域的创新实现。其研发的分布式关系型数据库 TiDB 项目,具备「分布式强一致性事务...