<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          管理訂單狀態(tài),該用上狀態(tài)機嗎?

          共 10466字,需瀏覽 21分鐘

           ·

          2022-05-22 11:17

          點擊上方關注公眾號:后端技術漫談

          技術之路不迷路~

          前言

          在平常的后端項目開發(fā)中,狀態(tài)機模式的使用其實沒有大家想象中那么常見,筆者之前由于不在電商領域工作,很少在業(yè)務代碼中用狀態(tài)機來管理各種狀態(tài),一般都是手動get/set狀態(tài)值。去年筆者進入了電商領域從事后端開發(fā)。電商領域,狀態(tài)又多又復雜,如果仍然在業(yè)務代碼中東一塊西一塊維護狀態(tài)值,很容易陷入出了問題難于Debug,難于追責的窘境。

          碰巧有個新啟動的項目需要進行訂單狀態(tài)的管理,我著手將Spring StateMachine接入了進來,管理購物訂單狀態(tài),不得不說,Spring StateMachine全家桶的文檔寫的是不錯,并且Spring StateMachine也是有官方背書的。但是,它實在是太”重“了,想要簡單修改一個訂單的狀態(tài),需要十分復雜的代碼來實現(xiàn)。具體就不在這里展開了,不然我感覺可以吐槽一整天。

          說到底Spring StateMachine上手難度非常大,如果沒有用來做重型狀態(tài)機的需求,十分不推薦普通的小項目進行接入。

          最最重要的是,由于Spring StateMachine狀態(tài)機實例不是無狀態(tài)的,無法做到線程安全,所以代碼要么需要使用鎖同步,要么需要用Threadlocal,非常的痛苦和難用。 例如下面的Spring StateMachine代碼就用了重量級鎖保證線程安全,在高并發(fā)的互聯(lián)網(wǎng)應用中,這種頻繁的獲取釋放鎖會造成嚴重的性能問題。

          private?synchronized?boolean?sendEvent(Message?message,?OrderEntity?orderEntity)?{
          ????????boolean?result?=?false;
          ????????try?{
          ????????????stateMachine.start();
          ????????????//?嘗試恢復狀態(tài)機狀態(tài)
          ????????????persister.restore(stateMachine,?orderEntity);
          ????????????//?執(zhí)行事件
          ????????????result?=?stateMachine.sendEvent(message);
          ????????????//?持久化狀態(tài)機狀態(tài)
          ????????????persister.persist(stateMachine,?(OrderEntity)?message.getHeaders().get("purchaseOrder"));
          ????????}?catch?(Exception?e)?{
          ????????????log.error("sendEvent?error",?e);
          ????????}?finally?{
          ????????????stateMachine.stop();
          ????????}
          ????????return?result;
          ????}

          吃了一次虧后,我再一次在網(wǎng)上翻閱各種Java狀態(tài)機的實現(xiàn),有大的開源項目,也有小而美的個人實現(xiàn)。結果在COLA架構中發(fā)現(xiàn)了COLA還寫了一套狀態(tài)機實現(xiàn)。COLA的作者給我們提供了一個無狀態(tài)的,輕量化的狀態(tài)機,接入十分簡單。并且由于無狀態(tài)的特點,可以做到線程安全,支持電商的高并發(fā)場景。

          COLA是什么?如果你還沒聽說過COLA,不妨看一看我之前的文章,傳送門如下:

          https://mp.weixin.qq.com/s/07i3FjcFrZ8rxBCACgeWVQ

          如果你確實需要在項目中引入狀態(tài)機,此時此刻,我會推薦使用COLA狀態(tài)機。

          COLA狀態(tài)機介紹

          COLA狀態(tài)機是在Github開源的,作者也寫了介紹文章:

          https://blog.csdn.net/significantfrank/article/details/104996419

          官方文章的前半部分重點介紹了DSL(Domain Specific Languages),這一部分比較抽象和概念化,大家感興趣,可以前往原文查看。我精簡一下DSL的主要含義:

          什么是DSL? DSL是一種工具,它的核心價值在于,它提供了一種手段,可以更加清晰地就系統(tǒng)某部分的意圖進行溝通。

          比如正則表達式,/\d{3}-\d{3}-\d{4}/就是一個典型的DSL,解決的是字符串匹配這個特定領域的問題。

          文章的后半部分重點闡述了作者為什么要做COLA狀態(tài)機?想必這也是讀者比較好奇的問題。我?guī)痛蠹揖喴幌略牡谋硎觯?/p>

          • 首先,狀態(tài)機的實現(xiàn)應該可以非常的輕量,最簡單的狀態(tài)機用一個Enum就能實現(xiàn),基本是零成本。
          • 其次,使用狀態(tài)機的DSL來表達狀態(tài)的流轉,語義會更加清晰,會增強代碼的可讀性和可維護性。
          • 開源狀態(tài)機太復雜:就我們的項目而言(其實大部分項目都是如此)。我實在不需要那么多狀態(tài)機的高級玩法:比如狀態(tài)的嵌套(substate),狀態(tài)的并行(parallel,fork,join)、子狀態(tài)機等等。
          • 開源狀態(tài)機性能差:這些開源的狀態(tài)機都是有狀態(tài)的(Stateful)的,因為有狀態(tài),狀態(tài)機的實例就不是線程安全的,而我們的應用服務器是分布式多線程的,所以在每一次狀態(tài)機在接受請求的時候,都不得不重新build一個新的狀態(tài)機實例。

          所以COLA狀態(tài)機設計的目標很明確,有兩個核心理念:

          1. 簡潔的僅支持狀態(tài)流轉的狀態(tài)機,不需要支持嵌套、并行等高級玩法。
          2. 狀態(tài)機本身需要是Stateless(無狀態(tài))的,這樣一個Singleton Instance就能服務所有的狀態(tài)流轉請求了。

          COLA狀態(tài)機的核心概念如下圖所示,主要包括:

          State:狀態(tài) Event:事件,狀態(tài)由事件觸發(fā),引起變化 Transition:流轉,表示從一個狀態(tài)到另一個狀態(tài) External Transition:外部流轉,兩個不同狀態(tài)之間的流轉 Internal Transition:內部流轉,同一個狀態(tài)之間的流轉 Condition:條件,表示是否允許到達某個狀態(tài) Action:動作,到達某個狀態(tài)之后,可以做什么 StateMachine:狀態(tài)機

          COLA狀態(tài)機原理

          這一小節(jié),我們先講幾個COLA狀態(tài)機最重要兩個部分,一個是它使用的連貫接口,一個是狀態(tài)機的注冊和使用原理。如果你暫時對它的實現(xiàn)原理不感興趣,可以直接跳過本小節(jié),直接看后面的實戰(zhàn)代碼部分。

          PS:講解的代碼版本為cola-component-statemachine 4.2.0-SNAPSHOT

          下圖展示了COLA狀態(tài)機的源代碼目錄,可以看到非常的簡潔。

          1. 連貫接口 Fluent Interfaces

          COLA狀態(tài)機的定義使用了連貫接口Fluent Interfaces,連貫接口的一個重要作用是,限定方法調用的順序。比如,在構建狀態(tài)機的時候,我們只有在調用了from方法后,才能調用to方法,Builder模式?jīng)]有這個功能。

          下圖中可以看到,我們在使用的時候是被嚴格限制的:

          StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ????????builder.externalTransition()
          ????????????????.from(States.STATE1)
          ????????????????.to(States.STATE2)
          ????????????????.on(Events.EVENT1)
          ????????????????.when(checkCondition())
          ????????????????.perform(doAction());

          這是如何實現(xiàn)的?其實是使用了Java接口來實現(xiàn)。

          2. 狀態(tài)機注冊和觸發(fā)原理

          這里簡單梳理一下狀態(tài)機的注冊和觸發(fā)原理。

          用戶執(zhí)行如下代碼來創(chuàng)建一個狀態(tài)機,指定一個MACHINE_ID:

          StateMachine?stateMachine?=?builder.build(MACHINE_ID);

          COLA會將該狀態(tài)機在StateMachineFactory類中,放入一個ConcurrentHashMap,以狀態(tài)機名為key注冊。

          static?Map?stateMachineMap?=?new?ConcurrentHashMap<>();

          注冊好后,用戶便可以使用狀態(tài)機,通過類似下方的代碼觸發(fā)狀態(tài)機的狀態(tài)流轉:

          stateMachine.fireEvent(StateMachineTest.States.STATE1,?StateMachineTest.Events.EVENT1,?new?Context("1"));

          內部實現(xiàn)如下:

          1. 首先判斷COLA狀態(tài)機整個組件是否初始化完成。
          2. 通過routeTransition尋找是否有符合條件的狀態(tài)流轉。
          3. transition.transit執(zhí)行狀態(tài)流轉。

          transition.transit方法中:

          檢查本次流轉是否符合condition,符合,則執(zhí)行對應的action。

          COLA狀態(tài)機實戰(zhàn)

          **PS:以下實戰(zhàn)代碼取自COLA官方倉庫測試類

          一、狀態(tài)流轉使用示例

          1. 從單一狀態(tài)流轉到另一個狀態(tài)
          @Test
          public?void?testExternalNormal(){
          ????StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ????builder.externalTransition()
          ????????????.from(States.STATE1)
          ????????????.to(States.STATE2)
          ????????????.on(Events.EVENT1)
          ????????????.when(checkCondition())
          ????????????.perform(doAction());

          ????StateMachine?stateMachine?=?builder.build(MACHINE_ID);
          ????States?target?=?stateMachine.fireEvent(States.STATE1,?Events.EVENT1,?new?Context());
          ????Assert.assertEquals(States.STATE2,?target);
          }

          private?Condition?checkCondition()?{
          ??return?(ctx)?->?{return?true;};
          }

          private?Action?doAction()?{
          ????return?(from,?to,?event,?ctx)->{
          ????????System.out.println(ctx.operator+"?is?operating?"+ctx.entityId+"?from:"+from+"?to:"+to+"?on:"+event);
          ????????};
          }

          可以看到,每次進行狀態(tài)流轉時,檢查checkCondition(),當返回true,執(zhí)行狀態(tài)流轉的操作doAction()。

          后面所有的checkCondition()和doAction()方法在下方就不再重復貼出了。

          1. 從多個狀態(tài)流傳到新的狀態(tài)
          @Test
          public?void?testExternalTransitionsNormal(){
          ????StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ????builder.externalTransitions()
          ????????????.fromAmong(States.STATE1,?States.STATE2,?States.STATE3)
          ????????????.to(States.STATE4)
          ????????????.on(Events.EVENT1)
          ????????????.when(checkCondition())
          ????????????.perform(doAction());

          ????StateMachine?stateMachine?=?builder.build(MACHINE_ID+"1");
          ????States?target?=?stateMachine.fireEvent(States.STATE2,?Events.EVENT1,?new?Context());
          ????Assert.assertEquals(States.STATE4,?target);
          }
          1. 狀態(tài)內部觸發(fā)流轉
          @Test
          public?void?testInternalNormal(){
          ????StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ????builder.internalTransition()
          ????????????.within(States.STATE1)
          ????????????.on(Events.INTERNAL_EVENT)
          ????????????.when(checkCondition())
          ????????????.perform(doAction());
          ????StateMachine?stateMachine?=?builder.build(MACHINE_ID+"2");

          ????stateMachine.fireEvent(States.STATE1,?Events.EVENT1,?new?Context());
          ????States?target?=?stateMachine.fireEvent(States.STATE1,?Events.INTERNAL_EVENT,?new?Context());
          ????Assert.assertEquals(States.STATE1,?target);
          }
          1. 多線程測試并發(fā)測試
          @Test
          public?void?testMultiThread(){
          ?buildStateMachine("testMultiThread");

          ??for(int?i=0?;?i<10?;?i++){
          ???Thread?thread?=?new?Thread(()->{
          ??????StateMachine?stateMachine?=?StateMachineFactory.get("testMultiThread");
          ??????States?target?=?stateMachine.fireEvent(States.STATE1,?Events.EVENT1,?new?Context());
          ??????Assert.assertEquals(States.STATE2,?target);
          ??????});
          ??????thread.start();
          ????}


          ????for(int?i=0?;?i<10?;?i++)?{
          ??????Thread?thread?=?new?Thread(()?->?{
          ??????StateMachine?stateMachine?=?StateMachineFactory.get("testMultiThread");
          ??????States?target?=?stateMachine.fireEvent(States.STATE1,?Events.EVENT4,?new?Context());
          ??????Assert.assertEquals(States.STATE4,?target);
          ??????});
          ??????thread.start();
          ????}

          ????for(int?i=0?;?i<10?;?i++)?{
          ??????Thread?thread?=?new?Thread(()?->?{
          ??????StateMachine?stateMachine?=?StateMachineFactory.get("testMultiThread");
          ??????States?target?=?stateMachine.fireEvent(States.STATE1,?Events.EVENT3,?new?Context());
          ??????Assert.assertEquals(States.STATE3,?target);
          ??????});
          ??????thread.start();
          ??}

          }

          由于COLA狀態(tài)機時無狀態(tài)的狀態(tài)機,所以性能是很高的。相比起來,SpringStateMachine由于是有狀態(tài)的,就需要使用者自行保證線程安全了。

          二、多分支狀態(tài)流轉示例

          /**
          *?測試選擇分支,針對同一個事件:EVENT1
          *?if?condition?==?"1",?STATE1?-->?STATE1
          *?if?condition?==?"2"?,?STATE1?-->?STATE2
          *?if?condition?==?"3"?,?STATE1?-->?STATE3
          */
          @Test
          public?void?testChoice(){
          ??StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ??builder.internalTransition()
          ??.within(StateMachineTest.States.STATE1)
          ??.on(StateMachineTest.Events.EVENT1)
          ??.when(checkCondition1())
          ??.perform(doAction());
          ??builder.externalTransition()
          ??.from(StateMachineTest.States.STATE1)
          ??.to(StateMachineTest.States.STATE2)
          ??.on(StateMachineTest.Events.EVENT1)
          ??.when(checkCondition2())
          ??.perform(doAction());
          ??builder.externalTransition()
          ??.from(StateMachineTest.States.STATE1)
          ??.to(StateMachineTest.States.STATE3)
          ??.on(StateMachineTest.Events.EVENT1)
          ??.when(checkCondition3())
          ??.perform(doAction());

          ??StateMachine?stateMachine?=?builder.build("ChoiceConditionMachine");
          ??StateMachineTest.States?target1?=?stateMachine.fireEvent(StateMachineTest.States.STATE1,?StateMachineTest.Events.EVENT1,?new?Context("1"));
          ??Assert.assertEquals(StateMachineTest.States.STATE1,target1);
          ??StateMachineTest.States?target2?=?stateMachine.fireEvent(StateMachineTest.States.STATE1,?StateMachineTest.Events.EVENT1,?new?Context("2"));
          ??Assert.assertEquals(StateMachineTest.States.STATE2,target2);
          ??StateMachineTest.States?target3?=?stateMachine.fireEvent(StateMachineTest.States.STATE1,?StateMachineTest.Events.EVENT1,?new?Context("3"));
          ??Assert.assertEquals(StateMachineTest.States.STATE3,target3);
          ??}

          可以看到,編寫一個多分支的狀態(tài)機也是非常簡單明了的。

          三、通過狀態(tài)機反向生成PlantUml圖

          沒想到吧,還能通過代碼定義好的狀態(tài)機反向生成plantUML圖,實現(xiàn)狀態(tài)機的可視化。(可以用圖說話,和產(chǎn)品對比下狀態(tài)實現(xiàn)的是否正確了。)

          四、特殊使用示例

          1. 不滿足狀態(tài)流轉條件時的處理
          @Test
          public?void?testConditionNotMeet(){
          ??StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ??builder.externalTransition()
          ??.from(StateMachineTest.States.STATE1)
          ??.to(StateMachineTest.States.STATE2)
          ??.on(StateMachineTest.Events.EVENT1)
          ??.when(checkConditionFalse())
          ??.perform(doAction());

          ??StateMachine?stateMachine?=?builder.build("NotMeetConditionMachine");
          ??StateMachineTest.States?target?=?stateMachine.fireEvent(StateMachineTest.States.STATE1,?StateMachineTest.Events.EVENT1,?new?StateMachineTest.Context());
          ??Assert.assertEquals(StateMachineTest.States.STATE1,target);
          }

          可以看到,當checkConditionFalse()執(zhí)行時,永遠不會滿足狀態(tài)流轉的條件,則狀態(tài)不會變化,會直接返回原來的STATE1。相關源碼在這里:


          1. 重復定義相同的狀態(tài)流轉
          @Test(expected?=?StateMachineException.class)
          public?void?testDuplicatedTransition(){
          ??StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ??builder.externalTransition()
          ??.from(StateMachineTest.States.STATE1)
          ??.to(StateMachineTest.States.STATE2)
          ??.on(StateMachineTest.Events.EVENT1)
          ??.when(checkCondition())
          ??.perform(doAction());

          ??builder.externalTransition()
          ??.from(StateMachineTest.States.STATE1)
          ??.to(StateMachineTest.States.STATE2)
          ??.on(StateMachineTest.Events.EVENT1)
          ??.when(checkCondition())
          ??.perform(doAction());
          }

          會在第二次builder執(zhí)行到on(StateMachineTest.Events.EVENT1)函數(shù)時,拋出StateMachineException異常。拋出異常在on()的verify檢查這里,如下:

          1. 重復定義狀態(tài)機
          @Test(expected?=?StateMachineException.class)
          public?void?testDuplicateMachine(){
          ??StateMachineBuilder?builder?=?StateMachineBuilderFactory.create();
          ??builder.externalTransition()
          ??.from(StateMachineTest.States.STATE1)
          ??.to(StateMachineTest.States.STATE2)
          ??.on(StateMachineTest.Events.EVENT1)
          ??.when(checkCondition())
          ??.perform(doAction());

          ??builder.build("DuplicatedMachine");
          ??builder.build("DuplicatedMachine");
          }

          會在第二次build同名狀態(tài)機時拋出StateMachineException異常。拋出異常的源碼在狀態(tài)機的注冊函數(shù)中,如下:

          結語

          為了不把篇幅拉得過長,在這里無法詳細地橫向對比幾大主流狀態(tài)機(Spring Statemachine,Squirrel statemachine等)和COLA的區(qū)別,不過基于筆者在Spring Statemachine踩過的深坑,目前來看,COLA狀態(tài)機的簡潔設計適合用在訂單管理等小型狀態(tài)機的維護,如果你想要在你的項目中接入狀態(tài)機,又不需要嵌套、并行等高級玩法,那么COLA是個十分合適的選擇。

          我是后端工程師,蠻三刀醬。

          持續(xù)的更新原創(chuàng)優(yōu)質文章,離不開你的點贊,轉發(fā)和分享!

          我的唯一技術公眾號:后端技術漫談

          往期精彩文章:


          如何使用注解優(yōu)雅的記錄操作日志


          你買的云服務器,可能正泡在水里。


          模仿UP主,做一個彈幕控制的直播間!


          如何保證同事的代碼不會腐爛?一文帶你了解 Alibaba COLA 架構


          什么是鴨子類型?什么是猴子補???


          - END -


          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本极品无码巨乳在线播放视频 | 最新三级网站 | 亚洲成人一二三区 | 青青操人人操 | 国产精品久久久久久久久久妞妞 |