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增量同步
  • 怎么进行数据双向校验
  • 怎么进行灰度验证,怎么进行切流
  • .........

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/715921.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构——Top-k问题

Top-k问题 方法一:堆排序(升序)(时间复杂度O(N*logN))向上调整建堆(时间复杂度:O(N * logN) )向下调整建堆(时间复杂度:O(N) )堆排序代码 方法二&…

LeetCode---386周赛

题目列表 3046. 分割数组 3047. 求交集区域内的最大正方形面积 3048. 标记所有下标的最早秒数 I 3049. 标记所有下标的最早秒数 II 一、分割数组 这题简单的思维题,要想将数组分为两个数组,且分出的两个数组中数字不会重复,很显然一个数…

Redis 的哨兵模式配置

1.配置 vim sentinel.conf# mymaster 给主机起的名字 # 192.168.205.128 主机的ip地址 # 6379 端口号 # 2 当几个哨兵发现主观宕机,则判定为客观宕机。 原则上是大于一半。比如三个哨兵,则设置为 2 sentinel monitor mymaster 192.168.205.128 63…

【动态规划入门】01背包问题

每日一道算法题之01背包问题 一、题目描述二、思路三、C++代码四、结语一、题目描述 题目来源:Acwing 有N件物品和一个容量是 V的背包。每件物品只能使用一次。第 i件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大…

LeetCode题练习与总结:合并K个升序链表

一、题目 给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后的链表。 二、解题思路 创建一个最小堆(优先队列)来存储所有链表的头节点。这样我们可以始终取出当前所有链表中值最小…

人工智能指数报告2023

人工智能指数报告2023 主要要点第 1 章 研究与开发第 2 章 技术性能第 3 章 人工智能技术伦理第 4 章 经济第 5 章 教育第 6 章 政策与治理第 7 章 多样性第 8 章 舆论 人工智能指数是斯坦福大学以人为本的人工智能研究所(HAI)的一项独立倡议&#xff0c…

Java 石头剪刀布小游戏

一、任务 编写一个剪刀石头布游戏的程序。程序启动后会随机生成1~3的随机数,分别代表剪刀、石头和布,玩家通过键盘输入剪刀、石头和布与电脑进行5轮的游戏,赢的次数多的一方为赢家。若五局皆为平局,则最终结果判为平局。 二、实…

redis 为什么会阻塞

目录 前言 客户端交换时的阻塞 redis 磁盘交换的阻塞 主从节点交互的阻塞 切片集群交互时的阻塞 异步执行的演变 redis 异步执行如何实现的 前言 大家对redis 比较熟悉吧,只要做项目都会用到redis,提高系统的吞吐。小米商城抢购高峰18k的qps&…

KubeSphere平台安装系列之三【Linux多节点部署KubeSphere】(3/3)

**《KubeSphere平台安装系列》** 【Kubernetes上安装KubeSphere(亲测–实操完整版)】(1/3) 【Linux单节点部署KubeSphere】(2/3) 【Linux多节点部署KubeSphere】(3/3) **《KubeS…

一句话讲清楚数据库中事务的隔离级别(通俗易懂版)

为什么我只说通俗易懂版不说严谨版? 因为严谨版遍地都是, 但是他们却有一个缺点就是让人看得云里雾里, 所以这就是我写通俗易懂版的初衷! 但是既然是通俗易懂版就必然有缺陷, 只为了各位在开发过程中头脑更加清晰, 如有错误还望兄弟们不吝赐教! 在MySQL数据库中,事务一共有4…

C语言之strcmp函数,strlen函数

strcmp函数是比较两个字符串ASCII大小的函数。 比较方式是自左向右比较&#xff0c;直到出现不同字符或者\0为止 语法格式 strcmp(字符串1,字符串2&#xff09; 如果两个字符串相同&#xff0c;会返回数值0 如果字符串1>字符串2,会返回一个正数 如果字符串1<字符串2…

新一代电话机器人开源PHP源代码

使用easyswoole 框架开发的 新一代电话机器人开源PHP源码 项目地址&#xff1a;https://gitee.com/ddrjcode/robotphp 代理商页面演示地址 http://119.23.229.15:8080 用户名&#xff1a;c0508 密码&#xff1a;123456 包含 AI外呼管理&#xff0c;话术管理&#xff0c;CR…

每日一题 — 复写零

1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 首先找到最后一个复写的数&#xff1a; 双指针算法&#xff1a; 1、先判断 cur 位置上的值 2、然后决定 dest 移动一步还是两步 3、然后判断 dest 是否到终点了 4、最后 cur 处理越界的情况 arr[n-1] …

使用sourceCompatibility = 11不匹配解决方法

运行springbootgradle项目报错。 原因&#xff1a;在生产该项目时&#xff0c;选择的JDK是11版本的&#xff0c;但是本地电脑只安装了1.8版本。不兼容所以报错。 解决办法&#xff1a; 找到build.gradle配置文件—>找到sourceCompatibility ‘11’—>把11改成自己本地…

思维题(蓝桥杯 填空题 C++)

目录 题目一&#xff1a; ​编辑 代码&#xff1a; 题目二&#xff1a; 代码&#xff1a; 题目三&#xff1a; 代码&#xff1a; 题目四&#xff1a; 代码&#xff1a; 题目五&#xff1a; 代码&#xff1a; 题目六&#xff1a; 代码七&#xff1a; 题目八&#x…

用python和pygame库实现刮刮乐游戏

用python和pygame库实现刮刮乐游戏 首先&#xff0c;确保你已经安装了pygame库。如果没有安装&#xff0c;可以通过以下命令安装&#xff1a; pip install pygame 示例有两个。 一、简单刮刮乐游戏 准备两张图片&#xff0c;一张作为背景bottom_image.png&#xff0c;一张作…

Leetcoder Day35| 动态规划part02

62.不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff…

如何在Linux配置C、C++、Go语言的编译环境?

在 Linux 系统上配置 C、C、Go 语言的编译环境可以通过安装相应的编译器和相关工具来实现。以下是在 Linux 系统上配置这些语言的编译环境的一般步骤&#xff1a; 1. C 和 C 编译环境配置&#xff1a; 安装 GCC 编译器&#xff08;一般 Linux 发行版都会包含&#xff09;&…

Android 显示系统框架

一.FrameBuffer FrameBuffer 介绍&#xff1a; FrameBuffer中文译名为帧缓冲驱动&#xff0c;它是出现在2.2.xx内核中的一种驱动程序接口。主设备号为29&#xff0c;次设备号递增。 Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。FrameBuffer机制模仿显卡的功能…

Day11:信息打点-Web应用企业产权指纹识别域名资产网络空间威胁情报

目录 Web信息收集工具 业务资产-应用类型分类 Web单域名获取-接口查询 Web子域名获取-解析枚举 Web架构资产-平台指纹识别 思维导图 章节知识点&#xff1a; Web&#xff1a;语言/CMS/中间件/数据库/系统/WAF等 系统&#xff1a;操作系统/端口服务/网络环境/防火墙等 应用…