首页 > 其他分享 >开发故事:一个 @Async 如何搞瘫整个微服务系统

开发故事:一个 @Async 如何搞瘫整个微服务系统

时间:2024-09-13 09:51:30浏览次数:11  
标签:依赖 故事 Spring 代理 Bean 开发 Async 注入



嘿,大家好!我是小米,一个充满活力、喜欢分享技术的29岁开发者。今天的文章,我们要来聊一聊一个发生在我们开发环境的惊险故事。这个问题折腾了我们整个团队好一阵子,最终我们发现元凶竟然是一个看似无害的 @Async 注解。废话不多说,直接开讲!

故事的开始:微服务无法启动

就在昨天,我们的开发环境微服务无法启动,连续尝试了好几次,每次启动的时候日志都报错:

开发故事:一个 @Async 如何搞瘫整个微服务系统_Async

读完这个错误日志,我们开发团队的小伙伴们都挠了挠头,心想这到底是个啥?特别是我们刚刚合并的代码中也没有涉及到什么大改动,为什么会突然冒出这么个问题?

错误日志解析

首先,让我们来分析一下这个错误日志究竟在说什么。

  • Bean 被注入到其他 Beans 中:错误提示说,tradeService 这个 Bean 被注入到了其他两个 Bean(returnOrderServicerefundOrderService)中,这本身其实是没什么问题的。在 Spring 的依赖注入机制下,Bean 被注入到其他 Bean 中是一件再普通不过的事。
  • Raw version 被注入:问题的关键是这里提到的 “raw version”(原始版本)。在 Spring 中,Bean 经过创建和初始化后,可能还会被代理包装。比如,AOP 或者 @Async 会通过动态代理对 Bean 进行增强。那么这里的意思是,tradeService 被注入时,其他服务接收到的不是它的最终版本,而是未经过增强的原始版本。
  • Circular reference(循环引用)问题:这个提示还提到了可能存在的循环引用问题。Spring 为了避免 Bean 的循环依赖,采用了三级缓存机制。在 Bean 创建过程中,如果有 Bean 依赖另一个还未完全初始化完成的 Bean,Spring 会暂时将原始 Bean 注入,这样可以打破循环引用的死锁。
  • “over-eager type matching”:这个错误的最后部分提到了可能的原因:过度积极的类型匹配。在某些情况下,Spring 容器会过早地初始化某些 Bean,这可能会导致一些尚未完全准备好的 Bean 被注入。

至此,我们已经大致明白问题的轮廓:tradeService 这个 Bean 因为某种原因,被注入了它的原始版本,而不是最终被代理增强后的版本。

团队排查:@Async 的可疑之处

我们开始逐步排查代码变更,最后注意到一个开发同事在 tradeService 的某个方法上添加了 @Async 注解。大家知道,@Async 是 Spring 提供的一个非常方便的异步执行注解。通过在方法上加上这个注解,Spring 会把这个方法的执行交给一个线程池,避免占用主线程资源。

虽然 @Async 本身非常好用,但它的实现依赖于 AOP 动态代理机制。当 Spring 看到一个方法被 @Async 注解修饰时,它会创建一个代理对象,代理对象接管方法的调用逻辑。在这种情况下,如果你直接在其他地方引用了这个 Bean 的原始版本,而不是代理后的版本,就会导致预期外的行为——比如像我们遇到的错误。

正是这个 @Async 导致了 Bean 没有被完整初始化,进而引发了循环依赖和代理注入的问题。

深入解析:Spring 的代理机制与循环依赖

为了更好地理解这个问题,我们需要进一步探讨 Spring 是如何处理 Bean 的依赖注入和代理机制。

1. Bean 的创建过程

在 Spring 中,Bean 的创建分为以下几个步骤:

  1. 实例化:首先,Spring 会通过构造函数或者工厂方法实例化 Bean。
  2. 依赖注入:接下来,Spring 会为这个 Bean 注入所需的依赖,这时候如果某个 Bean 依赖还没有准备好,Spring 就会遇到循环依赖问题。
  3. 初始化:在依赖注入完成后,Spring 会执行 Bean 的初始化方法,比如 @PostConstruct 标注的方法,或者执行一些自定义的初始化逻辑。
  4. 代理包装:在初始化完成后,如果这个 Bean 需要增强(如 AOP 或者 @Async),Spring 会为这个 Bean 创建一个代理对象。

2. 循环依赖

如果两个 Bean 相互依赖,比如 A 依赖 B,而 B 又依赖 A,那么 Spring 就会遇到循环依赖问题。为了打破这种循环,Spring 会在依赖注入时注入原始版本的 Bean,而不是最终被代理包装后的版本。

在我们的案例中,tradeServicereturnOrderServicerefundOrderService 引用,而它自身因为使用了 @Async 注解,导致它需要被代理。如果 Spring 在创建代理之前就将它注入到了其他服务中,就会引发前面提到的那个错误。

找到了问题的根源,我们就可以着手解决它了。

方法一:调整依赖结构

最直接的解决方案就是重新审视我们的依赖结构。我们可以考虑是否能打破循环引用,避免 tradeService 被过早地注入到其他 Bean 中。

在我们的案例中,我们尝试通过调整依赖关系,将 tradeService 的一些依赖注入拆分到其他组件中,避免直接注入原始 Bean。

