| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> 聊聊我最近的分库分表骚操作 -> 正文阅读 |
|
[大数据]聊聊我最近的分库分表骚操作 |
聊聊我最近的分库分表骚操作前言国庆前这段时间一直都在搞分库分表相关的东西,中间遇到了比较多的问题,不过最后还是都艰难地解决掉了。这段时间感觉挺累但又挺有意思的,需要思考的点很多,所以决定写篇博客 背景先说一下背景吧。我们项目之前是完全没做分库分表的,现在有几张核心的表,数据量已经非常大了,数据量最大的一张表已经有接近250亿的数据了……我刚开始看到这张表的数据量是比较惊讶的,而且还跟DBA大佬了解了一下,现在线上这张表的操作耗时非常短,暂时没有性能问题。一般来说,千万级的数据量应该已经比较影响性能了,现在这个表已经差不多250亿数据了,还不出问题? 我分析了一下这张表的结构,然后看了一下相关的SQL,我认为这张表暂时还没有性能问题有以下两点:
虽然现在没有性能问题,但是嘛……现在这张表每个月的增长速度是亿级别的,再不做分库分表,迟早要出问题。所以我决定先对它出手了!另外几张表的数据量也很大,但是与之相关的操作太复杂了,所以我觉得我得先找个简单的表实践实践,积累一点经验后再对其它几张表下手。 分库分表方案分库分表主要有垂直切分和水平水平,这次针对的问题是单表容量达到瓶颈的问题,因此采用水平切分。 以下把这张表叫做 t_file 表吧。 切分策略水平切分策略主要有hash切分和范围切分,优缺点如下表。
最终经过考虑,决定采用范围切分策略,主要原因是 t_file 的主键原来是整型的自增主键,不适合用hash切分。目前数据量非常大,并且还涉及到另外几张表,有些表存储了一个列表,列表里存的就是整型ID,那些表也非常大,改的话影响范围太大,所以不可能把整型修改成其它类型。 不分库,只分表这张表我们有没有必要分库呢?我和我的mentor讨论了一下,觉得是暂时只做分表即可。只要原因有以下几个:
所以最终决定不分库,只分表。 技术选型目前比较主流的分库分表中间件有mycat、sharding-jdbc、tidb,最终决定使用sharding-jdbc,原因有下:
综合业务及中间件优缺点考虑,sharding-jdbc较为合适。 问题及解决方案写的过程碰到了几个问题,搞了点骚操作。 分布式ID实现不重复的ID该怎么实现?分库分表后,需要保证数据插入后ID的唯一性,实现不重复的ID一般有以下几种方案:
方案四实现全局唯一ID比较简单,类型符合我们要求,性能也没有损耗,因此决定采用方案四。但是问题接踵而至。 使用方案四,就会有三个问题:
第一个问题只需运维或者后端开发建表前注意一下即可,而第二个问题则需要在代码中解决,而第三个问题,只需要我们把表下标传进mapper,交给mybatis帮我们拼装到SQL里面即可。 所以现在主要是要解决第二个问题。 表下标维护那么要怎么维护这个表下标呢?我当时是思考了一个下午,一步步优化,得到一个自认为不错的方案(太佩服我自己了吧),而且实现也不复杂。这个方案就是每个表预留一部分ID,然后每个实例各自维护一个表下标,不存储在redis中。 下面来看看整个优化的过程。 插入前查询maxId最直接的写法就是每次批量插入数据前,先查询表的最大ID,再加上当前的数据量,预测插入数据后的ID是否还在范围内,若超过了该范围,就把下标+1,更新到redis中,然后把数据插入到下一个表中。 单线程下这种做法当然没有问题,但是多线程问题就大了,何况这个业务并发量还那么高。 假设 t_file_0 的ID范围是0 ~ 9999,现在该表最大的ID是9995,此时2个线程同时插入数据,这2个线程都插入4行数据……2个线程获取到的tableIndex都是0,并且查了一下maxId加上自己的数据量,最后得到9999,在范围内,然后同时插入数据……那么 t_file_0 这个表就会出现ID超过了9999的情况,而 t_file_1 的ID范围是 10000~19999,ID重复了! 所以很明显,这个方案不靠谱。 插入后查询maxId既然插入后的ID可能会超出范围,那我们能不能在插入后再检查一下maxId呢?如果maxId超出范围,就回滚事务,再更新tableIndex,把数据插入到下一张表中。 预留ID那能不能再优化一下呢?我不想控制事务回滚然后再提交一次…… 所以我想到了预留最后一部分ID的做法,我可以让每张表都预留一部分ID,这部分ID可以插入一部分数据,但是插入之后,以后的数据就插入到下一张表。只要我们预留的ID范围足够大,那在高并发场景下就不会出现ID超范围的情况。 比如 t_file_0 的预留ID范围是 9000~9999,当插入一批数据后得到的最大ID是9100,达到了预留ID范围,那就更新表下标,以后的数据插入到 t_file_1 中。即使多个线程同时插入一大批数据,只要我们预留的范围足够大,就不会出现最大ID超过这张表范围的情况。这样就不用手动控制事务了。
预留ID + 不依赖redis上面的方案,都是在redis维护了一个表下标。这样的话,redis一出问题,那可能就会影响到当前业务的新增操作了,但是实际上表下标是不需要在redis中再维护一份的,我只需要在每个实例中维护自己的一个下标就行了! 这样的话,会有这个问题:当其中一个实例插入数据后,发现自己的下标要+1了,其它的实例无法感知到。 但是这个问题并不会使ID重复,因为只要我预留的ID足够大,保证其它实例再插入数据的时候,不会超过当前表的最大ID,就不会出现问题!并且我们插入数据是分批插入的,每批最多100个数据。比如要插入1000个数据,我会分成10批数据,每批数据插入完成后都会检查一下maxId并更新表。所以理论上,只要满足这个条件,就绝对不会有重复ID的问题发生(当然我是测过的,你完全可以相信七里翔):
多数据源、sharding-jdbc、主从怎么结合到一块?使用sharding-jdbc的shardingsphere,我又遇到了以下两个问题:
针对这两个问题,我决定优化多数据源组件,整合shardingsphere。 怎么解决多数据源和shardingsphere数据源冲突的问题?我们的项目中要到了一个自己开发多数据源组件,把这个多数据源组件的DataSource注到mybatis中,可以通过注解动态切换数据源。但是现在用shardingsphere,会跟这个组件起冲突。 看了一天源码后,我最后决定把shardingsphere集成到我们的多数据源组件中。我先在shardingsphere基础上写个自动配置类,封装多个shardingsphere的datasource,然后交给多数据源去管理。也就是,数据源组件只需要负责帮我切换数据源到shardingsphere,剩下的分片、路由相关的操作还是交给shardingsphere本身去完成。完美解决二者冲突的问题! 怎么解决分表+主从数据库不适合用shardingsphere的问题?我们的数据库是主从,现在使用shardingsphere来管理 t_file 表的路由,想要切换到从库数据源,只有两个办法:
这两种方法都没法满足我们的业务需求,最终我决定再优化多数据源组件,使用多数据源管理多个shardingsphere DataSource。 影响部分SQL性能问题虽然这个表涉及的SQL不多,并且不复杂,但是还是有个别SQL可能会出现问题。 上线前准备数据迁移上线前需要运维协助建表并迁移数据,分表后每张表数据量为20亿,预留10万个ID。 服务切换服务更新与服务回滚是比较重要的环节,这个得要提前规划好。考虑好怎么样才能将服务切换时的影响降到最低。我刚开始的方案是,预留一部分主键ID,具体方案如下。 预留一部分主键ID采取预留一部分主键ID的方案让新旧服务能同时运行而不会产生主键冲突。 假如现在线上有250亿数据,按照每张表20亿数据分表,则可以分为13张表,下标为 0 ~ 12 ,t_file_12 的主键范围是 240亿~260亿-1,全量同步后开启增量同步,把后续新增的数据也同步过去。 假设增量的数据有1亿,那么增量同步差不多完成后,t_file_12 中的最大主键大概是251亿,还有9亿的ID可用。 这个时候新服务上线,新服务新增的数据插入到 t_file_13 中,而 t_file_13 的起始自增序列号为260亿,所以新旧服务一起运行时,一定时间内并不会出现主键冲突的情况。因此新服务上线后可以开启双向同步,观察新服务上线后的情况,确认完全没有问题后再关闭旧服务、双向同步。 整个过程的步骤为:
当我说完这个方案后,我自信满满,此时我的表情是这样的👇。 DBA大佬说:小伙子,这个上线方案不行。假如一个表原本的当前的自增序列号是10,而你插入了一行数据,这行数据里面包含了ID(即不是使用自增ID)的话,是会更新自增序列号的,比如你插入了一个ID为15的数据,那这个时候表的自增序列号就会变成16,而不是原来的10了。 我…… 最后的方案还是DBA大佬经验丰富,和他讨论过后,我豁然开朗。最终的上线步骤是这样的。
其它存储过程新建表的时候的存储过程如下:
多数据源组件我感觉这个多数据源组件有点意思。所以我在国庆这两天自己也写了个多数据源组件,并且集成了shardingsphere。感兴趣的可以点我头像,里面有我github主页链接,里面一个名为 dynamic-datasource-spring-boot-starter 的仓库就是这个项目了。 后续思考假如要分库现在是只做分表的情况,我可以把表下标传进去给mybatis拼到SQL里面。但是如果做了分库后要怎么办呢? 这个我也考虑到了,假如要做分库的话,那数据库的路由就需要交给sharingsphere去完成了,我能想到的是:用shardingsphere的hint强制路由策略,然后自己实现自定义注解,并且实现相应的路由策略,按理说是可以满足分库的需求的。 不过嘛,没实践过怎么会知道有没有其它问题呢?等有空了我再实现试试。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 0:53:24- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |