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

?

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

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

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

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


?
更直觀地,用一個JSON數據示例來看一下數據結構:
{
"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 時間區間是用來卡控配置生效的時間段,正常情況下配置是短暫生效,起到數據處理的作用后,應去掉該配置。
?
目前策略有兩種:

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

?
按不同策略MOCK或SPY

?
使用案例
案例一 MOCK服務調用
通過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平臺模擬客戶端調用方發起調用
?

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

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

?
阻隔配置
找到異常倉號和單號,與現場電話對齊后,決定對該異常單進行阻隔攔截,避免產生更多的異常數據。
?

?
{
"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
}
}
]
}
?
結果核實

?
通過核實日志和數據,該工具有效阻隔了部分異常數據的生成,節省了異常數據核對和處理的時間。
?
總結
本文所提出的一款輕量級仿冪等數據校正處理輔助工具,可以達到MOCK或SPY的效果。不僅可以用在無冪等或冪等失效場景下,數據庫快速處理恢復的場合,還可以用于一些查詢類、校驗類的讀服務的MOCK場景。
現階段工具還比較簡單,功能還很有限,使用場景也有針對性和局限性,希望在一些場景上可以幫助大家。
本文工具并不重要,重要的是與大家一起探討一些解決方案,給大家提供一種思路。
本文的解決方案是我短時間內的一個思考和落地嘗試,未必是最優的,希望與大家一起交流更好的方案。
?
如何接入使用?
如果小伙伴也有類似使用訴求,大家可以先在測試、UAT環境接入試用,然后再逐步推廣線上生產環境。
接入方法也非常簡單,如下。
?
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表達式。
建議在服務提供方的內部方法實現內,或者調用方在調用目標API的防腐層上進行注解。
服務提供方的內部方法實現內,不一定是放在API的impl層,也可以是其內部的Service層,比如放在冪等防重和輕量級校驗判斷之后,重量級核心邏輯實現之前。
?
3、使用時進行按需配置
DUCC配置或Spring yml 配置都可以,更推薦使用DUCC動態配置生效。
使用完應盡快去掉配置,可以保留空殼,將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}}]}'
審核編輯 黃宇
-
接口
+關注
關注
33文章
9520瀏覽量
157034 -
RPC
+關注
關注
0文章
114瀏覽量
12264 -
京東
+關注
關注
2文章
1108瀏覽量
50077
發布評論請先 登錄
借助TRAE和MCUXpresso for VS Code實現AI輔助開發MCX A系列MCU工程
瑞芯微SOC智能視覺AI處理器
通過2的冪次進行除法和取余數快捷方法優化
【CW32】uart_obj_fw 輕量級串口框架
要求穩定可靠,必選的一款10.1寸屏(LVDS, 高分變率、戶外高亮、CTP防暴玻璃蓋板)
【正點原子】新一代經濟型工業級核心板RK3506J開發板及資料發布
Crypto核心庫:顛覆傳統的數據安全輕量級加密方案
基于米爾瑞芯微RK3576開發板部署運行TinyMaix:超輕量級推理框架
ReviewHub:實現Booster與設計工具端無縫鏈接的評審協作平臺
Segger RTT調試工具的使用方法
求助,關于iMX DDR3寄存器編程輔助問題求解
樹莓派替代臺式計算機?樹莓派上七款最佳的輕量級操作系統!
如何秒級實現接口間“冪等”補償:一款輕量級仿冪等數據校正處理輔助工具
評論