WB
WB
文章目录
  1. 1. 分库分表
    1. 1.1. 亿级订单系统分库分表技术方案
      1. 1.1.1. 一、需求背景
      2. 1.1.2. 二、业务现状
      3. 1.1.3. 三、技术选型
        1. 1.1.3.1. 1、分库分表和分布式数据库对比
        2. 1.1.3.2. 2、分库分表方案对比
      4. 1.1.4. 四、唯一ID方案
        1. 1.1.4.1. 1、数据库自增或者Redis自增
        2. 1.1.4.2. 2、Snowflake算法
        3. 1.1.4.3. 3、Random算法
        4. 1.1.4.4. 4、美团分布式ID生成-Leaf
        5. 1.1.4.5. 5、自定义业务ID
      5. 1.1.5. 五、分片键和分片策略选择
        1. 1.1.5.1. 1、时间范围(Range)
        2. 1.1.5.2. 2、租户ID(List)
        3. 1.1.5.3. 3、自定义业务ID(Hash)
      6. 1.1.6. 六、最佳实践
        1. 1.1.6.1. (一)改造点梳理:
        2. 1.1.6.2. (二)实施步骤:
      7. 1.1.7. 六、总结

(no title)

分库分表

亿级订单系统分库分表技术方案

一、需求背景

随着公司业务的发展,订单系统的数据量和访问量日益增长,单库单表的架构已经无法满足我们的需求。主要面临以下问题:

  1. 数据量大:单一数据库存储所有订单数据,导致数据量过大,影响查询效率。
  2. 并发压力大:大量用户同时访问系统,产生高并发请求,对数据库造成较大压力。
  3. 扩展性差:当需要对订单表进行改动时,大量的数据造成表结构修改时间变长

二、业务现状

  1. 订单表有主键orderId和唯一索引orderNo,orderId依赖数据库自增,orderNo自定义生成并且后4位为类用户id,内部场景用orderId进行数据传递,外部场景用orderNo进行数据传递

解决方案:去除数据库自增,将orderId和orderNo进行合并,将自定义编号作为唯一主键,内外部场景全部采用orderNo进行数据传递

  1. 订单表超过10个索引,部分索引用于C端场景,部分索引用于B端场景

解决方案:将OLTP中的订单数据同步到OLAP数据库中;确保所有C端场景能够命中分库分表的分片键;将B端分析场景进行剥离,例如查询订单列表,在OLAP数据库中进行查询,再根据订单主键CRUD时,再走OLTP数据库

  1. 订单表中存在部分老数据,老orderNo没有采用后4位是类用户id,并且老orderNo中可能存在字母

解决方案:借助Flink将老订单编号数据进行清洗(数据清洗技术方案另行商议),统一采用现有订单编号规则

  1. 订单表存在一些拓展表,比如订单位置、订单申诉记录等,有些采用orderNo进行关联,有些采用orderId进行关联

解决方案:和第1点一样,统一采用主键进行关联,并且需要保证关联字段为分片键。如果有分库,最好将拓展表也进行分库分表,并且需要保证拓展表的分库分表规则和原表一致

  1. 订单表中字段过多,有计费、开票、退款等非核心数据

解决方案:考虑纵向拆分,可以考虑这几个方面:是否核心、更新是否频繁、字段长度是否过大

三、技术选型

1、分库分表和分布式数据库对比

理解普通数据库、分布式数据库、云原生数据库、云原生分布式数据库区别

  • 普通数据库:通常采用传统的垂直架构,由单一的数据库服务器提供数据存储和查询服务。
  • 分布式数据库:采用水平分片的方式,将数据分散到多个数据库服务器上,实现数据的高可用性和可扩展性。
  • 云原生数据库:基于云计算技术构建,具有容器化、微服务等特性,可以快速部署和扩展。
  • 云原生分布式数据库:结合了分布式数据库和云原生数据库的特点,实现高性能、高可用、可扩展的数据库服务。