方法二:使用 @Lazy 延迟加载

如果调整依赖结构有些困难,另一种方法是使用 @Lazy 注解。通过将 Bean 设置为懒加载,Spring 会推迟它的实例化,直到它真正被使用时才会创建。这可以有效避免 Bean 在初始化过程中被过早地注入。

开发故事:一个 @Async 如何搞瘫整个微服务系统_依赖注入_02

方法三:禁用 @Async 的代理

在某些场景下,如果 @Async 只是在某些方法上使用,而不是全局依赖,我们可以考虑不使用代理。在 Spring 中,可以通过配置文件或者编程的方式禁用某些 Bean 的代理行为。

但这种方式比较少见,通常我们会选择保留 @Async 代理,毕竟它为我们提供了异步执行的便利。

END

这次开发环境中的微服务启动问题,最终是由 @Async 引起的代理问题导致的。通过深入理解 Spring 的 Bean 代理机制和循环依赖的处理方法,我们成功找到了问题的根源,并采取了有效的解决方案。这个案例告诉我们,在使用诸如 @Async 这样的注解时,一定要小心其背后的代理机制,尤其是在存在复杂依赖关系的场景下。

如果你也遇到类似的问题,记得从错误日志中寻找线索,逐步排查问题的根源。希望这篇文章对大家有所帮助!

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!

标签:依赖,故事,Spring,代理,Bean,开发,Async,注入
From: https://blog.51cto.com/u_16237826/11999164

相关文章

  • 了解 UNPKG:前端开发者的包管理利器
    在现代前端开发中,JavaScript包管理和模块化是至关重要的,而npm则是最流行的JavaScript包管理器之一。不过,随着前端项目复杂性的增加,有时候我们希望快速引入外部依赖,而无需本地安装和构建。此时,CDN(内容分发网络)成为了一种方便快捷的解决方案,而UNPKG就是这种方式中的佼......
  • 一文搞定高并发编程:CompletableFuture的supplyAsync与runAsync
    CompletableFuture是Java8中引入的一个类,用于简化异步编程和并发操作。它提供了一种方便的方式来处理异步任务的结果,以及将多个异步任务组合在一起执行。CompletableFuture支持链式操作,使得异步编程更加直观和灵活。在引入CompletableFuture之前,Java已经有了Future接口来......
  • 【智能终端】HBuilder X 与微信开发者工具集成与调试实战
    目录1.需求和理解库、框架、平台1.1需求1.2理解2.3库、框架、平台2.3.1库(Library)2.3.2框架(Framework)2.3.3平台(Platform)2.3.4总结2.使用HBuilderX创建第一个uni-app应用步骤1:进入DCloud 官网,下载并安装HBuilderX。步骤2:打开HBuilderX,选择新......
  • 项目完成小结:使用DjangoStarter v3和Taro开发的微信小程序
    前言不知不觉已经九月了,又到了一年的开学季,我每年都想做的项目墙甚至连个影子都没有…......
  • 百度地图SDK Android版开发 11 覆盖物示例 4 线
    百度地图SDKAndroid版开发11覆盖物示例4线前言界面布局MapPolyline类常量成员变量初始值创建覆盖物移除覆盖物设置属性加载地图和释放地图MapPolylineActivity类控件响应事件运行效果图前言文本通过创建多个不同线宽的折线和大地曲线,介绍Polyline的使用方法。......
  • 1765asp.net古镇旅游网站VS开发sqlserver数据库web结构c#编程web网页设计
    博主介绍:专注于Java.net phpphython 小程序等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作☆☆☆精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟我的博客空间发布了1000+毕设题目方便大家学习使用感兴趣的可以先收藏起来,还有大家在......
  • ESP8266 开发板 带 0.96 OLED显示屏
     市面上有两种板子主要区别在于IIC通讯引脚不一样:第1款:SCL为12  /*D5=SCL=GPIO12*/SDA为14  /*D6=SDA=GPIO14*/第2款:SCL为5  /*D1=SCL=GPIO5*/SDA为4  /*D2=SDA=GPIO4*/先发第一款效果图示注意红色字体里的局部配置信息。#defineSCL12/*D5=SC......
  • 分享一个AI开发者的强力助手:openMind Library
    在人工智能的浪潮中,深度学习开发套件openMindLibrary,以其强大的功能和易用性,逐步成为AI开发者们的强力助手。本文将通过魔乐社区近期关注挺高的大模型平台魔乐社区,深入了解openMindLibrary。openMindLibrary是什么?openMindLibrary是一个开源的深度学习开发套件,它通过简单易用......
  • 异步实例化预制体Object.InstantiateAsync配合Async/Await使用
    Unity2022.3.20之后,可以使用异步克隆,正如前面一篇文章《Unity2022.3.20f1新功能,异步实例化预制体Object.InstantiateAsync》说明的那样,常规的使用携程方式异步克隆,但如今await/async写法如此简单方便,肯定就不想放IEnumerator里头去实现了,怎么办呢?实现一个,代码如下:publicGame......
  • Springboot点餐系统jl9ml(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景与意义随着信息技术的飞速发展和餐饮行业的日益繁荣,传统的点餐方式已难以满足现代消费者对于便捷性、高效性和个性化服务的需求。因此......