導(dǎo)語
本文分析了在網(wǎng)絡(luò)超時場景下,RPC服務(wù)調(diào)用數(shù)據(jù)一致性的問題,對于接口無冪等、接口冪等失效情況下,對異常數(shù)據(jù)快速處理做了分析思考和嘗試,開發(fā)了一款輕量級仿冪等數(shù)據(jù)校正處理輔助工具。該工具可以MOCK或SPY服務(wù)調(diào)用,不限于RPC接口,進程內(nèi)的方法調(diào)用也支持,與JSF、WebService、HTTP方式無關(guān),只要方法能被代理,就可以使用,寫服務(wù)、讀服務(wù)均可以支持。目前已在生產(chǎn)環(huán)境中使用,在關(guān)鍵時刻可以發(fā)揮相應(yīng)的作用。本文工具并不重要,重要的是與大家一起探討一些解決方案,給大家提供一種思路。如果小伙伴有類似訴求,也歡迎大家合適的場景下接入使用。
?
由來
最近在參與系統(tǒng)的故障與處理恢復(fù)專題,我腦海中衍生了一個關(guān)于數(shù)據(jù)校正處理(或稱之為修數(shù),或數(shù)據(jù)處理)相關(guān)的一個idea,可以在一些場景下發(fā)揮重要作用。
本文的重點不是探討故障與處理恢復(fù)措施,比如三板斧、三把刀,而是將我腦海中的這個idea場景剖開,打算設(shè)計和開發(fā)一款對應(yīng)的數(shù)據(jù)處理提效工具,落地到相應(yīng)場景中去使用。
?
場景分析
在分布式架構(gòu)中,應(yīng)用之間的網(wǎng)絡(luò)通信,簡單說存在三種狀態(tài):成功、失敗、超時,簡稱為網(wǎng)絡(luò)三態(tài)。
成功:請求成功發(fā)送并且得到正確的響應(yīng)。
失敗:請求發(fā)送失敗或收到的響應(yīng)表示操作失敗。
超時:請求在指定時間內(nèi)沒有收到響應(yīng)。
?

?

?
對于成功而言,可以正常響應(yīng)處理。
對于失敗而言,可以進行數(shù)據(jù)回退、重試補償?shù)仁侄巍?/p>
對于成功、失敗這兩種狀態(tài)而言,結(jié)果都是明確的,在分布式數(shù)據(jù)一致性處理上也相對比較簡單。
對于超時而言,調(diào)用方感知的是超時,服務(wù)提供方處理的時間超出預(yù)期時間,但服務(wù)提供方最終是否執(zhí)行成功,不得而知。有可能執(zhí)行失敗,也有可能最終處理成功并落庫,只是未能響應(yīng)給調(diào)用方。
在超時情況下,即使調(diào)用方再感知超時后,回退自身數(shù)據(jù)后,同時嘗試回退服務(wù)提供方的數(shù)據(jù)時,大概率也是回退失敗,因為此時服務(wù)提供方尚未執(zhí)行完成,數(shù)據(jù)尚未落庫完成。如果說delay一段時間后,再去回退服務(wù)提供方的數(shù)據(jù),倒是可行,但delay多長時間,回退多少次才能成功,都不確定,對調(diào)用方來說,也增加了復(fù)雜性和運維難度。
?
假如服務(wù)調(diào)用是同一個線程中的本地調(diào)用,訪問同一個數(shù)據(jù)庫實例,則可以直接使用數(shù)據(jù)庫事務(wù)來保障一致性。
如果是分布式調(diào)用,可以采取分布式事務(wù)措施,例如2PC、3PC、TCC、Saga事務(wù)等方式來保障一致性,市面上也有成熟的分布式事務(wù)中間件可以使用,例如Seata解決方案。
?
上面說到分布式事務(wù)只是順著話題延伸了一下,本文重點不是探討分布式事務(wù)的解決方案,況且很多京東系統(tǒng),并沒有接入分布式事務(wù)解決方案,本文重點思考在超時場景下,有沒有一些手段或工具可以幫助快速數(shù)據(jù)一致性處理、故障恢復(fù)。
?
思考
超時也許是由于網(wǎng)絡(luò)抖動,或者服務(wù)器負載過高造成的服務(wù)超時,也有可能是程序性能不佳造成的持續(xù)超時。最終的數(shù)據(jù)處理和恢復(fù)方向,都是要讓數(shù)據(jù)在應(yīng)用之間得以流動落地,才能使整個鏈路的流程走下去,即要保障應(yīng)用間數(shù)據(jù)的最終一致性。
?
如果服務(wù)可以降級,則降級是比較快速的一個恢復(fù)手段。
如果服務(wù)不可降級,則通過重試補償?shù)仁侄蝸砘謴?fù)數(shù)據(jù)的一致性。
?
RPC服務(wù)重試,調(diào)用方、服務(wù)提供方需要保障接口的冪等性才能保證重試無副作用。
何為冪等性?冪等是一次和多次請求某一個資源對于資源本身應(yīng)該具有同樣的結(jié)果,換言之,其任意多次執(zhí)行對資源本身所產(chǎn)生的影響均與一次執(zhí)行的影響相同。
接口的冪等性,需要調(diào)用方和服務(wù)提供方相互配合才行,倘若服務(wù)提供方提供的接口支持冪等性,雙方按照約定接口入?yún)⒅械膗uid作為唯一序列號進行防重,但服務(wù)提供方每次的重試調(diào)用(無論上次調(diào)用成功與否)uuid都會改變,這就會使得冪等失效。
?
如果接口沒有實現(xiàn)冪等性,或者由于調(diào)用方每次必變uuid導(dǎo)致冪等失效,在這種情況下,該如何快速恢復(fù)數(shù)據(jù)呢?
?
?

