修改一行注解引起的故障

简介: 作者记录了故障发生时的排查思路,再对问题进行详细描述并分析根本原因,最终找到解决方案。

1.事情起因

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.

2.排查思路

查看代码发现引入服务的地方最近有一次修改,在原先@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



2.1直接问题定位

那么问题就来了,为什么刚开始使用注解进行服务引用没有问题,加上一个uniqueId就报错了?

uniqueId是为了解决一个接口发布多个不同的服务而诞生的,如有一个接口Service对应两个实现AServiceImpl和BServiceImpl,分别发布了AService和BService两个服务,在引入服务的时候就需要通过uniqueId来声明要引入哪一个服务,通过接口名+uniqueId表达服务的唯一性。

直接通过搜索的手段查找报错的原因,在SofaBoot的官方排查文档中是这么描述的:按照从先到后的顺序排查发现问题正好符合第一个解决方法描述,故障代码引入的服务确实共存在同一个Bundle当中,故障应急其实通过代码回滚就已经解决了,那么问题又回来了,为啥没有uniqueId不报错,加了uniqueId就报错?


问题分析-step1

难道是不加uniqueId的时候也会找同Bundle发布的JVM服务或者降级找同Bundle的bean,加了uniqueId之后就严格跨Bundle查找JVM服务?

image.png

回滚代码应急后发现当时是误判,其实两个服务不是在一个Bundle里,不符合第一个解决方法描述,但是这个第一个问题的描述是否成立?因为官方正式文档中并没有对这些细节的非标准设置进行说明,本来想做个实验来验证一下这个同Bundle的服务是否可以引用,突然想起来之前一些历史接口存在同Bundle内通过@SofaReference引入服务依赖,于是乎直接扒皮代码,实验都不用做了,直接可以调通找到服务,上述文档的解决方法1不成立!


com.alipay.xxx.core.service.product.XxxManageServiceImpl

    @SofaReference
    private XxxQueryService       xxxQueryService;
————————————————————————————————————————————————————————————————————
com.alipay.xxx.core.service.product.XxxQueryService

那么这里真正的调用标准到底是什么呢?真的服务匹配/查找逻辑是啥?

这里通过实验进行了探索,在同Bundle中分别通过@SofaReference引用一个发布了JVM服务和只发布了bean的不同服务。


@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
@SofaService
public 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();
    }
}


实验结果

1、调用jvmBeanTestService成功,那么可以引用同Bundle的JVM服务这个推测结论成立,官方答疑文档有误;


2、调用jvmBeanTestV2Service失败,证明了@SofaReference只能匹配JVM服务不能匹配bean,之前的一个猜想方向是不对的。


问题分析-step2

第二个怀疑的方向是服务的发布方式有问题,依据是服务发布的时候没有指定uniqueId,但是服务引用确指定了uniqueId,和官方文档的描述不一致。


经过和SOFA官方人员核实,使用uniqueId发布和引用服务的时候,查找服务的时候是按照【接口名+uniqueId】进行匹配的,可以理解为如果发布服务的时候指定了uniqueId,那么引用服务的时候也要指定uniqueId,否则会匹配不到服务。我们的这个报错场景就是引用服务的时候指定了uniqueId,但是服务发布的时候并没有指定uniqueId,问题根因就在这里。


<sofa:service ref="aftsFileClient" interface="com.alipay.aaa.client.AftsFileClient"/>


那么当时改代码的同学为什么要在服务引用的时候加上uniqueId呢?通过翻看代码是因为当时的项目需求需要重新配置aftsFileClient服务对应的参数,也就是需要将aftsFileClient发布一个新服务出来。


1、原先有一个aftsFileClient的bean,改造的同学新配置了aftsFileXxxClient这个bean


<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>

2、原先有一个aftsFileClient的JVM服务,改造的同学又将aftsFileXxxClient这个bean包装成了一个新的JVM服务xxxAftsFileClient


<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"/>


那么此时新代码只需要通过@SofaReference直接引用xxxAftsFileClient这个JVM服务即可,因为新JVM服务xxxAftsFileClient和原先的JVM服务aftsFileClient实现的并不是一个接口也没有重名,所以实际上并没有使用uniqueId的需求,无需改动老代码。


这里能想到的有两个优化空间,第一个是不改代码,在官方文档说明不按标准进行配置会存在问题,但我估计还是解决不了问题,出现问题的往往是老司机很少再去看文档;第二个是在@SofaReference进行服务匹配的时候,如果设置了uniqueId可以先维持现状通过【接口名+uniqueId】的方式查找,最后兜底使用接口名查找,但是其实我如果是框架的开发者会觉得不合适,uniqueId本身就是一种进阶用法,不应该为了错误case做这种所谓的兼容。


2.2附加问题定位

依稀记得之前一些新依赖注入时有问题,在系统部署阶段就能发现,但是这次是系统部署成功后才发现,为什么?


