1.亿级积分数据分库分表:总体方案设计

项目背景

        以一个积分系统为例,积分系统最核心的有积分账户表和积分明细表:

  • 积分账户表:每个用户在一个品牌下有一个积分账户记录,记录了用户的积分余额,数据量在千万级
  • 积分明细表:用户每次积分发放、积分扣减、积分退款、积分过期都会产生一条积分明细,积分明细的数据量很大在亿级别

        随着业务的增长、时间的推移,积分明细的数据量越来越多,单表数据量太大,产生很多问题:慢SQL,加字段、索引比较耗时(虽然可以用MySQL8.0的新特性INSTANT算法加字段),统计查询很慢,数据库CPU压力大等

一、为什么要选择分表

        目前积分系统的积分明细数据现存几亿条,每日新增积分明细数据大几十万,并且随着活动的接入还在逐渐递增,慢慢的带来了很多问题,需要进行优化处理。

        历史数据归档不行吗

        因为系统积分规则规定积分有效期两年,并且积分扣减、退款、过期等操作需要对积分明细溯源,最近两年的积分明细数据也有几个亿,即使将两年前的冷数据归档,也无法解决积分明细单表几亿数据量的问题。

        根据积分现有数据量以及日增长量进行评估,未来3-5年内单数据库实例能够满足积分数据的存储,并且积分系统的写入TPS单实例可以支撑,本次积分决定只分表不分库。

        什么情况适合用数据归档?

        如果能把历史冷数据归档,热数据一两亿左右、读写并发不太高的情况下,利用好数据库索引、数据库配置高一些完全可以不用分表,采用历史数据归档也可以解决很多问题。但是大表还是有一些其他的问题,比如加字段比较耗时。

        分库分表有很多问题

        网上动不动就说超过2000万数据就要分库分表并不太对,在单表数据一两亿、并发不是很高、利用好数据库索引的情况下,MySQL数据库配置高一点是完全可以扛得住的。

        而且能不分库分表就不要分库分表,分库分表会产生很多问题,没有其他优化手段了再进行分库分表。分库分表可能会产生的问题如下:

  • 分布式ID问题
  • 分片键选择问题
  • 分库分表算法问题
  • 容量不够了扩容问题
  • 分布式事物问题
  • 统计查询分析问题
  • 数据迁移问题
  • 灰度验证问题
  • 数据校验问题
  • ................

二、分库分表组件

        分库分表组件选择Sharding-JDBC,因为目前该项目文档较为丰富、社区活跃度高、无中心化、性能相较于proxy方式性能更好,对于开发来说使用更为灵活可控。官网地址Apache ShardingSphere

三、分多少张表,分片键和分片算法

        需要拆分的表:point_info(积分明细表)

        根据目前的数据量、单日新增的数据量来进行分析,计划分为128张表,未来3-5年内单表数据尽量不要超过2000万,后期分表数据量大了可以进行历史数据归档。

        拆分后的表为point_info[0-127],采用user_id作为分片键,选取user_id后四位取模定位到具体的分表

        table后缀 = (user_id后四位) % 128

        如果怕user_id的尾数不均匀,可以将hashcode(user_id)%128来计算分表下标

        分表数量为啥是128,不是127或者100?

        我自己的理解如下:

  • 设置为2的次幂,是程序员的习惯;
  • 好处是求余运算,可以用 num & (128-1),按位与运算求余数比除法速度快。

        如果还有其他的好处欢迎指正

四、分布式事务?

        因为只进行了分表,没有进行分库,所以没有分布式事务问题。

        假如后面进行了分库分表,可以通过将相同用户的积分账户表和积分明细表分到同一个分库中,来避免同一个用户操作账户和明细的分布式事务问题。

        shardingsphere里面也有一些分布式事务的支持,比如XA,Seata框架的AT模式等

五、分布式ID

        原来单表时主键采用MySQL自增id,分表之后再使用自增id会导致不同表主键值重复,可以使用雪花算法、美团的Leaf等生成分布式ID,也可以自定义实现。

        注意:原生雪花算法有时钟回拨问题、低频场景下生成的id都是偶数的问题,需要进行优化一下,感兴趣的话可以看下我的另一篇博客 雪花算法生成分布式ID源码分析及低频场景下全是偶数的解决办法

六、总体计划

简要描述一下整个流程:

线上库新的分表创建配置完成,然后按照下面的步骤执行:

  1. 改造双写代码预发测试(多种case跑一下,双写开关等校验),没问题发布上线,上线时双写开关默认关闭,可以通过配置中心动态开启,打开双写开关(新表写入失败先忽略,因为更新和删除操作会因为新表数据不存在而失败),记录双写开始时间点A
  2. 将老表的积分明细的createTime小于等于双写开始时间点A+5分钟(防止时间不同步导致少迁移数据,预留一些缓冲时间)的数据进行全量迁移到分表
  3. 新老数据全量数据校验,查看数据是否一致;同时定时任务每隔一小段时间进行增量校验,增量数据因为读取新老数据存在短暂时间差可能会瞬时不一致,这种数据隔一段时间再次校验,多次校验还不一致的数据进行数据订正(老表数据覆盖到新表数据)
  4. 改造代码,添加双读的逻辑上线(读新表的开关默认关闭)
  5. 低流量节点(凌晨过后)进行白名单、灰度切流userId%10000,进行验证,逐步流量打开,持续观察
  6. 双写开关切到新表,保证只写新表(也可以继续写老表一段时间,或者创建一个新表往老表同步的canal任务,方便回滚),完成数据迁移方案
  7. 系统稳定运行一段时间,迁移&双写代码下线,老表进行资源释放

        为什么没有采用全量同步+canal增量同步方式,这种方式不是比双写实现更简单吗?怎么进行双写?双写有什么好处?后面会继续写一篇单独介绍。