?
如上圖所示,由于服務(wù)超時后,應(yīng)用B內(nèi)部仍在持續(xù)執(zhí)行,此時恢復(fù)手段是:人工介入,梳理數(shù)據(jù)后,人工將應(yīng)用B的數(shù)據(jù)進行回退,或者人工將應(yīng)用A的數(shù)據(jù)進行補齊推動流程向后走,人工保證A和B之間的數(shù)據(jù)一致性。倘若應(yīng)用A、B背后的流程比較長,涉及的表關(guān)系比較復(fù)雜,數(shù)據(jù)量比較大,這時候人工就難以處理了,也容易出錯,造成二次傷害。
?
之前還遇到過一種情況,服務(wù)提供方和調(diào)用方都支持冪等,但由于一些原因,調(diào)用方很久之前的一個異步任務(wù)失敗了,而調(diào)用方用于冪等防重的數(shù)據(jù)歸檔了。當(dāng)時為了支持冪等重試,從歸檔庫里拉回了相應(yīng)的流水?dāng)?shù)據(jù)到生產(chǎn)庫,才重試調(diào)用成功,費力費力,效率低。
?
思路
這里持續(xù)探索無冪等或冪等失效場景下的重試能力建設(shè)。
?

在應(yīng)急處理情況下,向來都是爭分奪秒,這里可以通過MOCK結(jié)果返回給調(diào)用方A,相當(dāng)于“預(yù)支成功”。
并非所有的“預(yù)支成功”都是合理的,為了讓“預(yù)支成功”盡可能合理,需要在服務(wù)提供方內(nèi)部實現(xiàn)里,做好充分的判斷和校驗,這種判斷和校驗盡量是輕量級的。如果高并發(fā)情況下的“預(yù)支成功”判斷不合理,事后可以人工介入核對和補償數(shù)據(jù)。
?
建設(shè)工具
對工具的期望
?由于接口無冪等或冪等失效,需要對能夠預(yù)支成功的請求圈定一個范圍,這個范圍要支持配置,最好支持動態(tài)配置秒級生效。
?對這個范圍內(nèi)的請求,進行偽冪等,MOCK特定結(jié)果,返回給調(diào)用方,使得調(diào)用方可以拿到成功結(jié)果快速推動流程。
?圈定的范圍盡可能具體,盡量避免不該MOCK的進行了MOCK,造成服務(wù)調(diào)用方的數(shù)據(jù)沒得到刷新,導(dǎo)致數(shù)據(jù)的不一致。
?
在實現(xiàn)中,我稱這個工具為“魔法工具”,是一種“障眼法”,是一種“預(yù)先支付成功”,是一種MOCK或SPY,對于調(diào)用方A來說,是一種體感上的成功,認為調(diào)用方真的處理成功了。
?
配置

