阿里妹導讀
事情起因
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服務?

回滾程式碼應急後發現當時是誤判,其實兩個服務不是在一個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
publicclassJvmBeanTestServiceImplimplementsJvmBeanTestService {
@Override
public String test(){
return"test1";
}
}
@Service
publicclassJvmBeanTestV2ServiceImplimplementsJvmBeanTestV2Service{
@Override
public String testV2(){
return"testV2";
}
}
@Service
@SofaService
publicclassJvmBeanTestRunTimeServiceImplimplementsJvmBeanTestRunTimeService {
@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做這種所謂的相容。
附加問題定位
依稀記得之前一些新依賴注入時有問題,在系統部署階段就能發現,但是這次是系統部署成功後才發現,為什麼?
這裡懷疑應用應該和服務啟動的依賴Bundle配置有關係,透過官方排查文件尋找應用啟動了哪些模組發現都是外部jar包依賴,和我們要排查的方向不符合。
Spring context initialize success modulelist(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依賴。

轉換一下思路來排查問題就是,SOFA應用部署的過程是什麼樣的?健康檢查等能查出來什麼問題?
當前透過檢視health.sh和deploy.sh指令碼沒看出啥可以用的資訊,找到的一些排錯文件都是部署失敗後的排查,還需要進一步看一下部署成功但是呼叫時失敗的問題。
一般應用部署分為下面幾步,根據經驗編譯成功後如果部署不起來,一般都是健康檢查出現了問題。
映象啟動:即執行映象中的各類指令碼,主要包括準備環境變數、構建啟動引數、啟動nginx等邏輯。 應用啟動:映象指令碼成功拉起 SOFABoot 應用後,應用開始執行啟動邏輯直至框架完成健康檢查階段。處於本階段的容器 /actuator/readiness 服務為不可用狀態,發起 HTTP 服務將拒絕連線或者返回 500(Not Ready)。 健康檢測完成後:框架完成健康檢查相關邏輯後,/actuator/readiness 服務為可用狀態,發起 HTTP 服務將返回 200(可用)或者 503 (不可用);
查詢SOFABOOT基礎配置,其中有一條關於健康檢查的基礎配置描述直接正中眉心,直接到程式碼中檢視配置:
com.alipay.sofa.boot.skipJvmReferenceHealthCheck=true,改成false重新部署就部署不起來了,破案(此刻心裡暗爽+10086)。


寫在最後
本次教訓/經驗:保持程式碼敬畏之心,修改歷史程式碼務必謹慎,搞清“之所以”再行動。
透過RocketMQ事務訊息實現分散式事務
在日益複雜的分散式系統中,確保資料一致性成為一大挑戰。本方案利用RocketMQ強大事務訊息能力,確保跨服務事務的高可靠與最終一致性,同時還實現了事務處理的高效執行,是構建高效能、高可用分散式系統的關鍵路徑。
點選閱讀原文檢視詳情。