产品名称 PolarDB-分区表 OceanBase PolarDB-X TIDB 传统分库分表
开发团队 阿里-阿里云团队 阿里-蚂蚁团队 阿里-阿里云团队 PingCAP团队 根据具体实现和需求可能有多个开发团队参与
类型 云原生数据库 分布式数据库 云原生分布式数据库 分布式数据库 传统数据库
应用场景 HTAP(行列混合) HTAP(行列混合) OLTP(行存索引) HTAP(行列混合) OLTP(行存)
产品优势 一写多读、共享分布式存储、计算与存储分离、自动读写分离、高速链路互联、数据可靠性和强一致性、维护成本很低、支持列存索引 可满足金融级容灾标准、水平扩展、支持多租户资源隔离、支持列存索引、支持空间索引 支持数据强一致性、支持水平扩展、列存索引(灰度中) 水平扩展、实时HTAP、行列混合 灵活性强、不需要进行数据库迁移
产品劣势 分区算法只能采用单一key、行存节点进行了分区,列存节点也会分区、分区表不支持空间类型 成本比PolarDB贵一倍、涉及到数据库迁移 成本较高、涉及到数据库迁移、列存索引还在灰度中、OLAP依赖并行计算、目前不支持mysql8.0 不属于阿里云体系、运维成本较高、涉及到数据库迁移、不支持空间类型 考虑分布式事务兼容性、需要集成中间件
分片原理 推荐使用分区表代替分库分表 基于分布式技术的分片、无共享架构实现数据的分散存储和处理。 基于MySQL内核的分片、通过特定的存储计算分离架构实现数据的分散存储和处理。 基于TiKV内核的分片、通过Raft协议实现数据的一致性复制和分散存储。 根据业务需求和自定义的分片规则进行数据分散存储和处理;可以根据具体的实现方式采用不同的分片算法和策略。
文档地址 PolarDB OceanBase PolarDB-X TIDB ShardingSphere

2、分库分表方案对比

中间件名称 ShardingSphere-JDBC ShardingSphere-Proxy Mycat
类型 客户端分表 数据库代理 数据库代理
开发团队 Apache Apache Mycat社区
优势 轻量级、易于集成、支持多种数据源、提供分布式事务和读写分离功能、 功能丰富、支持多种数据源、提供分布式事务、读写分离、分布式主键生成等功能、业务无侵入、支持异构语言 开源免费、功能完善、支持多种数据源、提供分布式事务、读写分离、分布式序列等功能、适用于各种项目
劣势 代码改造、集成分布式事务困难、配置较为繁琐 性能损耗略高、需要单独部署维护 社区支持有限、维护和更新不及时、性能损耗略高、需要单独部署维护
总结 适用于对性能要求较高,代码侵入性可接受的OLTP应用 支持异构语言,独立于应用程序部署,适用于 OLAP 应用以及对分片数据库进行管理和运维的场景。 开源免费,但社区支持有限、维护和更新不及时。独立于应用程序部署,适用于 OLAP 应用以及对分片数据库进行管理和运维的场景。

四、唯一ID方案

1、数据库自增或者Redis自增

优点:最简单。 缺点:单点风险、单机性能瓶颈、暴露业务量

2、Snowflake算法

image.png 上图为二进制bit位,总长度为2^64次方,刚好Long可以存下。10进制字符长度为19位

优点:高性能高可用、易拓展 缺点:需要独立中心节点,时钟回拨可能造成ID重复、没有带业务标识、41位

3、Random算法

例如UUID

优点:简单 缺点:生成ID较长,有重复几率

4、美团分布式ID生成-Leaf

Leaf-snowflake:通过集群部署,自动剔除时钟回拨的节点,可以避免Snowflake时钟回拨ID重复的问题 Leaf-segment:在数据库自增方案上进行了改进,加了批量生成和本地缓存,解决了自增性能瓶颈和单节点风险

优点:高可用、解决了自增和Snowflake部分缺点 缺点:弱依赖ZooKeeper,需要独立部署Leaf系统

5、自定义业务ID

订单类型(1)+ 业务类型(1) + 时间戳yyMMddHHmmss(12) + 随机数(4) + 类用户id(后4位,不足用0代替)

优点:单机、有业务属性、有用户标识、有顺序性 缺点:过万QPS情况下容易出现ID冲突

