修改一行注解引起的故障:阿里云开发者
事情起因
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 AftsFileClient
aftsFileClient;
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
@SofaReference
private XxxQueryService xxxQueryService;
————————————————————————————————————————————————————————————————————
com.alipay.xxx.core.service.product.XxxQueryService
那么这里真正的调用标准到底是什么呢?真的服务匹配/查找逻辑是啥?
@Service
@SofaService
public class JvmBeanTestServiceImpl implements JvmBeanTestService {
@Override
public String test() {
return "test1";
}
}
@Service
public class JvmBeanTestV2ServiceImpl implements JvmBeanTestV2Service{
@Override
public String testV2() {
return "testV2";
}
}
@Service@SofaServicepublic class JvmBeanTestRunTimeServiceImpl implements JvmBeanTestRunTimeService {
@SofaReference private JvmBeanTestService jvmBeanTestService;
@SofaReference private JvmBeanTestV2Service jvmBeanTestV2Service;
@Override public String runTest1() { return jvmBeanTestService.test(); }
@Override public 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
继续打开思路,找到一篇CloudEngine部署容器的文档详细的介绍了一些启动日志细节,但查看日志详情没有看到我们想要的应用内自己的Bundle启动的信息,依旧都是外部jar依赖。
一般应用部署分为下面几步,根据经验编译成功后如果部署不起来,一般都是健康检查出现了问题。
镜像启动:即运行镜像中的各类脚本,主要包括准备环境变量、构建启动参数、启动nginx等逻辑。 应用启动:镜像脚本成功拉起 SOFABoot 应用后,应用开始执行启动逻辑直至框架完成健康检查阶段。处于本阶段的容器 /actuator/readiness 服务为不可用状态,发起 HTTP 服务将拒绝连接或者返回 500(Not Ready)。 健康检测完成后:框架完成健康检查相关逻辑后,/actuator/readiness 服务为可用状态,发起 HTTP 服务将返回 200(可用)或者 503 (不可用);
查找SOFABOOT基础配置,其中有一条关于健康检查的基础配置描述直接正中眉心,直接到代码中查看配置:
写在最后




