在线进行分库分表中间件的平滑升级,正所谓艺高人胆大
大家好,我是架构摆渡人。这是实践经验系列的第十篇文章,这个系列会给大家分享很多在实际工作中有用的经验,如果有收获,还请分享给更多的朋友。
故事还要从N年前说起,那时业务发展的比较快,数据每天增长比较多。很快就经常出现慢SQL各种问题,拆库迫在眉睫。为了快速上线,直接在应用中进行拆分规则的处理,也就是将分库分表的逻辑在客户端进行处理,这样的方式简单,性能也不错。
过了一年多,技术部加了好多人,细分的团队也更多了。老项目用的自研的客户端分库分表方式,有一些新团队内部用的开源的框架。随着技术部规模越来越大,我们也有了自己的中间件团队。CTO想统一技术栈,分库分表中间件是排在第一位要进行统一的,方便统一维护和升级。
于是,中间件团队研发了一款分库分表中间件,类似于开源的Mycat之类的,是一个代理服务,这个服务就是MySQL的协议,我们的应用程序可以直接把它当做一个数据库进行连接即可。
对于这种中间件的替换,最简单的方式当然就是直接替换,然后发布即可。但是如果这个中间件没有经过长时间业务的考验,你会觉得它很稳定吗?如果不稳定,后面出问题了怎么办? 所以,在上线这个中间件切换的时候,大家讨论了很久,最终定了两种方案来实施。
方案一:独立分支,独立节点灰度
方案一比较简单,实现成本较低。也就是单独切一个分支进行改造,改造完成后,不立马合入master分支,而是直接用这个功能分支进行发布,但是只发布一个节点。
这样即使有问题,影响面也比较小。停止服务即可立马回滚,验证一段时间没有问题,就可以将代码合入master进行全量发布。
如果,用了一段时间后,新的代理中间件不稳定,那么此时是没办法立马回滚的,变成了强依赖。此方案虽然成本低,但是稳定性考虑的一般。
方案二:多数据源并存,在线灰度
方案二,实现过程较为复杂,工作量也比较大,但是稳定可靠。下面来介绍下多数据源并存,在线灰度的方式。
首先,目前我们的项目中用的还是之前自己写的一套方案,本地分库分表的。如果想要保证在出问题的时候能够及时切回来,所以就不能采取直接替换的方式,也就是之前的所有数据源都要保留。
至于新的中间件,我们会配置一个新的数据源,所以项目中就有两套数据源。那么只需要在我们对数据库进行增删改查操作的时候,动态进行数据源切换,就可以实现在线灰度逻辑。
灰度可以直接在接口级别进行,这样即使出问题,也只影响某个接口。而且你可以从不重要的接口开始灰度。灰度的核心逻辑无非就是控制AbstractRoutingDataSource的返回值,返回哪个数据源,那么这次SQL的执行将由哪个数据源负责。
我们是Spring Cloud架构,所有接口都是Http协议,所以写了一个Http Filter进行处理。通过配置灰度的规则,然后给要灰度的接口设置一个值,这个值保存到ThreadLocal中,在AbstractRoutingDataSource中使用,这样就完成了整个灰度的控制。
另外,这里还需要注意的是,有一些异步的处理要关注,比如MQ的消费是不经过Http Filter,所以对于MQ的消费你需要单独进行灰度的控制。
同时需要注意的是你的DB的连接数是否够用,因为采用本方案,连接数会翻倍,需要确认。
通过这个方案,我们将灰度做到了最小粒度,接口级别。同时代码也可以合入master分支一起发布。发布的时候默认还是老的数据源,对现有业务不影响。
发布完成后,通过配置开启灰度逻辑,慢慢灰度,灰度完成后所有SQL都会通过新的数据源,也就是代理的方式进行数据库的访问。
假设跑了三个月,代理中间件出问题,我们还是可以秒级别将SQL的执行切到老的数据源上面来保证业务的稳定性。当然不是永远都要保留两套数据源,一般用个半年如果没问题,就可以把老数据源下掉了。
同时要注意的是在这半年期间,如果加了新的分表规则,也需要在老代码中维护,因为要具备随时切回来的能力,所以必须同步,否则切回来没有对应的分表规则,还是会报错。