ToB復雜業(yè)務(wù)狀態(tài)的可復用的解決方案
年底了大家都要述職,老板都會問:你這個方案,是否沉淀形成一套可復用解決方案?
今天就介紹關(guān)于一個業(yè)務(wù):狀態(tài)變多、條件分支增多、狀態(tài)轉(zhuǎn)換規(guī)則、依賴關(guān)系變復雜,導致代碼難以維護,代碼可重用性和可擴展性變差這個問題,一套可復用解決方案。
1.問題背景
產(chǎn)品:來活啦,有用戶反饋我們的APP沒有線上退保證金功能,都在抖音上找到老板反饋了。
產(chǎn)品:我們商戶入駐狀態(tài)有:未入駐、待入駐、已入駐、清退中、已清退。清退中又有子狀態(tài) 商戶申請關(guān)戶中、統(tǒng)計商戶有責貨損中、待商戶確認有責貨損、申訴商戶有責貨損中、提現(xiàn)失敗、提現(xiàn)成功、已清退等。
我這里簡單畫一下關(guān)戶狀態(tài)流轉(zhuǎn)圖
圖片
2.如何解決狀態(tài)問題
在軟件開發(fā)過程中,隨著功能的不斷迭代和添加,代碼的可維護性往往會面臨挑戰(zhàn)。
我們有一個商戶入駐的功能,功能也是由簡單迭代到復雜,現(xiàn)在又要增加商戶退出、重新入駐相關(guān)狀態(tài),狀態(tài)轉(zhuǎn)換邏輯也變得復雜,在這個迭代過程中,我們?nèi)绾畏乐故荷酱a形成?
2.1 解耦重構(gòu)優(yōu)化代碼
項目初期,狀態(tài)比較少,我們通過如下方法重構(gòu)代碼就可以解決問題。
圖片
下面是我們重構(gòu)優(yōu)化代碼的例子。
重構(gòu)前:入駐方法很長,所有業(yè)務(wù)代碼邏輯耦合在一起。
class A {
//入駐方法
public void join() {
if(未入駐過){
// 實人認證
//查詢關(guān)聯(lián)賬號
...//其他業(yè)務(wù)邏輯
}else if(曾入駐過){
//查詢關(guān)聯(lián)賬號
//計算售后率
...//其他業(yè)務(wù)邏輯
}
}
重構(gòu)后:提取出實人認證、計算售后率等方法。
class A {
//入駐方法
public void join() {
if(未入駐過){
// 實人認證
realPersonAuthentication();
...//其他業(yè)務(wù)邏輯
}else if(曾入駐過){
//計算售后率
calculatingAfterSalesRate();
...//其他業(yè)務(wù)邏輯
}
//查詢關(guān)聯(lián)賬號
associatedAccount();
}
public void realPersonAuthentication() {
// 實人認證
}
public void associatedAccount() {
//查詢關(guān)聯(lián)賬號
}
public void calculatingAfterSalesRate() {
//計算售后率
}
}
2.2 使用設(shè)計模式重構(gòu)優(yōu)化
使用狀態(tài)模式、策略模式等都能幫助我們的代碼清晰易讀。
下面就是個狀態(tài)模式解決不同入駐狀態(tài)下商戶擁有不同權(quán)限的例子,如果你的業(yè)務(wù)場景有如下特點,那么可以嘗試使用狀態(tài)模式優(yōu)化。
1)對象行為取決于狀態(tài)。
比如商戶是否可以送檢行為取決于商戶的已入駐狀態(tài)。
2)狀態(tài)轉(zhuǎn)換邏輯復雜。
比如商戶申請關(guān)戶中、待確認貨損狀態(tài)才能撤銷關(guān)戶等。
3)避免大量條件語句。
這個業(yè)務(wù)場景,如果使用ifelse判斷,需要寫6個,代碼也不好擴展維護。
業(yè)務(wù)規(guī)則:
圖片
狀態(tài)模式類圖:
狀態(tài)模式類圖
2.3 使用狀態(tài)機
如果你的狀態(tài)很多,并且轉(zhuǎn)換邏輯很復雜的時候,我們可以使用狀態(tài)機,幫助我們進行狀態(tài)管理。
2.3.1 思考狀態(tài)機特點
看上面狀態(tài)圖,思考我們開發(fā)的其他場景的狀態(tài)流轉(zhuǎn)圖,這些場景是不是有以下共同特點:1)根據(jù)不同觸發(fā)條件執(zhí)行不同處理動作最后轉(zhuǎn)變?yōu)樾碌臓顟B(tài)(狀態(tài)也有可能不變)。
2)狀態(tài)間轉(zhuǎn)換邏輯比較復雜。如果使用ifelse進行判斷,業(yè)務(wù)復雜之后很難維護。
3)各個狀態(tài)轉(zhuǎn)換分散在不同方法中,很難清晰知道狀態(tài)全部的流轉(zhuǎn)規(guī)則。
2.3.2 總結(jié)歸納狀態(tài)機特點
針對狀態(tài)流轉(zhuǎn)過程,其實是有很多相似的地方,我們可以簡單總結(jié)歸納一下。
1)我們可以考慮將狀態(tài)全部從業(yè)務(wù)層抽離,統(tǒng)一進行收口維護。
2)抽象狀態(tài)流轉(zhuǎn)規(guī)則, 比如:已入駐 狀態(tài)轉(zhuǎn)變?yōu)?清退中 狀態(tài),需要原狀態(tài)是已入駐,并要完成一些業(yè)務(wù)操作(記錄申請信息,通知商戶、BD等等)。
3)我們將狀態(tài)流轉(zhuǎn)規(guī)則也進行統(tǒng)一維護(配置文件配置、代碼里寫死配置、數(shù)據(jù)庫里配置等),這樣便能清晰看到全部流轉(zhuǎn)規(guī)則,簡化狀態(tài)管理。
3.狀態(tài)機介紹
狀態(tài)機其實很簡單,不然也不會這么流行。由上面抽象總結(jié)出來的方法,其實就是狀態(tài)機。狀態(tài)機的概念很早就提出了,而且比Java語言本身還要早。(詳見百度百科)
3.1 狀態(tài)機的四大概念
狀態(tài)機核心概念
第一個是State,狀態(tài)。一個狀態(tài)機至少要包含兩個狀態(tài)。例如上面例子,有 未入駐、已入駐、清退中、已清退等多個狀態(tài)。
第二個是Event,事件。事件就是執(zhí)行某個操作的觸發(fā)條件。對于關(guān)戶流程,”申請關(guān)戶” 就是一個事件。
第三個是Action,動作。事件發(fā)生以后要執(zhí)行的動作。例如事件是“申請關(guān)戶”,動作是“記錄申請信息、通知BD聯(lián)系商戶等”。編程的時候,一個Action一般就對應(yīng)一個函數(shù)或者類。
第四個是Transition,描述了一個狀態(tài)機如何從一個狀態(tài)轉(zhuǎn)換到另一個狀態(tài)相關(guān)規(guī)則、條件。當特定的 Event事件發(fā)生時,狀態(tài)機會根據(jù)Transition規(guī)則執(zhí)行狀態(tài)轉(zhuǎn)換。這個過程中,可能伴隨著動作(Action)的執(zhí)行,這些動作可以是狀態(tài)進入前執(zhí)行的預處理、狀態(tài)轉(zhuǎn)換時執(zhí)行的中間操作或狀態(tài)退出后執(zhí)行的清理工作。例如 “商戶操作入駐,狀態(tài)從未入駐轉(zhuǎn)到已入駐” 就是一個變換。
3.2 狀態(tài)機的優(yōu)點
參考網(wǎng)上各種描述,這里就簡單提一下
1)清晰的狀態(tài)管理:其實就是統(tǒng)一在一個地方配置轉(zhuǎn)態(tài)流轉(zhuǎn)規(guī)則,這有助于確保系統(tǒng)的行為符合預期(可以在配置文件配置,代碼寫死,數(shù)據(jù)庫配置流轉(zhuǎn)規(guī)則),減少錯誤和不確定性。
2)易于維護和擴展:狀態(tài)機的結(jié)構(gòu)使得對系統(tǒng)的修改和擴展變得更加容易。由于狀態(tài)機的各個部分相對獨立,因此可以在不干擾其他部分的情況下添加新狀態(tài)或修改現(xiàn)有狀態(tài)
3)促進團隊溝通:比如前面的狀態(tài)流轉(zhuǎn)圖,狀態(tài)機提供了一種圖形化的方式來描述系統(tǒng)行為,這對于團隊成員之間的溝通非常有幫助。非技術(shù)團隊成員也能理解狀態(tài)圖,從而更好地參與項目討論
4.狀態(tài)機對比與選型
下面我們就介紹幾種狀態(tài)機的核心實現(xiàn),大家可以作為參考,搭建符合自己業(yè)務(wù)場景的狀態(tài)機。
4.1 轉(zhuǎn)轉(zhuǎn)狀態(tài)機
轉(zhuǎn)轉(zhuǎn)狀態(tài)機主要看這個圖。
轉(zhuǎn)轉(zhuǎn)狀態(tài)機流程圖
1)項目啟動,從Mysql數(shù)據(jù)庫讀取狀態(tài)機配置的狀態(tài)轉(zhuǎn)換規(guī)則Transition,并在內(nèi)存使用Map保存。
2)Event事件發(fā)生后,根據(jù)當前狀態(tài)、事件到Map獲取對應(yīng)的Action執(zhí)行。
3)這是init()方法加載轉(zhuǎn)換規(guī)則,轉(zhuǎn)換為Map,Map<String, List> fsmNodeMap,key是事件+當前狀態(tài),value是要執(zhí)行的FsmNode類。
public class FsmNode {
private Integer opType;
private Integer role;
private Integer sourceStatus;
private Integer targetStatus;
private NodeType nodeType;
private FsmAction action;
private HashSet<JobConfig> jobConfigs;
private HashSet<TransactionConfig> transactionConfigs;
}
總結(jié)起來有如下特點
1)把狀態(tài)機流轉(zhuǎn)條件、執(zhí)行的業(yè)務(wù)類名(Transition)配置到數(shù)據(jù)庫。如果你的狀態(tài)機、狀態(tài)轉(zhuǎn)換條件特別多,那么存儲在數(shù)據(jù)庫就很方便查找。
2)通過定時任務(wù)輪訓數(shù)據(jù)表,重試執(zhí)行失敗的Action。
3)無狀態(tài)設(shè)計,每次狀態(tài)流轉(zhuǎn)無需生成狀態(tài)機實例,只要根據(jù)當前狀態(tài)、事件到Map里找到需要執(zhí)行的Action及job來執(zhí)行就好。
4)支持每次狀態(tài)轉(zhuǎn)換的時候發(fā)送事務(wù)消息(結(jié)合數(shù)據(jù)庫事務(wù)實現(xiàn))適用場景:狀態(tài)機轉(zhuǎn)換條件配置特別多,需要定時任務(wù)補償執(zhí)行,需要在狀態(tài)轉(zhuǎn)換時候發(fā)送事務(wù)消息的場景。
以上就是宙斯狀態(tài)機的核心功能,總結(jié)來說就是:把事件、當前狀態(tài)、目標狀態(tài)、要執(zhí)行的Action類配置數(shù)據(jù)庫里,執(zhí)行時候根據(jù)這個配置來查找Action并執(zhí)行。
4.2 Cola狀態(tài)機
參考Cola的官方文檔。阿里團隊覺得開源狀態(tài)機不好用,太復雜且性能差,就自己寫了一個簡潔版狀態(tài)機。詳見博客
Cola狀態(tài)機是一個輕量級的開源框架,相比Spring Statemachine和squirrel-foundation,它更加簡單、輕量且性能極高。
Cola狀態(tài)機主要有如下特點:
1)簡單、輕量,僅支持狀態(tài)流轉(zhuǎn)的狀態(tài)機,不支持嵌套、并行等高級玩法。
2)無狀態(tài)設(shè)計。可以使用一個狀態(tài)機實例來響應(yīng)所有的請求。
適用場景:Cola狀態(tài)機適用于需要高并發(fā)支持且希望簡化狀態(tài)管理的場景。
4.2.1 Cola狀態(tài)機核心源碼解析
Cola 狀態(tài)機本質(zhì)上是狀態(tài)模式+兩個Map。
核心類
第一個Map,Map<S, State< S, E, C>> stateMap,key為當前狀態(tài),value為State對象。
State對象類似狀態(tài)模式,內(nèi)部存儲了狀態(tài)轉(zhuǎn)移transitions map,即第二個Map。
第二個Map,HashMap<E, Transition<S, E,C>> transitions,在State對象內(nèi)部,key是Event事件,value是Transition對象,Transition內(nèi)有要執(zhí)行的Action、初始狀態(tài)、目標狀態(tài)、驅(qū)動事件(Event)等。
執(zhí)行過程
1)業(yè)務(wù)代碼執(zhí)行入口,根據(jù)當前狀態(tài)、事件傳參執(zhí)行。
StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID+"1");
//觸發(fā)一次事件代碼
States target = stateMachine.fireEvent(States.STATE2, Events.EVENT1, new Context());
2)根據(jù)當前狀態(tài)找到map里的State。
public S fireEvent(S sourceStateId, E event, C ctx){
isReady();
//找到當前狀態(tài)對應(yīng)的State
State sourceState = getState(sourceStateId);
//執(zhí)行
return doTransition(sourceState, event, ctx).getId();
}
3)從State里找到另一個Map,transitions,找到map配置的value,轉(zhuǎn)換規(guī)則。
private State<S, E, C> doTransition(State sourceState, E event, C ctx) {
//找到對應(yīng)的Transition
Optional<Transition<S,E,C>> transition = sourceState.getTransition(event);
if(transition.isPresent()){
//執(zhí)行
return transition.get().transit(ctx);
}
Debugger.debug("There is no Transition for " + event);
return sourceState;
}
4)找到Transition里的action執(zhí)行。
@Override
public State<S, E, C> transit(C ctx) {
Debugger.debug("Do transition: "+this);
this.verify();
if(condition == null || condition.isSatisfied(ctx)){
if(action != null){
//執(zhí)行具體的action
action.execute(source.getId(), target.getId(), event, ctx);
}
return target;
}
Debugger.debug("Condition is not satisfied, stay at the "+source+" state ");
return source;
}
Cola的源碼很簡單,就是把當前狀態(tài)、觸發(fā)的事件、目標狀態(tài)、執(zhí)行的業(yè)務(wù)Action都配置兩個Map里,執(zhí)行時候根據(jù)當前狀態(tài),事件分別去兩個map里拿Action出來執(zhí)行就好了。
4.3 Spring Statemachine
參考Spring Statemachine的官方文檔。Spring官方提供的一個狀態(tài)機框架,支持狀態(tài)的嵌套(substate)、狀態(tài)的并行(parallel, fork, join)、子狀態(tài)機等高級特性。
1)簡單易用
2)狀態(tài)機結(jié)構(gòu)層次化,有助于簡化狀態(tài)控制的開發(fā)過程。
3、功能完備。與Cola狀態(tài)機類似,也包括狀態(tài)(State)、事件(Event)、轉(zhuǎn)換(Transition)、動作(Action)等要素。并提供了更豐富的狀態(tài)類型配置,如choice、join、fork、history等。
適用場景:適用于需要將復雜邏輯拆分為較小可管理任務(wù)、狀態(tài)嵌套或需要循環(huán)遍歷if-else結(jié)構(gòu)并進行異常處理的場景。
4.3.1 部分源碼解析
1)我們看看狀態(tài)轉(zhuǎn)換時,sendEvent都進行了哪些操作。
Region.sendEvent() 向狀態(tài)機發(fā)布事件。
private Flux<StateMachineEventResult<S, E>> handleEvent(Message<E> message) {
//檢查狀態(tài)機錯誤
//如果狀態(tài)機存在錯誤(通過hasStateMachineError()方法檢查),則立即返回一個包含單個StateMachineEventResult對象的Flux流。
//這個對象表示事件被拒絕(ResultType.DENIED),并包含了當前狀態(tài)機實例、原始消息和拒絕結(jié)果類型。
if (hasStateMachineError()) {
return Flux.just(StateMachineEventResult.<S, E>from(this, message, ResultType.DENIED));
}
//處理正常事件流:
return Mono.just(message)
.map(m -> getStateMachineInterceptors().preEvent(m, this))
.flatMapMany(m -> acceptEvent(m))
.onErrorResume(error -> Flux.just(StateMachineEventResult.<S, E>from(this, message, ResultType.DENIED)))
.doOnNext(notifyOnDenied());
}
AbstractStateMachine.acceptEvent()接收事件。
//發(fā)送事件并收集結(jié)果:
return cs.sendEvent(message)
.collectList()
//處理發(fā)送事件的結(jié)果:
使用collectList().flatMapMany(l -> {...})來收集sendEvent的結(jié)果,并將它們轉(zhuǎn)換為一個新的Flux流。
.flatMapMany(l -> {
Flux<StateMachineEventResult<S, E>> ret = Flux.fromIterable(l);
if (!l.stream().anyMatch(er -> er.getResultType() == ResultType.ACCEPTED)) {
Mono<StateMachineEventResult<S, E>> result = Flux.fromIterable(transitions)
.filter(transition -> cs != null && transition.getTrigger() != null)
.filter(transition -> StateMachineUtils.containsAtleastOne(transition.getSource().getIds(), cs.getIds()))
.flatMap(transition -> {
//查找可接受的觸發(fā)條件:
遍歷狀態(tài)轉(zhuǎn)換(transitions),找到與當前狀態(tài)和事件負載匹配的觸發(fā)條件。
對每個匹配的觸發(fā)條件,評估其是否應(yīng)該觸發(fā)(通過transition.getTrigger().evaluate(triggerContext))。
如果觸發(fā)條件評估為真,則使用stateMachineExecutor.queueEvent將事件加入隊列,并設(shè)置一個回調(diào)來處理執(zhí)行結(jié)果。
return Mono.from(transition.getTrigger().evaluate(triggerContext))
.flatMap(
...
return ret;
StateMachineExecutor.queueEvent()將事件添加到狀態(tài)機的隊列。
public Mono<Void> queueEvent(Mono<Message<E>> message, StateMachineExecutorCallback callback) {
//這行代碼將傳入的事件消息(message)和一個延遲事件列表(deferList)合并成一個單一的Flux<Message<E>>流。這意味著所有這些事件都將按順序被處理。
Flux<Message<E>> messages = Flux.merge(message, Flux.fromIterable(deferList));
MonoSinkStateMachineExecutorCallback triggerCallback = new MonoSinkStateMachineExecutorCallback();
Mono<Void> triggerCallbackSink = Mono.create(triggerCallback);
return messages
.flatMap(m -> handleEvent(m, callback, triggerCallback))
.flatMap(tqi -> Mono.fromRunnable(() -> {
triggerSink.emitNext(tqi, EmitFailureHandler.FAIL_FAST);
})
.retryWhen(Retry.fixedDelay(10, Duration.ofMillis(10))))
.then()
.and(triggerCallbackSink);
}
從上面的源碼我們大概可以看出 Spring狀態(tài)機內(nèi)部通過事件發(fā)布、訂閱和線程池、阻塞隊列實現(xiàn)了整個狀態(tài)的流轉(zhuǎn)。到這里介紹完畢Spring狀態(tài)機,詳細使用建議參考官方文檔,里面有很多高級用法,這里只是進行簡單功能介紹。
5.選型總結(jié)
特性 | Cola狀態(tài)機 | Spring狀態(tài)機 | 轉(zhuǎn)轉(zhuǎn)狀態(tài)機 |
集成性 | 較為獨立,但易于集成到其他Java項目中 | 與Spring框架緊密集成,方便使用Spring的各種特性(如依賴注入、AOP等) | 依賴數(shù)據(jù)庫,易于集成到Java項目 |
功能豐富性 | 支持基本的狀態(tài)定義和轉(zhuǎn)換 | 提供了完整的狀態(tài)機功能,包括狀態(tài)定義、事件觸發(fā)、動作執(zhí)行、狀態(tài)轉(zhuǎn)換和守衛(wèi)條件等,并發(fā)狀態(tài)、子狀態(tài)機 | 支持基本的狀態(tài)定義和轉(zhuǎn)換,同時提供失敗重試、事務(wù)消息等功能 |
學習成本 | 低,源碼較簡單,同步API,使用更為直觀 | 較高,使用Reactive的Mono、Flux等響應(yīng)式編程模型 | 較低,源碼較簡單易理解 |
使用場景 | 適用于中小型項目、需要輕量級狀態(tài)機管理的場景 | 適用于需要管理復雜狀態(tài)轉(zhuǎn)換邏輯、與Spring框架緊密結(jié)合的業(yè)務(wù)場景 | 需要輕量級狀態(tài)機管理,狀態(tài)機配置較多場景 |
5.1 業(yè)務(wù)簡單用Cola狀態(tài)機
從前面的源碼分析來看,Cola狀態(tài)機通過兩個map來實現(xiàn)狀態(tài)流轉(zhuǎn),源碼簡單易懂,擴展也方便。更加簡潔和直觀的狀態(tài)管理方式。如果你的項目比較簡單,開發(fā)人員也比較少,僅使用狀態(tài)機輔助狀態(tài)流轉(zhuǎn),在滿足業(yè)務(wù)需求的前提下,可以考慮使用Cola狀態(tài)機。
5.2 業(yè)務(wù)復雜用Spring Statemachine
Spring Statemachine源碼、功能相對復雜,使用起來不夠直觀;同時不是無狀態(tài)的,因此在線程安全性方面需要額外的處理,但是功能完備,提供了豐富的狀態(tài)和事件處理機制,與Spring框架緊密集成,可以利用Spring的依賴注入、面向切面編程等特性,方便地進行擴展和集成。如果你的狀態(tài)轉(zhuǎn)換比較復雜,涉及到層次化狀態(tài)、并發(fā)狀態(tài)、子狀態(tài)機,同時研發(fā)人員較多,研發(fā)實力較強,后續(xù)考慮擴展功能,可以考慮使用Spring的狀態(tài)機。
5.3 個性化訴求自研
如果你考慮自研狀態(tài)機,并且你的狀態(tài)轉(zhuǎn)換比較多,或者轉(zhuǎn)換后,Action有重試需求,也可以參考轉(zhuǎn)轉(zhuǎn)宙斯狀態(tài)機,將狀態(tài)轉(zhuǎn)換邏輯配置到數(shù)據(jù),方便查找狀態(tài)轉(zhuǎn)換配置及從數(shù)據(jù)庫獲取Action數(shù)據(jù)進行重試處理。
5.4 B2B復雜業(yè)務(wù)場景選型
下面是我把B2B整個保賣業(yè)務(wù)簡化之后的流程圖,大家可以看到整個鏈路流程是很長很復雜。
保賣業(yè)務(wù)流程簡圖
分而治之。我們把長鏈路劃分多為個階段,每個階段有自己的狀態(tài),使用狀態(tài)機來管理每個階段狀態(tài)流轉(zhuǎn),這樣就能把復雜問題簡化為每個小問題解決。
回收段:
回收段狀態(tài)圖
銷售段:
圖片
售后段:
圖片
篇幅有限,就不列出詳細代碼了。
我們在保賣業(yè)務(wù)、關(guān)戶退保證金項目中使用Spring狀態(tài)機,主要考慮是我們業(yè)務(wù)場景特別復雜。同時,我們根據(jù)保賣業(yè)務(wù)特點,在Spring狀態(tài)機基礎(chǔ)功能上進行一些擴展。
調(diào)研發(fā)現(xiàn)B2B行業(yè)友商也使用了Spring狀態(tài)機,他們考慮點也是狀態(tài)機對于復雜的業(yè)務(wù)場景的支持。
1)性能方面,我們B2B業(yè)務(wù)對并發(fā)性能要求不高,但是業(yè)務(wù)復雜度高,Spring狀態(tài)機性能完全可以滿足我們需求。
2)功能方面,Spring狀態(tài)機功能比較豐富,支持子狀態(tài)等,而我們業(yè)務(wù)需求比較復雜,同時有子狀態(tài)等業(yè)務(wù)場景,它可以滿足我們的業(yè)務(wù)需求。
3)Spring狀態(tài)機提供狀態(tài)機事件監(jiān)聽器,允許開發(fā)者監(jiān)聽狀態(tài)機的各種事件,如狀態(tài)變化、轉(zhuǎn)換觸發(fā)等,從而執(zhí)行相應(yīng)的業(yè)務(wù)邏輯。我們的關(guān)戶需求,也需要在關(guān)戶申請、申訴通過等節(jié)點觸發(fā)時進行一些通用業(yè)務(wù)邏輯。
4)我們在Spring狀態(tài)機基礎(chǔ)上擴展出事務(wù)消息,主要是基于Mysql數(shù)據(jù)庫表的事務(wù)實現(xiàn)。
5)擴展出失敗重試功能,主要基于XXL-JOB定時任務(wù)掃描失敗數(shù)據(jù)并進行重試。
其實架構(gòu)選型,沒有銀彈,也難有通用解決方案,大家還是需要結(jié)合自己的業(yè)務(wù)做選型及改造。
參考部分網(wǎng)上資料,如有侵權(quán),聯(lián)系我們。
關(guān)于作者黃培祖,采貨俠開發(fā)工程師
參考資料
[1] https://github.com/alibaba/COLA
[2] https://spring.io/projects/spring-statemachine
[3] https://blog.csdn.net/significantfrank/article/details/104996419