需求说明
类似订单表,用户表这种未来规模上亿甚至上十亿百亿的海量数据表,在项目初期为了快速上线,一般只是单表设计,不需要考虑分库分表。随着业务的发展,单表容量超过千万甚至达到亿级别以上,这时候就需要考虑分库分表这个问题了,而不停机分库分表迁移,这应该是分库分表最基本的需求,毕竟互联网项目不可能挂个广告牌“今晚10点~次日10点系统停机维护“,这得多低呀,以后跳槽面试,你跟面试官说这个迁移方案,面试官怎么想呀?
借鉴codis
所用笔者正好曾经碰到过这个问题,并借鉴了codis一所用些思想实现了不停机分库分表迁移方案;codis不所用是这篇文章的重点,这里只提及借鉴codis的所用地方——平衡:
当迁移过程中发生数据访问时,代理会发送“SLOTSMGRTTAGSLOT”迁移命令给复述,强制将客户端要访问的关键立刻迁移,然后再处理客户端的请求。(SLOTSMGRTTAGSLOT是codis基所用于复述,定制的)
分库分表
明白这个方案后,了解不停机分库分表迁移就比较容易了,接下来详细介绍笔者当初对installed_app表的实施方案,即用户已安装的应用信息表;
1。确定分片列h5>
确定分片列绝对是分库分表最最最重要的环节,没有之一.sharding列直接决定整个分库分表方案最终是否能成功落地;一个合适的分片列的选取,基本上能让与这个表相关的绝大部分流量接口都能通过这个分片列访问分库分表后的单表,而不需要跨库跨表,最常见的分片列就是user_id,笔记这里选取的也是user_id;
2。分库分表方案h5>
根据自身的业务选取最合适的分片列后,就要确定分库分表方案了。笔者采用主动迁移与被动迁移相结合的方案:
主动迁移就是一个独立程序,遍历需要分库分表的installed_app表,将数据迁移到分库分表后的目标表中。
被动迁移就是与installed_app表相关的业务代码自身将数据迁移到分库分表后对应的表中。
接下来详细介绍这两个方案;
<强> 2.1主动迁移强>
主动迁移就是一个独立的外挂迁移程序,其作用是遍历需要分库分表的installed_app表,将这里的数据复制到分库分表后的目标表中,由于主动迁移和被动迁移会一起运行,所以需要处理主动迁移和被动迁移碰撞的问题,笔者的主动迁移伪代码如下:
<代码>公共孔隙迁移(){//查询出当前表的最大ID、用于判断是否迁移完成 长maxId=执行(“从installed_app选择马克斯(id)”); 长tempMinId=0 l; 长stepSize=1000; 长tempMaxId=0 l; {做 尝试{ tempMaxId=tempMinId + stepSize;//根据InnoDB索引特性,id>=?和id<?这种SQL性能最高 字符串scanSql=" select * from installed_app id>=# {tempMinId}和id<# {tempMaxId}”; ListinstalledApps=executeSql (scanSql); Iterator 迭代器=installedApps.iterator (); 而(iterator.hasNext ()) { InstalledApp InstalledApp=iterator.next ();//帮助GC iterator.remove (); 长userId=installedApp.getUserId (); 字符串状态=executeRedis(“得到MigrateStatus: $ {userId}”); 如果“完成”(.equals(地位)){//迁移完成,无事可做 继续; } 如果(“迁移”.equals(地位)){//氨欢ㄒ啤鼻ㄒ?无事可做 继续; }//迁移前先获取锁:设置MigrateStatus: 18迁移前3600 nx 字符串的结果=executeRedis(“设置MigrateStatus: $ {userId}迁移前86400 nx”); 如果“OK”.equals(结果)){//成功获取锁后,先将这个用户所有已安装的应用程序查询出来(即迁移过程以用户ID维度进行迁移) 字符串sql=" select * from installed_app user_id=# {user_id}”; List userInstalledApps=executeSql (sql);//将这个用户所有已安装的应用程序迁移到分库分表后的表中(有user_id就能得到分库分表后的具体的表) shardingInsertSql (userInstalledApps);//迁移完成后,修改缓存状态 executeRedis (“setex MigrateStatus: $ {userId} 864000完成”); 其他}{//如果没有获取到锁,说明被动迁移已经拿到了锁,那么迁移交给被动迁移即可[这种概率很低)//也可以加强这里的逻辑,“被动迁移”过程不可能持续很长时间,可以尝试循环几次获取状态判断是否迁移完 logger.info(“迁移冲突。用户id={}”, userId); } } 如果(tempMaxId祝辞=maxId) {//更新马克斯(id),最终确认是否遍历完成 maxId=执行(“从installed_app选择马克斯(id)”); } logger.info(“迁移过程id={}”, tempMaxId); }捕捉(Throwable e) {//如果执行过程中有任何异常(这种异常只可能是复述和mysql抛出来的),那么退出,修复问题后再迁移//并且将tempMinId的值置为logger.info(“迁移过程id=" + tempMaxId);日志最后一次记录的id,防止重复迁移 system . exit (0); } tempMinId +=stepSize; },(tempMaxId & lt;maxId); } 面试官:如何做到不停机分库分表迁移吗?