亿级大表分库分表实战总结( 六 )


4)删除旧表
4.5 最佳实践4.5.1 写完立即读可能读不到在分批上线过程中 , 遇到了写完立即读可能读不到的情况 。由于业务众多 , 我们采用了分批上线的方式降低风险 , 存在一部分应用已经升级 , 一部分应用尚未升级的情况 。未升级的服务仍然往旧表写数据 , 而升级后的应用会从新表读数据 , 当延迟存在时 , 很多新写入的记录无法读到 , 对具体业务场景造成了比较严重的影响 。
延迟的原因主要有两个:
1)写服务还没有升级 , 还没有开始双写 , 还是写旧表 , 这时候会有读新表、写旧表的中间状态 , 新旧表存在同步延迟 。
2)为了避免主库压力 , 新表数据是从旧表获取变更、然后反查旧表只读实例的数据进行同步的 , 主从库本身存在一定延迟 。
解决方案一般有两种:
1)数据同步改为双写逻辑 。
2)在读接口做补偿 , 如果新表查不到 , 到旧表再查一次 。
 
4.5.2 数据库中间件唯一ID替换自增主键(划重点 , 敲黑板)由于分表后 , 继续使用单表的自增主键 , 会导致全局主键冲突 。因此 , 需要使用分布式唯一ID来代替自增主键 。各种算法网上比较多 , 本项目采用的是数据库自增sequence生成方式 。

数据库自增sequence的分布式ID生成器 , 是一个依赖Mysql的存在 ,  它的基本原理是在Mysql中存入一个数值 ,  每有一台机器去获取ID的时候 , 都会在当前ID上累加一定的数量比如说2000 ,  然后把当前的值加上2000返回给服务器 。这样每一台机器都可以继续重复此操作获得唯一id区间 。
但是仅仅有全局唯一ID就大功告成了吗?显然不是 , 因为这里还会存在新旧表的id冲突问题 。
因为服务比较多 , 为了降低风险需要分批上线 。因此 , 存在一部分服务还是单写旧表的逻辑 , 一部分服务是双写的逻辑 。
这样的状态中 , 旧表的id策略使用的是auto_increment 。如果只有单向数据来往的话(旧表到新表) , 只需要给旧表的id预留一个区间段 , sequence从一个较大的起始值开始就能避免冲突 。
但该项目中 , 还有新表数据和旧表数据的双写 , 如果采用上述方案 , 较大的id写入到旧表 , 旧表的auto_increment将会被重置到该值 , 这样单鞋旧表的服务产生的递增id的记录必然会出现冲突 。
所以这里交换了双方的区间段 , 旧库从较大的auto_increment起始值开始 , 新表选择的id(也就是sequence的范围)从大于旧表的最大记录的id开始递增 , 小于旧表auto_increment即将设置的起始值 , 很好的避免了id冲突问题 。
1)切换前:
sequence的起始id设置为当前旧表的自增id大小 , 然后旧表的自增id需要改大 , 预留一段区间 , 给旧表的自增id继续使用 , 防止未升级业务写入旧表的数据同步到新库后产生id冲突;
2)切换后
无需任何改造 , 断开数据同步即可
3)优点
只用一份代码;
切换可以使用开关进行 , 不用升级改造;
如果万一中途旧表的autoincrement被异常数据变大了 , 也不会造成什么问题 。
4)缺点
如果旧表写失败了 , 新表写成功了 , 需要日志辅助处理
4.6 本章小结完成旧表下线后 , 整个分库分表的改造就完成了 。
在这个过程中 , 需要始终保持对线上业务的敬畏 , 仔细思考每个可能发生的问题 , 想好快速回滚方案(在三个阶段提到了projectdb的jar包版本迭代 , 从1.0.0-SNAPSHOT到3.0.0-SNAPSHOT , 包含了每个阶段不同的变更 , 在不同阶段的分批上线的过程中 , 通过jar包版本的方式进行回滚 , 发挥了巨大作用) , 避免造成重大故障 。
5.稳定性保障这一章主要再次强调稳定性的保障手段 。作为本次项目的重要目标之一 , 稳定性其实贯穿在整个项目周期内 , 基本上在上文各个环节都已经都有提到 , 每一个环节都要引起足够的重视 , 仔细设计和评估方案 , 做到心中有数 , 而不是靠天吃饭:


推荐阅读