多数据源@DS和@Transactional踩坑之路
概述
随着项目规模的不断扩大,原本的单体应用架构和单一数据库模式已逐渐无法满足日益增长的业务需求。为了更好地应对这一挑战,我们启动了核心服务及相关模块的拆分工作,以实现更高效、灵活的系统架构。然而,在拆分过程中,我们不可避免地遇到了数据源切换这一关键问题。接下来,让我为您详细阐述这一过程。
背景
在核心库拆分的第一个版本中,我们并未进行物理层面的拆分,而是通过数据库用户权限来划分核心表与非核心表。同时,我们封装了一个数据源切换注解,其实现方式与 Mybatis-Plus 类似。为了避免切换失败,我们还在切面中统一进行了一次传播行为的变更(关于为何新开事务可以解决此问题,可参考:多数据源@DS和@Transactional,此处不再赘述),部分代码如图
图一
图一
图一
上线
经过几轮功能性测试后,我们选定了一个日子准备上线。然而,正是在这一天,我们遭遇了一记重锤。-_-
凌晨五六点开始上线,但问题直到八九点才逐渐暴露。起初,其他需求出现了不少问题,掩盖了数据源切换的问题,给当时的故障排查带来了困扰。直到上午十点,我们联合硬件厂家修复了其他问题后,随着项目开始上量,数据源切换的问题才真正显现,大量接口开始超时。
图二
图二
图二
图三
图三
图三
当时,我们一时也难以确定问题的根源,因为这个版本涉及了众多架构改动,包括底层框架和项目架构等。面对各种各样的问题,我们疲于应对,但问题却一个比一个棘手。最终,我们无法承受业务上的压力,不得不将系统回滚。令人讽刺的是,回滚后,刚才的巨大流量瞬间得到了漂亮处理
当日回滚后,我们便立即着手分析当时的日志与快照信息。我们逐行分析了调用量最大、耗时最长的几个接口,对相关提出了一个观点:如果先获取缓存,再获取数据库连接,那么将减少很多压力。不过,当时我们并未顺着这个思路深入下去。以下是具体方法
图四:典型的旁路缓存
−
读模式
图四:典型的旁路缓存-读模式
图四:典型的旁路缓存−读模式
次日
第二天,我们继续分析事故现场,观察到在流量升起前的两分钟左右,有不少获取配置(如图四所示)时出现超时,而该方法其实非常简单。于是,我们查看了阿里云监控,发现当时的 Redis 响应速度很快,这表明问题只能出现在 MySQL 相关操作慢。而此表仅有一百多条数据,且此时间节点的阿里云 MySQL 负载并不高,内网传输也不应成为问题。因此,真相只有一个:连接未获取到,导致阻塞。
大胆假设,小心求证
顺着获取连接阻塞这条线索,我们开始了深入分析。查看当时的德鲁伊连接池和 Tomcat 线程池,发现连接池已满,线程池也满了,且队列中还存有几千个任务,这表明获取连接的线程数量非常多(Tomcat最大线程数配置为200,德鲁伊连接数最大配置为20)。然而,回滚代码后,系统瞬间恢复,压力也随之消失。
图五,上一版本数据源切换注解
图五,上一版本数据源切换注解
图五,上一版本数据源切换注解
这时,我们开始怀疑图一中新的多数据源切换注解的规则——PROPAGATION_REQUIRES_NEW 创建新事务。导致缓存失效,没有起到应有的作用。这正好印证了之前的分析:目前的顺序是先获取 MySQL 连接,再查询 Redis。之前使用 PROPAGATION_REQUIRES_NEW 创建新事务是为了避免外层有事务导致数据源切换失败,因此会重新获取一次连接以切换数据源。此时,我们又想到另一种传播行为:PROPAGATION_NOT_SUPPORTED——以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。这种传播行为也能满足我们的需求,若当前存在事务就直接挂起,这样就不会第一时间去竞争德鲁伊连接池的资源,仅在缓存失效时去获取连接(而这时会获取我们切换后的数据源)。于是,同事迅速修改代码后开始压测,结果令人满意。
图六:最终形态数据源切面逻辑
图六:最终形态数据源切面逻辑
图六:最终形态数据源切面逻辑
结语
通过这次经历,我们认识到在高并发场景下,合理的事务传播行为与缓存策略对系统性能至关重要。未来,我们将继续优化系统架构,加强监控与预警机制,确保系统的高效运行。同时,我们也会不断总结经验,提升团队的技术能力和应急响应能力,为项目的持续发展保驾护航。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/594962.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!