修改一行注解引起的故障
阿里妹导读
事情起因
11.11号接到咨询反馈,有用户在沙箱测试环境的一个上传文件场景遇到异常,原因是其依赖我们团队的应用AxxxCore的一个TR接口报错,通过错误堆栈定位是服务内部依赖的一个SOFA JVM服务找不到。
can not find the corresponding JVM service. Please check if there is a SOFA deployment publish the corresponding JVM service. If this exception occurred when the application starts up, please add Require-Module to SOFA deployment's MANIFEST.MF to indicate the startup dependency of SOFA modules.排查思路
查看代码发现引入服务的地方最近有一次修改,在原先@SofaReference的基础上新增了uniqueId。
@SofaReference(uniqueId = "aftsFileClient")private AftsFileClientaftsFileClient;
SOFA框架有SOFA模块的概念,通常称为Bundle,可以简单理解成成maven项目中的maven module,当然想要被SOFA框架识别为SOFA Bundle还需要配置META-INF/MANIFEST.MF文件、Module-Name等SOFA定制化内容,从官方文档扒出来大致如下。
每个Bundle都有自己独立的Spring上下文,Bundle之间的通信通过SOFA的JVM服务进行,如模块A发布了JVM服务AService、模块B引入该AService即可访问到A模块提供的服务完成Bundle质检的通信,对应上边的代码就是AftsFileClient通过@SofaService注解/配置XML的方式发布服务,问题接口通过@SofaReference的方式引入该服务,简而言之就是:
1、模块A通过@SofaService将模块内的bean发布成一个JVM服务
2、模块B通过@SofaReference将模块A的JVM服务引入
app├── pom.xml└── module1├── src│ └── main│ ├── java│ └── resources│ └── META-INF│ ├── MANIFEST.MF│ └── spring│ └── module1.xml└── pom.xml
直接问题定位
那么问题就来了,为什么刚开始使用注解进行服务引用没有问题,加上一个uniqueId就报错了?
uniqueId是为了解决一个接口发布多个不同的服务而诞生的,如有一个接口Service对应两个实现AServiceImpl和BServiceImpl,分别发布了AService和BService两个服务,在引入服务的时候就需要通过uniqueId来声明要引入哪一个服务,通过接口名+uniqueId表达服务的唯一性。
直接通过搜索的手段查找报错的原因,在SofaBoot的官方排查文档中是这么描述的:按照从先到后的顺序排查发现问题正好符合第一个解决方法描述,故障代码引入的服务确实共存在同一个Bundle当中,故障应急其实通过代码回滚就已经解决了,那么问题又回来了,为啥没有uniqueId不报错,加了uniqueId就报错?
问题分析-step1
难道是不加uniqueId的时候也会找同Bundle发布的JVM服务或者降级找同Bundle的bean,加了uniqueId之后就严格跨Bundle查找JVM服务?
com.alipay.xxx.core.service.product.XxxManageServiceImpl@SofaReferenceprivate XxxQueryService xxxQueryService;————————————————————————————————————————————————————————————————————com.alipay.xxx.core.service.product.XxxQueryService
那么这里真正的调用标准到底是什么呢?真的服务匹配/查找逻辑是啥?
@Service@SofaServicepublic class JvmBeanTestServiceImpl implements JvmBeanTestService {@Overridepublic String test() {return "test1";}}@Servicepublic class JvmBeanTestV2ServiceImpl implements JvmBeanTestV2Service{@Overridepublic String testV2() {return "testV2";}}@Service@SofaServicepublic class JvmBeanTestRunTimeServiceImpl implements JvmBeanTestRunTimeService {@SofaReferenceprivate JvmBeanTestService jvmBeanTestService;@SofaReferenceprivate JvmBeanTestV2Service jvmBeanTestV2Service;@Overridepublic String runTest1() {return jvmBeanTestService.test();}@Overridepublic String runTest2() {return jvmBeanTestV2Service.testV2();}}
实验结果
问题分析-step2
<sofa:service ref="aftsFileClient" interface="com.alipay.aaa.client.AftsFileClient"/><bean id="aftsFileXxxClient" class="com.alipay.aaa.client.impl.AftsFileXxxClientImpl" init-method="init"><property name="env" value="xxx" /><property name="sysName" value="yyy" /><property name="appId" value="zzz" /><property name="bizKey" value="ttt" /></bean>
<bean id="xxxAftsFileClient"class="com.alipay.xxx.common.service.integration.afts.impl.XxxAftsFileClientImpl"/><sofa:service ref="xxxAftsFileClient"interface="com.alipay.xxx.common.service.integration.afts.XxxAftsFileClient"/>
附加问题定位
Spring context initialize success module list(53) >>>>>>> [totalTime = 146001 ms, realTime = 9242 ms]├─aaa-common-service-facade-1.1.0.20211014.jar [1303 ms]│ `---aaa-common-service-facade.xml├─bbb-common-service-facade-1.2.0.20230922.jar [1463 ms]│ `---common-service-facade.xml├─ccc-common-service-facade-2.0.0.20150526.jar [1456 ms]│ `---common-service-facade.xml├─ddd-api-1.0.5.jar [1506 ms]│ `---ddd-api.xml├─eee-common-service-facade-1.0.0.20240510.jar [1474 ms]│ `---common-service-facade.xml├─fff-common-service-facade-1.0.0.20240430.jar [1501 ms]│ `---common-service-facade.xml----省略一万字----└─zzz-common-service-client-1.0.0.20220501.jar [846 ms]+---common-service-cache.xml+---common-service-client.xml+---common-service-integration.xml`---common-service-xxxcache.xml
一般应用部署分为下面几步,根据经验编译成功后如果部署不起来,一般都是健康检查出现了问题。
镜像启动:即运行镜像中的各类脚本,主要包括准备环境变量、构建启动参数、启动nginx等逻辑。 应用启动:镜像脚本成功拉起 SOFABoot 应用后,应用开始执行启动逻辑直至框架完成健康检查阶段。处于本阶段的容器 /actuator/readiness 服务为不可用状态,发起 HTTP 服务将拒绝连接或者返回 500(Not Ready)。 健康检测完成后:框架完成健康检查相关逻辑后,/actuator/readiness 服务为可用状态,发起 HTTP 服务将返回 200(可用)或者 503 (不可用);
查找SOFABOOT基础配置,其中有一条关于健康检查的基础配置描述直接正中眉心,直接到代码中查看配置:
写在最后
通过RocketMQ事务消息实现分布式事务
在日益复杂的分布式系统中,确保数据一致性成为一大挑战。本方案利用RocketMQ强大事务消息能力,确保跨服务事务的高可靠与最终一致性,同时还实现了事务处理的高效执行,是构建高性能、高可用分布式系统的关键路径。
点击阅读原文查看详情。