前言
在平常的后端項(xiàng)目開(kāi)發(fā)中,狀態(tài)機(jī)模式的使用其實(shí)沒(méi)有大家想象中那么常見(jiàn),筆者之前由于不在電商領(lǐng)域工作,很少在業(yè)務(wù)代碼中用狀態(tài)機(jī)來(lái)管理各種狀態(tài),一般都是手動(dòng)get/set狀態(tài)值。去年筆者進(jìn)入了電商領(lǐng)域從事后端開(kāi)發(fā)。電商領(lǐng)域,狀態(tài)又多又復(fù)雜,如果仍然在業(yè)務(wù)代碼中東一塊西一塊維護(hù)狀態(tài)值,很容易陷入出了問(wèn)題難于Debug,難于追責(zé)的窘境。
碰巧有個(gè)新啟動(dòng)的項(xiàng)目需要進(jìn)行訂單狀態(tài)的管理,我著手將Spring StateMachine接入了進(jìn)來(lái),管理購(gòu)物訂單狀態(tài),不得不說(shuō),Spring StateMachine全家桶的文檔寫(xiě)的是不錯(cuò),并且Spring StateMachine也是有官方背書(shū)的。但是,它實(shí)在是太”重“了,想要簡(jiǎn)單修改一個(gè)訂單的狀態(tài),需要十分復(fù)雜的代碼來(lái)實(shí)現(xiàn)。具體就不在這里展開(kāi)了,不然我感覺(jué)可以吐槽一整天。
說(shuō)到底Spring StateMachine上手難度非常大,如果沒(méi)有用來(lái)做重型狀態(tài)機(jī)的需求,十分不推薦普通的小項(xiàng)目進(jìn)行接入。
最最重要的是,由于Spring StateMachine狀態(tài)機(jī)實(shí)例不是無(wú)狀態(tài)的,無(wú)法做到線程安全,所以代碼要么需要使用鎖同步,要么需要用Threadlocal,非常的痛苦和難用。 例如下面的Spring StateMachine代碼就用了重量級(jí)鎖保證線程安全,在高并發(fā)的互聯(lián)網(wǎng)應(yīng)用中,這種代碼留的隱患非常大。
private synchronized boolean sendEvent(Message message, OrderEntity orderEntity) { boolean result = false; try { stateMachine.start(); // 嘗試恢復(fù)狀態(tài)機(jī)狀態(tài) persister.restore(stateMachine, orderEntity); // 執(zhí)行事件 result = stateMachine.sendEvent(message); // 持久化狀態(tài)機(jī)狀態(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)機(jī)的實(shí)現(xiàn),有大的開(kāi)源項(xiàng)目,也有小而美的個(gè)人實(shí)現(xiàn)。結(jié)果在COLA架構(gòu)中發(fā)現(xiàn)了COLA還寫(xiě)了一套狀態(tài)機(jī)實(shí)現(xiàn)。COLA的作者給我們提供了一個(gè)無(wú)狀態(tài)的,輕量化的狀態(tài)機(jī),接入十分簡(jiǎn)單。并且由于無(wú)狀態(tài)的特點(diǎn),可以做到線程安全,支持電商的高并發(fā)場(chǎng)景。
COLA是什么?如果你還沒(méi)聽(tīng)說(shuō)過(guò)COLA,不妨看一看我之前的文章,傳送門(mén)如下:
https://mp.weixin.qq.com/s/07i3FjcFrZ8rxBCACgeWVQ
如果你需要在項(xiàng)目中引入狀態(tài)機(jī),此時(shí)此刻,我會(huì)推薦使用COLA狀態(tài)機(jī)。
COLA狀態(tài)機(jī)介紹
COLA狀態(tài)機(jī)是在Github開(kāi)源的,作者也寫(xiě)了介紹文章:
https://blog.csdn.net/significantfrank/article/details/104996419
官方文章的前半部分重點(diǎn)介紹了DSL(Domain Specific Languages),這一部分比較抽象和概念化,大家感興趣,可以前往原文查看。我精簡(jiǎn)一下DSL的主要含義:
什么是DSL? DSL是一種工具,它的核心價(jià)值在于,它提供了一種手段,可以更加清晰地就系統(tǒng)某部分的意圖進(jìn)行溝通。
比如正則表達(dá)式,/d{3}-d{3}-d{4}/就是一個(gè)典型的DSL,解決的是字符串匹配這個(gè)特定領(lǐng)域的問(wèn)題。
文章的后半部分重點(diǎn)闡述了作者為什么要做COLA狀態(tài)機(jī)?想必這也是讀者比較好奇的問(wèn)題。我?guī)痛蠹揖?jiǎn)一下原文的表述:
- 首先,狀態(tài)機(jī)的實(shí)現(xiàn)應(yīng)該可以非常的輕量,最簡(jiǎn)單的狀態(tài)機(jī)用一個(gè)Enum就能實(shí)現(xiàn),基本是零成本。
- 其次,使用狀態(tài)機(jī)的DSL來(lái)表達(dá)狀態(tài)的流轉(zhuǎn),語(yǔ)義會(huì)更加清晰,會(huì)增強(qiáng)代碼的可讀性和可維護(hù)性。
- 開(kāi)源狀態(tài)機(jī)太復(fù)雜: 就我們的項(xiàng)目而言(其實(shí)大部分項(xiàng)目都是如此)。我實(shí)在不需要那么多狀態(tài)機(jī)的高級(jí)玩法:比如狀態(tài)的嵌套(substate),狀態(tài)的并行(parallel,fork,join)、子狀態(tài)機(jī)等等。
- 開(kāi)源狀態(tài)機(jī)性能差: 這些開(kāi)源的狀態(tài)機(jī)都是有狀態(tài)的(Stateful)的,因?yàn)橛袪顟B(tài),狀態(tài)機(jī)的實(shí)例就不是線程安全的,而我們的應(yīng)用服務(wù)器是分布式多線程的,所以在每一次狀態(tài)機(jī)在接受請(qǐng)求的時(shí)候,都不得不重新build一個(gè)新的狀態(tài)機(jī)實(shí)例。
所以COLA狀態(tài)機(jī)設(shè)計(jì)的目標(biāo)很明確,有兩個(gè)核心理念:
COLA狀態(tài)機(jī)的核心概念如下圖所示,主要包括:
State:狀態(tài) Event:事件,狀態(tài)由事件觸發(fā),引起變化 Transition:流轉(zhuǎn),表示從一個(gè)狀態(tài)到另一個(gè)狀態(tài) External Transition:外部流轉(zhuǎn),兩個(gè)不同狀態(tài)之間的流轉(zhuǎn) Internal Transition:內(nèi)部流轉(zhuǎn),同一個(gè)狀態(tài)之間的流轉(zhuǎn) Condition:條件,表示是否允許到達(dá)某個(gè)狀態(tài) Action:動(dòng)作,到達(dá)某個(gè)狀態(tài)之后,可以做什么 StateMachine:狀態(tài)機(jī)
COLA狀態(tài)機(jī)原理
這一小節(jié),我們先講幾個(gè)COLA狀態(tài)機(jī)最重要兩個(gè)部分,一個(gè)是它使用的連貫接口,一個(gè)是狀態(tài)機(jī)的注冊(cè)和使用原理。如果你暫時(shí)對(duì)它的實(shí)現(xiàn)原理不感興趣,可以直接跳過(guò)本小節(jié),直接看后面的實(shí)戰(zhàn)代碼部分。
PS:講解的代碼版本為cola-component-statemachine 4.2.0-SNAPSHOT
下圖展示了COLA狀態(tài)機(jī)的源代碼目錄,可以看到非常的簡(jiǎn)潔。
1. 連貫接口 Fluent Interfaces
COLA狀態(tài)機(jī)的定義使用了連貫接口Fluent Interfaces,連貫接口的一個(gè)重要作用是,限定方法調(diào)用的順序。比如,在構(gòu)建狀態(tài)機(jī)的時(shí)候,我們只有在調(diào)用了from方法后,才能調(diào)用to方法,Builder模式?jīng)]有這個(gè)功能。
下圖中可以看到,我們?cè)谑褂玫臅r(shí)候是被嚴(yán)格限制的:
StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(States.STATE1) .to(States.STATE2) .on(Events.EVENT1) .when(checkCondition()) .perform(doAction());
這是如何實(shí)現(xiàn)的?其實(shí)是使用了Java接口來(lái)實(shí)現(xiàn)。
2. 狀態(tài)機(jī)注冊(cè)和觸發(fā)原理
這里簡(jiǎn)單梳理一下?tīng)顟B(tài)機(jī)的注冊(cè)和觸發(fā)原理。
用戶執(zhí)行如下代碼來(lái)創(chuàng)建一個(gè)狀態(tài)機(jī),指定一個(gè)MACHINE_ID:
StateMachine stateMachine = builder.build(MACHINE_ID);
COLA會(huì)將該狀態(tài)機(jī)在StateMachineFactory類(lèi)中,放入一個(gè)ConcurrentHashMap,以狀態(tài)機(jī)名為key注冊(cè)。
static Map stateMachineMap = new ConcurrentHashMap();
注冊(cè)好后,用戶便可以使用狀態(tài)機(jī),通過(guò)類(lèi)似下方的代碼觸發(fā)狀態(tài)機(jī)的狀態(tài)流轉(zhuǎn):
stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context(“1”));
內(nèi)部實(shí)現(xiàn)如下:
transition.transit方法中:
檢查本次流轉(zhuǎn)是否符合condition,符合,則執(zhí)行對(duì)應(yīng)的action。
COLA狀態(tài)機(jī)實(shí)戰(zhàn)
**PS:以下實(shí)戰(zhàn)代碼取自COLA官方倉(cāng)庫(kù)測(cè)試類(lèi)
一、狀態(tài)流轉(zhuǎn)使用示例
@Testpublic 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); };}
可以看到,每次進(jìn)行狀態(tài)流轉(zhuǎn)時(shí),檢查checkCondition(),當(dāng)返回true,執(zhí)行狀態(tài)流轉(zhuǎn)的操作doAction()。
后面所有的checkCondition()和doAction()方法在下方就不再重復(fù)貼出了。
@Testpublic 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);}
@Testpublic 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);}
@Testpublic void testMultiThread(){ buildStateMachine(“testMultiThread”); for(int i=0 ; i{ 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 { 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 { 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)機(jī)時(shí)無(wú)狀態(tài)的狀態(tài)機(jī),所以性能是很高的。相比起來(lái),SpringStateMachine由于是有狀態(tài)的,就需要使用者自行保證線程安全了。
二、多分支狀態(tài)流轉(zhuǎn)示例
/*** 測(cè)試選擇分支,針對(duì)同一個(gè)事件:EVENT1* if condition == “1”, STATE1 –> STATE1* if condition == “2” , STATE1 –> STATE2* if condition == “3” , STATE1 –> STATE3*/@Testpublic 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); }
可以看到,編寫(xiě)一個(gè)多分支的狀態(tài)機(jī)也是非常簡(jiǎn)單明了的。
三、通過(guò)狀態(tài)機(jī)反向生成PlantUml圖
沒(méi)想到吧,還能通過(guò)代碼定義好的狀態(tài)機(jī)反向生成plantUML圖,實(shí)現(xiàn)狀態(tài)機(jī)的可視化。(可以用圖說(shuō)話,和產(chǎn)品對(duì)比下?tīng)顟B(tài)實(shí)現(xiàn)的是否正確了。)
四、特殊使用示例
@Testpublic 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);}
可以看到,當(dāng)checkConditionFalse()執(zhí)行時(shí),永遠(yuǎn)不會(huì)滿足狀態(tài)流轉(zhuǎn)的條件,則狀態(tài)不會(huì)變化,會(huì)直接返回原來(lái)的STATE1。相關(guān)源碼在這里:
@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());}
會(huì)在第二次builder執(zhí)行到on(StateMachineTest.Events.EVENT1)函數(shù)時(shí),拋出StateMachineException異常。拋出異常在on()的verify檢查這里,如下:
@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”);}
會(huì)在第二次build同名狀態(tài)機(jī)時(shí)拋出StateMachineException異常。拋出異常的源碼在狀態(tài)機(jī)的注冊(cè)函數(shù)中,如下:
結(jié)語(yǔ)
為了不把篇幅拉得過(guò)長(zhǎng),在這里無(wú)法詳細(xì)地橫向?qū)Ρ葞状笾髁鳡顟B(tài)機(jī)(Spring Statemachine,Squirrel statemachine等)和COLA的區(qū)別,不過(guò)基于筆者在Spring Statemachine踩過(guò)的深坑,目前來(lái)看,COLA狀態(tài)機(jī)的簡(jiǎn)潔設(shè)計(jì)適合用在訂單管理等小型狀態(tài)機(jī)的維護(hù),如果你想要在你的項(xiàng)目中接入狀態(tài)機(jī),又不需要嵌套、并行等高級(jí)玩法,那么COLA是個(gè)十分合適的選擇。
我是后端工程師,蠻三刀醬。
持續(xù)的更新原創(chuàng)優(yōu)質(zhì)文章,離不開(kāi)你的點(diǎn)贊,轉(zhuǎn)發(fā)和分享!
我的唯一技術(shù)公眾號(hào):后端技術(shù)漫談
– END –