对应文档解析地址:https://www.yuque.com/liwenguang/ogxvf1/ny150b
基于 SpringBoot2.X
做平滑双写先要考虑,如何在一个应用中支持多数据源,以及多数据源的事务问题:失败一起失败、成功一起成功
当一个 @Transaction 注解中的方法里中有多个数据源时,是不能切换数据源的,默认在该事务中,拿的是第一个数据源的事务管理器执行,当然你可以使用 @Transactional(propagation = Propagation.REQUIRES_NEW) 方法来让某个事务内部的方法强制切换数据源,但是不会回滚。
因为数据源切换是通过切面+ ThreadLocal 实现,因此功能也会受到 ThreadLocal 限制,比较典型的就是父子线程的变量和线程池的变量问题,如果想支持这种级别的数据源切换,建议使用开源工具:transmittable-thread-local。关于数据源切面保存的策略,大致有如下策略
- 切面before:put 数据源,切面 after:remove 数据源
- 切面 before:put 数据源,切面 after:不操作
- 切面 before:使用 stack put 数据源,切面 afer:stack pop 数据源
最佳方案是使用 stack 保存每次切面的数据源,并每次执行完后 pop,这样能在复杂的切数据源场景,能避免出现数据源乱掉的情况,并且在复杂的全链路上也支持实时的数据源监控
什么是平滑,就是不停机,不影响现在的线上用户!说的简单,但是做起来还有很多要注意点,总结如下:
- 由于是切库,因此会有源库(老库)O 和目标库(新库)N 的区分
后面无特殊说明,O 代表源库,N 代表新库
- O 库的老数据,需要全量同步到 N 库,此时可以通过 binlog 实现,具体使用 canal/otter 或阿里云商业版 DTS 实现。
- 业务上新的数据改动(Insert/Update/Delete 等)需要在业务上对 O/N 库同时生效
- 对现有业务代码不能有过大影响改动,即对业务透明
什么是切库,就是换个新库,既然换个新库,就要理解新库和老库的区别
暂时默认为 MySQL 从 MySQL
- MySQL 版本的问题,如果 O 和 N 相差版本过大,需要看下 MySQL 的版本更新记录,尤其是索引这种是否有改动什么的,最好是通过模拟线上流量进行压测,来查看是否是正常的
- 切库我们这边是从 MySQL 到 DRDS(分库分表中间件,类似 Cobar/MyCat),会有部分 SQL以及索引特性不支持和更新。具体要看具体 O/N 库的区别
- O/N 库一些通用配置最好能一样或 N 库更高,例如像最大数据源,最小数据源,慢 SQL 日志、慢 SQL 阈值、TIMEOUT 等,数据库的一些自定义配置(谨慎,因为某些老库的配置可能是当年为了解决某个 bug 而配的,切到新库能一模一样是最好的)
基于:src/test/http/rest-api.http
- 没有事务,没有子线程,库切换表现正常
- 有事务,没有线程切换,库切换异常,并且异常后,第一个正常库是正常回滚的
- 有事务,没有线程切换,但是第二库使用了
@Transactional(propagation = Propagation.REQUIRES_NEW),因此可以切换库成功。但是因为挂起原事务,新起一个事务,当报错时,只有第一个库的操作会被回滚 - 没事务,有线程切换,根据线程本地变量不同表现不同的结果