五、分片键和分片策略选择

1、时间范围(Range)

适合数据有明显的时间属性,例如日志表、记录表、统计表等

优点:天然分片,好扩展,方便范围查询和排序操作,也可以方便数据归档 缺点:数据可能分布不均匀,易引起单机负载过大的问题

2、租户ID(List)

适合数据具有明显业务标识,例如Saas系统中的表按照租户ID、订单表按照订单类型、工单表按照工单类型

优点:可以根据具体的属性值进行分片,方便根据属性值进行查询和过滤操作 缺点:分片规则不好维护,可能产生数据倾斜,数据不好扩容

3、自定义业务ID(Hash)

常用于互联网C端场景,例如根据用户ID分片,可以轻松的根据用户ID查找用户所有数据。需要根据业务体量做好数据分片规划

优点:数据分布均匀,可以实现负载均衡 缺点:数据扩容困难,范围查询效率较低

六、最佳实践

结合上诉技术选型对比,并从数据迁移成本、可维护性考虑,最终决定采用ShardingSphere-JDBC分库分表方案

订单数据流转图.jpg

(一)改造点梳理:

1、数据库结构改造

  • 创建新订单表,将orderNo和orderId进行合并,统一采用自定义编号,自定义编号长度会超过long最大值,需要采用string类型
  • 将有orderId字段的相关表进行改造,统一通过自定义编号进行关联
  • 将订单表中多余字段进行剥离(待定

2、代码改造

  • 区分OLTP请求和OLAP请求,并从数据源进行隔离
  • 引入ShardingJDBC,并配置分片键、分库分表数据源、分布式事务代理
  • join查询改造,分表后的关联查询必须带有分片键

3、数据清洗

  • 对不符合规则的老订单号进行清洗,生成新订单号,并记录新老订单号关系
  • 对有关联orderNo的表进行清洗,将老订单号替换成新订单号
  • 对有关联orderId的表进行改造清洗,将orderNo注入到有orderId的关联表中

4、数据分片

  • 现在日订单高峰期20W,按照当前业务5倍进行规划,并预计10年订单量。日订单量100W,年订单量3.6亿,10年订单量36亿
  • 允许单表最大订单量约5000W,36亿/5000W = 72,为了满足一致性hash原则2^n,取64张表

5、分布式事务Seata接入

参考SpringCloud多数据源接入Seata和ShardingJDBC最佳实践

(二)实施步骤:

1、新建库、表结构

在新订单库中,新建各个表结构

2、批量生成新订单号

离线生成新订单号并记录新老订单号关系表order_new_relation(包含order_id、old_order_no,new_order_no)

3、新建Flink同步作业

old_order表当成源表、order_new_relation当成维表、new_order表当成结果表

同步订单表:

  • 如果有老订单编号,则去order_new_relation查找新订单编号
  • 根据订单表的order_no分片规则,确定在哪一张表,将订单表的order_no同步到新订单表的orderId中

同步订单拓展表:

  • 判断拓展表的主键是orderId还是orderNo,如果是orderId,则需要通过order_new_relation找到order_no,并根据order_no进行分表
  • 如果是orderNo,则判断是否是老orderNo,如果是,则去order_new_relation找到新order_no,并根据新order_no进行分表

六、总结

本方案结合一段时间内未来业务规模、运维成本、服务器成本等多种因素进行考虑,并分析了分区表、分布式数据库、分库分表的区别和优劣势。也简单介绍了一下分库分表需要考虑的唯一ID、分片键、分片算法等问题,并结合实际业务简单梳理了一下改造方案。本文采用停机迁移分库分表方案,如果想要不停机迁移,可以参考大众点评分阶段实施。 备注:如果只分表不分库也能满足需求的话,分区表其实也是一个不错的选择,不用引入其它第三方组件,mysql就原生支持,并且对开发比较友好。但是PolarDB分区表不支持多个分区键用同一个分区规则,并且PolarDB列存数据也会按照行存的进行分区,有一定的性能损耗。

支持一下
扫一扫,支持一下
  • 微信扫一扫
  • 支付宝扫一扫