七、双写

        双写改造点:增、删、改

        双写主要是为了避免数据延迟问题,但是双写无法保证两个数据源的事物一致性问题,极端情况下数据不一致(数据校验订正任务解决)

双写开关有两个(通过配置中心实时切换):

  1. 写老表开关:默认开启,新表写入没有问题时可以进行关闭,也可以继续写一段时间老表
  2. 写新表开关:默认关闭,需要开启时打开

        新老表的开关同时打开时,表示要进行双写

通过配置中心动态进行切换,双写期间需要注意的问题如下:

  • 对写新表操作需要记录日志
  • 新表不要求一定写成功(不影响服务,记录错误日志告警通知等,有数据校验订正任务兜底)
  • 双写事物回滚:极端情况下可能新表写成功了、老表数据事物回滚了(数据双向核对校验)

八、数据迁移

        首先我们程序会打开双写,然后需要将我们数据库已经存在的数据进行批量的全量迁移,在这一过程中我们需要不断的进行数据校验。当我们校验基本问题不大的时候,然后进行切流操作,直到完全切流之后,我们就可以不用再进行数据校验。

1)全量同步

  • 全量迁移:将老表的积分明细的createTime小于等于双写开始时间点A+5分钟(防止时间不同步导致少迁移数据,预留一些缓冲时间)的数据进行全量迁移
  • 全量默认走备库查询,目标端写入的是主库,防止影响主库读写,这里需要注意一下读写的qps,以免对线上服务造成影响。

        注意一下存量数据迁移完成的时间,存量数据迁移可以多线程执行,计算公式如下:

消耗小时数= 数据总量 / (3600*写入tps*任务线程数)

2)增量同步

        因为我们代码使用了双写,双写自动做了增量同步,所以不需要单独做增量同步的操作了

九、数据校验

        验证老的单表和新的分表的数据是否一致,也是全量订正服务必须的前置操作

全量校验&增量校验&抽样校验

  • 全量校验:历史全量数据校验
  • 增量校验:定时任务每隔一段时间校验该段时间内的增量数据校验是否一致
  • 抽样校验:可以按照user_id或者业务类型等自定义校验

校验过程

        需要注意:校验任务注意不要影响线上运行的服务,通常校验任务会写很多批查询的语句,会出现批量扫表的情况,如果代码没有写好很容易导致数据库挂掉。

        对账标准:老的单表和新的分表中数据保持一致(所有字段或者核心字段)

        对账流程:通过定时任务轮询+监听数据库binlog执行已经完成迁移的用户在新老积分明细表的数据一致性。需要注意的是由于读取新老库有先后顺序,所以产生瞬时的数据不一致,对于这种问题可以采用对账重试,只要保证最终一致即可。

数据双向核对

        单表核对多表,多表核对单表。原因是双写时无法完全控制事物一致性问题,所以要对单表和分表进行双向核对,具体怎么核对后面会再写一篇进行详细介绍。

校验结果

        校验结果:差异、缺失、多出

数据订正

  • 通过订正服务可以将不一致的老的单表数据和新的分表数据进行数据订正,保证一致性。
  • 使用订正服务前必须进行校验服务。

十、切流量

切流代码编写,切流量是针对的积分明细的查询操作

  • 对所有查询接口进行整理
  • 读取新老数据配置开关,可以通过配置中心实时修改
  • 添加验证白名单列表(userId)
  • 根据白名单、userId后4位取模进行灰度,动态获取查询时用的数据源。
  • 命中灰度时,查询走新分表数据源,查新表数据,同时也查询老表数据,比对新老表数据是否一致,如果一致返回新表数据,如果不一致返回老表数据,同时发送MQ信息对该用户数据进行数据校验、比对、订正。

        切流过程采用逐步放量的形式,灰度方式很多,我们采用的是先白名单验证,然后用户ID取模10000逐步放量的方式。灰度切流验证:万分之1-1%-5%-10%-50%-100%切流

        关于灰度验证、切流的过程后续会再单独写一篇细化介绍一些。

十一、收尾

        直到切流到100%,继续运行一段时间,然后观察各个业务后续工单反馈情况和各个系统预警&日志;

        切写开关到新的分表上面,可以继续写入老表一段时间,如果出现问题可以切回老表

        对新表进行性能压测,确保新表的稳定性

十二、OLAP数据统计分析

        对于积分的数据报表、统计查询分析等需求,由于分表后数据分散到了多个分表上面,如果要再进行统计查询的话需要查询所有的分表,效率太低了,可以使用一些非关系型数据库,比如ES、ClickHouse等。

        比如可以使用ClickHouse,将多个分表的数据同步聚合到ClickHouse上面,利用ClickHouse进行统计分析、出一些数据报表等。

        本篇博客从整体上介绍了一个完整的积分明细分表的实施方案,里面有一些方面介绍的不是很详细,后面会单独的针对一些关键点进行介绍:

  • 怎么进行代码双写,双写有什么好处,为什么没有使用数据库binlog增量同步
  • 怎么进行数据双向校验
  • 怎么进行灰度验证,怎么进行切流
  • .........

        如果有写得不对的地方,或者您有更好的方案,欢迎讨论指正

  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值