?
在配置中,支持多個配置內(nèi)容的存在,比如有多個單據(jù)需要同時進行偽冪等MOCK。
?


?
更直觀地,用一個JSON數(shù)據(jù)示例來看一下數(shù)據(jù)結(jié)構(gòu):
{
"detailList": [
{
"enabled": true,
"className": "com.jdwl.wms.stock.app.service.main.StockTransferAppServiceImpl",
"methodName": "increaseStock",
"basicNo1": null,
"basicNo2": null,
"basicNo3": "6_6_601",
"uuidList": null,
"businessNoList": [
"GZQ202503160250001"
],
"startTime": "2025-03-16 01:50:00",
"endTime": "2025-03-18 03:50:00",
"strategy": "DO_AND_RETURN_SUCCESS_REGARDLESS_OF_FAILURE",
"defaultResult": {
"resultValue": true,
"resultCode": 100000,
"prompType": 0,
"success": true
}
}
]
}
?
startTime、endTime 時間區(qū)間是用來卡控配置生效的時間段,正常情況下配置是短暫生效,起到數(shù)據(jù)處理的作用后,應(yīng)去掉該配置。
?
目前策略有兩種:

?
這兩個策略的區(qū)別是要不要真正執(zhí)行一次接口實現(xiàn),類似于單測中的MOCK和SPY效果。
defaultResult 是該接口方法的期望返回值,配置對應(yīng)的返回值JSON,會按照配置的內(nèi)容直接返回給調(diào)用方。
?
核心實現(xiàn)
圈定范圍的匹配

?
按不同策略MOCK或SPY

?
使用案例
案例一 MOCK服務(wù)調(diào)用
通過DUCC配置圈定要MOCK的范圍
?

?
{
"detailList": [
{
"enabled": true,
"className": "com.jdwl.wms.stock.app.service.main.StockTransferAppServiceImpl",
"methodName": "increaseStock",
"basicNo1": null,
"basicNo2": null,
"basicNo3": "6_6_601",
"uuidList": null,
"businessNoList": [
"GZQ202503160250001"
],
"startTime": "2025-03-16 01:50:00",
"endTime": "2025-03-18 03:50:00",
"strategy": "DO_NOTHING_AND_RETURN_SPECIFIED_VALUE",
"defaultResult": {
"resultValue": true,
"resultCode": 100000,
"prompType": 0,
"success": true
}
}
]
}
?
在JSF平臺模擬客戶端調(diào)用方發(fā)起調(diào)用
?

?
這里采用的策略是
DO_NOTHING_AND_RETURN_SPECIFIED_VALUE,即:不執(zhí)行,直接返回指定的返回值
JSF的返回值就是在上面所配置的返回值內(nèi)容。
?
驗證執(zhí)行情況
這里檢查數(shù)據(jù)庫落庫情況,看方法是否真地得到執(zhí)行。
?

?
與預(yù)期一致,方法被成功MOCK,未真正執(zhí)行該方法,返回了預(yù)先配置的返回值。
?
案例二 阻隔異常數(shù)據(jù)生成
近期生產(chǎn)環(huán)境遇到一個場景,逆向盤點時,有個終止盤點的操作,這個操作表示結(jié)束盤點,并且未盤點的明細則以少貨缺量的方式提報差異,并預(yù)占庫存。
雖然按鈕有提示,但少概率下會有操作人員不看提示而誤點擊,形成大量的差異庫存預(yù)占。
這些預(yù)占是由于誤點擊形成的差異預(yù)占,并非真實的差異,屬于異常數(shù)據(jù),這種數(shù)據(jù)需要釋放關(guān)閉處理,如果數(shù)據(jù)量較大,現(xiàn)場會找研發(fā)團隊協(xié)助處理。
?
異常監(jiān)控
收到監(jiān)控告警,查看流量情況,發(fā)現(xiàn)有突發(fā)差異提報流量,短時間內(nèi)調(diào)用量比日常高出很多。
?