这里怀疑应用应该和服务启动的依赖Bundle配置有关系,通过官方排查文档寻找应用启动了哪些模块发现都是外部jar包依赖,和我们要排查的方向不符合。


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依赖。


image.png


转换一下思路来排查问题就是,SOFA应用部署的过程是什么样的?健康检查等能查出来什么问题?


当前通过查看health.sh和deploy.sh脚本没看出啥可以用的信息,找到的一些排错文档都是部署失败后的排查,还需要进一步看一下部署成功但是调用时失败的问题。


一般应用部署分为下面几步,根据经验编译成功后如果部署不起来,一般都是健康检查出现了问题。

  1. 镜像启动:即运行镜像中的各类脚本,主要包括准备环境变量、构建启动参数、启动nginx等逻辑。
  2. 应用启动:镜像脚本成功拉起 SOFABoot 应用后,应用开始执行启动逻辑直至框架完成健康检查阶段。处于本阶段的容器 /actuator/readiness 服务为不可用状态,发起 HTTP 服务将拒绝连接或者返回 500(Not Ready)。
  3. 健康检测完成后:框架完成健康检查相关逻辑后,/actuator/readiness 服务为可用状态,发起 HTTP 服务将返回 200(可用)或者 503 (不可用);

查找SOFABOOT基础配置,其中有一条关于健康检查的基础配置描述直接正中眉心,直接到代码中查看配置:


com.alipay.sofa.boot.skipJvmReferenceHealthCheck=true,改成false重新部署就部署不起来了,破案(此刻心里暗爽+10086)。


image.png


3.写在最后

本次教训/经验:保持代码敬畏之心,修改历史代码务必谨慎,搞清“之所以”再行动。





来源  |  阿里云开发者公众号

作者  |  王谷




相关文章
|
2天前
|
调度 云计算 芯片
云超算技术跃进,阿里云牵头制定我国首个云超算国家标准
近日,由阿里云联合中国电子技术标准化研究院主导制定的首个云超算国家标准已完成报批,不久后将正式批准发布。标准规定了云超算服务涉及的云计算基础资源、资源管理、运行和调度等方面的技术要求,为云超算服务产品的设计、实现、应用和选型提供指导,为云超算在HPC应用和用户的大范围采用奠定了基础。
|
9天前
|
存储 运维 安全
云上金融量化策略回测方案与最佳实践
2024年11月29日,阿里云在上海举办金融量化策略回测Workshop,汇聚多位行业专家,围绕量化投资的最佳实践、数据隐私安全、量化策略回测方案等议题进行深入探讨。活动特别设计了动手实践环节,帮助参会者亲身体验阿里云产品功能,涵盖EHPC量化回测和Argo Workflows量化回测两大主题,旨在提升量化投研效率与安全性。
云上金融量化策略回测方案与最佳实践
|
11天前
|
人工智能 自然语言处理 前端开发
从0开始打造一款APP:前端+搭建本机服务,定制暖冬卫衣先到先得
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。
8834 20
|
15天前
|
Cloud Native Apache 流计算
资料合集|Flink Forward Asia 2024 上海站
Apache Flink 年度技术盛会聚焦“回顾过去,展望未来”,涵盖流式湖仓、流批一体、Data+AI 等八大核心议题,近百家厂商参与,深入探讨前沿技术发展。小松鼠为大家整理了 FFA 2024 演讲 PPT ,可在线阅读和下载。
4762 12
资料合集|Flink Forward Asia 2024 上海站
|
15天前
|
自然语言处理 数据可视化 API
Qwen系列模型+GraphRAG/LightRAG/Kotaemon从0开始构建中医方剂大模型知识图谱问答
本文详细记录了作者在短时间内尝试构建中医药知识图谱的过程,涵盖了GraphRAG、LightRAG和Kotaemon三种图RAG架构的对比与应用。通过实际操作,作者不仅展示了如何利用这些工具构建知识图谱,还指出了每种工具的优势和局限性。尽管初步构建的知识图谱在数据处理、实体识别和关系抽取等方面存在不足,但为后续的优化和改进提供了宝贵的经验和方向。此外,文章强调了知识图谱构建不仅仅是技术问题,还需要深入整合领域知识和满足用户需求,体现了跨学科合作的重要性。
|
23天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
11天前
|
人工智能 容器
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
本文介绍了如何利用千问开发一款情侣刮刮乐小游戏,通过三步简单指令实现从单个功能到整体框架,再到多端优化的过程,旨在为生活增添乐趣,促进情感交流。在线体验地址已提供,鼓励读者动手尝试,探索编程与AI结合的无限可能。
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
|
10天前
|
消息中间件 人工智能 运维
12月更文特别场——寻找用云高手,分享云&AI实践
我们寻找你,用云高手,欢迎分享你的真知灼见!
871 57

热门文章

最新文章