?
阻隔配置
找到異常倉號和單號,與現(xiàn)場電話對齊后,決定對該異常單進行阻隔攔截,避免產(chǎn)生更多的異常數(shù)據(jù)。
?

?
{
"detailList": [
{
"enabled": true,
"className": "com.jdwl.wms.stock.app.service.main.StockExceptionHandleAppServiceImpl",
"methodName": "recordDifferenceDetail",
"basicNo1": null,
"basicNo2": null,
"basicNo3": "11309_200",
"uuidList": null,
"businessNoList": [
"DPPT1904111957150015488"
],
"startTime": "2025-03-24 19:37:00",
"endTime": "2025-03-25 00:00:00",
"strategy": "DO_NOTHING_AND_RETURN_SPECIFIED_VALUE",
"defaultResult": {
"resultValue": true,
"resultCode": 100000,
"prompType": 0,
"success": true
}
}
]
}
?
結(jié)果核實

?
通過核實日志和數(shù)據(jù),該工具有效阻隔了部分異常數(shù)據(jù)的生成,節(jié)省了異常數(shù)據(jù)核對和處理的時間。
?
總結(jié)
本文所提出的一款輕量級仿冪等數(shù)據(jù)校正處理輔助工具,可以達到MOCK或SPY的效果。不僅可以用在無冪等或冪等失效場景下,數(shù)據(jù)庫快速處理恢復(fù)的場合,還可以用于一些查詢類、校驗類的讀服務(wù)的MOCK場景。
現(xiàn)階段工具還比較簡單,功能還很有限,使用場景也有針對性和局限性,希望在一些場景上可以幫助大家。
本文工具并不重要,重要的是與大家一起探討一些解決方案,給大家提供一種思路。
本文的解決方案是我短時間內(nèi)的一個思考和落地嘗試,未必是最優(yōu)的,希望與大家一起交流更好的方案。
?
如何接入使用?
如果小伙伴也有類似使用訴求,大家可以先在測試、UAT環(huán)境接入試用,然后再逐步推廣線上生產(chǎn)環(huán)境。
接入方法也非常簡單,如下。
?
1、引入Maven依賴
!-- http://sd.jd.com/article/44544?shareId=105168&isHideShareButton=1 --?>
com.jd.sword/groupId?>
sword-aspect/artifactId?>
1.0.2-SNAPSHOT/version?>
org.projectlombok/groupId?>
lombok/artifactId?>
/exclusion?>
org.apache.commons/groupId?>
commons-lang3/artifactId?>
/exclusion?>
org.slf4j/groupId?>
slf4j-api/artifactId?>
/exclusion?>
org.springframework/groupId?>
spring-context/artifactId?>
/exclusion?>
org.aspectj/groupId?>
aspectjweaver/artifactId?>
/exclusion?>
com.alibaba/groupId?>
fastjson/artifactId?>
/exclusion?>
com.jd.laf.config/groupId?>
laf-config-client-jd-spring/artifactId?>
/exclusion?>
/exclusions?>
/dependency?>
com.jd.sword/groupId?>
sword-constant/artifactId?>
1.0.0-SNAPSHOT/version?>
/dependency?>
com.jd.sword/groupId?>
sword-annotation/artifactId?>
1.0.1-SNAPSHOT/version?>
/dependency?>
對于其中的間接依賴,例如lombok等,大家可以使用自己工程中的已有依賴,在這里可以通過exclusion排掉,如果自己工程中沒有這些依賴,可以不exclusion。
?
2、在被攔截方法上打上注解
示例:
@Magic(enabled = true, basicNo3 = "#args[0].requestHeader.warehouseNo", uuid = "#args[0].requestHeader.uuid", businessNo = "#args[0].requestHeader.businessNo")
支持SpEL表達式。
建議在服務(wù)提供方的內(nèi)部方法實現(xiàn)內(nèi),或者調(diào)用方在調(diào)用目標(biāo)API的防腐層上進行注解。
服務(wù)提供方的內(nèi)部方法實現(xiàn)內(nèi),不一定是放在API的impl層,也可以是其內(nèi)部的Service層,比如放在冪等防重和輕量級校驗判斷之后,重量級核心邏輯實現(xiàn)之前。
?
3、使用時進行按需配置
DUCC配置或Spring yml 配置都可以,更推薦使用DUCC動態(tài)配置生效。
使用完應(yīng)盡快去掉配置,可以保留空殼,將detailList置為空list。
示例配置:
{
"detailList": [
{
"enabled": true,
"className": "com.jdwl.wms.stock.app.service.main.StockTransferAppServiceImpl",
"methodName": "increaseStock",
"basicNo1": null,
"basicNo2": null,
"basicNo3": "6_6_601",
"uuidList": null,
"businessNoList": [
"GZQ202503160250001"
],
"startTime": "2025-03-16 01:50:00",
"endTime": "2025-03-18 03:50:00",
"strategy": "DO_NOTHING_AND_RETURN_SPECIFIED_VALUE",
"defaultResult": {
"resultValue": true,
"resultCode": 100000,
"prompType": 0,
"success": true
}
}
]
}
或
magic:
content: '{"detailList":[{"enabled":true,"className":"com.jdwl.wms.stock.app.service.main.StockTransferAppServiceImpl","methodName":"increaseStock","basicNo1":null,"basicNo2":null,"basicNo3":"6_6_601","uuidList":null,"businessNoList":["GZQ202503160250"],"startTime":"2025-03-16 01:50:00","endTime":"2025-03-18 03:50:00","strategy":"DO_AND_RETURN_SUCCESS_REGARDLESS_OF_FAILURE","defaultResult":{"resultValue":true,"resultCode":100000,"prompType":0,"success":true}}]}'
審核編輯 黃宇
-
接口
+關(guān)注
關(guān)注
33文章
9587瀏覽量
157584 -
RPC
+關(guān)注
關(guān)注
0文章
114瀏覽量
12287 -
京東
+關(guān)注
關(guān)注
2文章
1128瀏覽量
50139
發(fā)布評論請先 登錄
2026視覺檢測產(chǎn)業(yè)深度調(diào)研及未來趨勢分析
NXP 產(chǎn)品包裝革新:輕量級卷軸的引入
二代 ADW300 秒級采集!新能源計量數(shù)據(jù)精準(zhǔn)到「每一秒」
借助TRAE和MCUXpresso for VS Code實現(xiàn)AI輔助開發(fā)MCX A系列MCU工程
瑞芯微SOC智能視覺AI處理器
通過2的冪次進行除法和取余數(shù)快捷方法優(yōu)化
RSA加速實現(xiàn)思路
商品詳情頁內(nèi)容更新接口設(shè)計與實現(xiàn)
【CW32】uart_obj_fw 輕量級串口框架
要求穩(wěn)定可靠,必選的一款10.1寸屏(LVDS, 高分變率、戶外高亮、CTP防暴玻璃蓋板)
【正點原子】新一代經(jīng)濟型工業(yè)級核心板RK3506J開發(fā)板及資料發(fā)布
Crypto核心庫:顛覆傳統(tǒng)的數(shù)據(jù)安全輕量級加密方案
基于米爾瑞芯微RK3576開發(fā)板部署運行TinyMaix:超輕量級推理框架
輕量級≠低效能:RK3506J核心板如何用性價比感動用戶?
ReviewHub:實現(xiàn)Booster與設(shè)計工具端無縫鏈接的評審協(xié)作平臺
如何秒級實現(xiàn)接口間“冪等”補償:一款輕量級仿冪等數(shù)據(jù)校正處理輔助工具
評論