狀態(tài)機引擎在vivo營銷自動化中的深度實踐 | 引擎篇02

一厢蒜、業(yè)務(wù)背景

營銷自動化平臺支持多種不同類型運營活動策略(比如:短信推送策略运嗜、微信圖文推送策略、App Push推送策略)逞泄,每種活動類型都有各自不同的執(zhí)行流程和活動狀態(tài)。比如短信活動的活動執(zhí)行流程如下:

[圖片上傳失敗...(image-611f9b-1649642767478)]

(圖1-1:短信活動狀態(tài)轉(zhuǎn)移)

整個短信活動經(jīng)歷了 未開始 → 數(shù)據(jù)準備中 → 數(shù)據(jù)已就緒 → 活動推送中→ 活動結(jié)束 多個狀態(tài)變更流程静檬。不僅如此炭懊, 我們發(fā)現(xiàn)在活動業(yè)務(wù)邏輯處理過程中,都有以下類似的特點:

  • 每增加一種新的活動業(yè)務(wù)類型拂檩,就要新增相應的活動狀態(tài)以及處理狀態(tài)變更的邏輯侮腹;
  • 當一個類型的活動業(yè)務(wù)流程有修改時,可能需要對原先的狀態(tài)轉(zhuǎn)移過程進行變更;
  • 當每個業(yè)務(wù)都各自編寫自己的狀態(tài)轉(zhuǎn)移的業(yè)務(wù)代碼時稻励,核心業(yè)務(wù)邏輯和控制邏輯耦合性會非常強父阻,擴展性差,成本也很高望抽。

針對系統(tǒng)狀態(tài)的流轉(zhuǎn)管理加矛,計算機領(lǐng)域有一套標準的理論方案模型——有限狀態(tài)機。

二煤篙、理解狀態(tài)機

2.1 狀態(tài)機定義

有限狀態(tài)機(Finite-State Machine , 縮寫:FSM)斟览,簡稱狀態(tài)機。是表示有限個狀態(tài)以及這些狀態(tài)之間的轉(zhuǎn)移和觸發(fā)動作的模型辑奈。

  • 狀態(tài)是描述系統(tǒng)對象在某個時刻所處的狀況苛茂。
  • 轉(zhuǎn)移指示狀態(tài)變更,一般是通過外部事件為條件觸發(fā)狀態(tài)的轉(zhuǎn)移鸠窗。
  • 動作是對給定狀態(tài)下要進行的操作妓羊。

簡而言之,狀態(tài)機是由事件稍计、狀態(tài)躁绸、動作三大部分組成。三者的關(guān)系是:事件觸發(fā)狀態(tài)的轉(zhuǎn)移,狀態(tài)的轉(zhuǎn)移觸發(fā)后續(xù)動作的執(zhí)行净刮。其中動作不是必須的剥哑,也可以只進行狀態(tài)轉(zhuǎn)移,不進行任何操作庭瑰。

[圖片上傳失敗...(image-c4f395-1649642767478)]

(圖2-1:狀態(tài)機組成)

所以將上述【圖1-1:短信活動狀態(tài)轉(zhuǎn)移 】使用狀態(tài)機模型來描述就是:

[圖片上傳失敗...(image-e41438-1649642767478)]

(圖2-2:短信活動狀態(tài)機)

狀態(tài)機本質(zhì)上是對系統(tǒng)的一種數(shù)學建模星持,將問題解決方案系統(tǒng)化表達出來抢埋。下面我們來看下在實際開發(fā)中有哪些實現(xiàn)狀態(tài)機的方式 弹灭。

2.2 狀態(tài)機的實現(xiàn)方式

2.2.1 基于條件判斷的實現(xiàn)

這是最直接的一種實現(xiàn)方式,所謂條件判斷就是通過使用 if-else 或 switch-case 分支判斷進行硬編碼實現(xiàn)揪垄。對于前面短信活動穷吮,基于條件判斷方式的代碼實例如下:

/**
  * 短信活動狀態(tài)枚舉
  */
public enum ActivityState {
    NOT_START(0), //活動未開始
    DATA_PREPARING(1), //數(shù)據(jù)準備中
    DATA_PREPARED(2), //數(shù)據(jù)已就緒
    DATA_PUSHING(3), //活動推送中
    FINISHED(4); //活動結(jié)束
}
 
/**
  * 短信活動狀態(tài)機
  */
public class ActivityStateMachine {
    //活動狀態(tài)
    private ActivityState currentState;
    public ActivityStateMachine() {
        this.currentState = ActivityState.NOT_START;
    }
    /**
     * 活動時間開始
     */
    public void begin() {
        if (currentState.equals(ActivityState.NOT_START)) {
            this.currentState = ActivityState.DATA_PREPARING;
            //發(fā)送通知給運營人員
            notice();
        }
        // do nothing or throw exception ...
    }
 
    /**
     * 數(shù)據(jù)計算完成
     */
    public void finishCalData() {
        if (currentState.equals(ActivityState.DATA_PREPARING)) {
            this.currentState = ActivityState.DATA_PREPARED;
            //發(fā)送通知給運營人員
            notice();
        }
        // do nothing or throw exception ...
    }
 
     /**
     * 活動推送開始
     */
    public void beginPushData() {
        //省略
    }
     /**
     * 數(shù)據(jù)推送完成
     */
    public void finishPushData() {
        //省略
    }
}

通過條件分支判斷來控制狀態(tài)的轉(zhuǎn)移和動作的觸發(fā),上述的 if 判斷條件也可以換成 switch 語句饥努,以當前狀態(tài)為分支來控制該狀態(tài)下可以執(zhí)行的操作捡鱼。

適用場景

適用于業(yè)務(wù)狀態(tài)個數(shù)少或者狀態(tài)間跳轉(zhuǎn)邏輯比較簡單的場景。

缺陷

當觸發(fā)事件和業(yè)務(wù)狀態(tài)之間對應關(guān)系不是簡單的一對一時酷愧,就需要嵌套多個條件分支判斷驾诈,分支邏輯會變得異常復雜;當狀態(tài)流程有變更時溶浴,也需要改動分支邏輯乍迄,不符合開閉原則,代碼可讀性和擴展性非常差士败。

2.2.2 基于狀態(tài)模式的實現(xiàn)

了解設(shè)計模式的童鞋闯两,很容易就可以把狀態(tài)機和狀態(tài)模式這兩個概念聯(lián)系起來,狀態(tài)模式其實可以作為狀態(tài)機的一種實現(xiàn)方式谅将。主要實現(xiàn)思路是通過狀態(tài)模式將不同狀態(tài)的行為進行分離漾狼,根據(jù)狀態(tài)變量的變化,來調(diào)用不同狀態(tài)下對應的不同方法饥臂。代碼示例如下:

/**
   * 活動狀態(tài)接口
   */
interface IActivityState {
    ActivityState getName();
    //觸發(fā)事件
    void begin();
    void finishCalData();
    void beginPushData();
    void finishPushData();
}
 
 /**
   * 具體狀態(tài)類—活動未開始狀態(tài)
   */
public class ActivityNotStartState implements IActivityState {
    private ActivityStateMachine stateMachine;
    public ActivityNotStartState(ActivityStateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }
 
    @Override
    public ActivityState getName() {
        return ActivityState.NOT_START;
    }
 
    @Override
    public void begin() {
        stateMachine.setCurrentState(new ActivityDataPreparingState(stateMachine));
        //發(fā)送通知
        notice();
    }
 
    @Override
    public void finishCalData() {
        // do nothing or throw exception ...
    }
    @Override
    public void beginPushData() {
        // do nothing or throw exception ...
    }
    @Override
    public void finishPushData() {
        // do nothing or throw exception ...
    }
}
 
 /**
   * 具體狀態(tài)類—數(shù)據(jù)準備中狀態(tài)
   */
public class ActivityDataPreparingState implements IActivityState {
    private ActivityStateMachine stateMachine;
    public ActivityNotStartState(ActivityStateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }
 
    @Override
    public ActivityState getName() {
        return ActivityState.DATA_PREPARING;
    }
    @Override
    public void begin() {
        // do nothing or throw exception ...
    }
    public void finishCalData() {
        stateMachine.setCurrentState(new ActivityDataPreparedState(stateMachine));
        //TODO:發(fā)送通知
    }
   @Override
    public void beginPushData() {
        // do nothing or throw exception ...
    }
    @Override
    public void finishPushData() {
        // do nothing or throw exception ...
    }
}
    ...(篇幅原因逊躁,省略其他具體活動類)
 
 
 /**
   * 狀態(tài)機
   */
public class ActivityStateMachine {
    private IActivityState currentState;
    public ActivityStateMachine(IActivityState currentState) {
        this.currentState = new ActivityNotStartState(this);
    }
    public void setCurrentState(IActivityState currentState) {
        this.currentState = currentState;
    }
    public void begin() {
        currentState.begin();
    }
    public void finishCalData() {
        currentState.finishCalData();
    }
    public void beginPushData() {
        currentState.beginPushData();
    }
    public void finishPushData() {
        currentState.finishCalData();
    }
}

狀態(tài)模式定義了狀態(tài)-行為的對應關(guān)系, 并將各自狀態(tài)的行為封裝在對應的狀態(tài)類中。我們只需要擴展或者修改具體狀態(tài)類就可以實現(xiàn)對應流程狀態(tài)的需求隅熙。

適用場景

適用于業(yè)務(wù)狀態(tài)不多且狀態(tài)轉(zhuǎn)移簡單的場景稽煤,相比于前面的if/switch條件分支法,當業(yè)務(wù)狀態(tài)流程新增或修改時猛们,影響粒度更小念脯,范圍可控,擴展性更強弯淘。

缺陷

同樣難以應對業(yè)務(wù)流程狀態(tài)轉(zhuǎn)移復雜的場景绿店,此場景下使用狀態(tài)模式會引入非常多的狀態(tài)類和方法,當狀態(tài)邏輯有變更時,代碼也會變得難以維護假勿。

可以看到借嗽,雖然以上兩種方式都可以實現(xiàn)狀態(tài)機的觸發(fā)、轉(zhuǎn)移转培、動作流程恶导,但是復用性都很低。如果想要構(gòu)建一個可以滿足絕大部分業(yè)務(wù)場景的抽象狀態(tài)機組件浸须,是無法滿足的惨寿。

2.2.3 基于DSL的實現(xiàn)

2.2.3.1 DSL 介紹

DSL 全稱是 Domain-Specific Languages,指的是針對某一特定領(lǐng)域删窒,具有受限表達性的一種計算機程序設(shè)計語言裂垦。不同于通用的編程語言,DSL只用在某些特定的領(lǐng)域肌索,聚焦于解決該領(lǐng)域系統(tǒng)的某塊問題蕉拢。DSL通常分為 內(nèi)部 DSL ( Internal DSLs ),外部 DSL ( external DSLs ) 诚亚。

  • 內(nèi)部DSL :基于系統(tǒng)的宿主語言晕换,由宿主語言進行編寫和處理的 DSL,比如:基于 Java 的 內(nèi)部 DSL 站宗、基于 C++ 的內(nèi)部 DSL 闸准、基于 Javascript 的 內(nèi)部 DSL 。
  • 外部DSL :不同于系統(tǒng)宿主語言份乒,由自定義語言或者其他編程語言編寫并處理的 DSL恕汇,有獨立的解析器。比如:正則表達式或辖、XML瘾英、SQL、HTML 等颂暇。

(有關(guān)DSL的更多內(nèi)容可以了解:Martin Fowler《Domain Specific Languages》)缺谴。

2.2.3.2 DSL 的選型和狀態(tài)機實現(xiàn)

使用DSL作為開發(fā)工具,可以用更加清晰和更具表達性的形式來描述系統(tǒng)的行為耳鸯。DSL 也是目前實現(xiàn)狀態(tài)機比較推薦的方式湿蛔,可以根據(jù)自身的需要選用內(nèi)部 DSL 或者外部DSL 來實現(xiàn)。

  • 內(nèi)部 DSL :業(yè)務(wù)系統(tǒng)如果只希望通過代碼直接進行狀態(tài)機的配置县爬,那么可以選擇使用內(nèi)部 DSL阳啥,特點是簡單直接,不需要依賴額外的解析器和組件财喳。

Java 內(nèi)部 DSL 一般是利用 Builder Pattern 和 Fluent Interface 方式(Builder 模式和流式接口)察迟,實現(xiàn)示例:

StateMachineBuilder builder = new StateMachineBuilder();
                     builder.sourceState(States.STATE1)
                            .targetState(States.STATE2)
                            .event(Events.EVENT1)
                            .action(action1());
  • 外部 DSL :可以利用外部存儲和通用腳本語言的解析能力斩狱,實現(xiàn)運行時動態(tài)配置、支持可視化配置和跨語言應用場景扎瓶。

外部 DSL 本質(zhì)上就是將狀態(tài)轉(zhuǎn)移過程用其他外部語言進行描述所踊,比如使用 XML 的方式:

<state id= "STATE1">
  <transition event="EVENT1"  target="STATE2">
    <action method="action1()"/>
  </transition>
</state>
 
<state id= "STATE2">
</state>

外部 DSL 一般放在配置文件或者數(shù)據(jù)庫等外部存儲中,經(jīng)過對應的文本解析器概荷,就可以將外部 DSL 的配置解析成類似內(nèi)部 DSL 的模型秕岛,進行流程處理;同時由于外部存儲的獨立性和持久性误证,可以很方便地支持運行時動態(tài)變更和可視化配置继薛。

Java開源的狀態(tài)機框架基本上都是基于DSL的實現(xiàn)方式。

三雷厂、開源狀態(tài)機框架

我們分別使用三種開源狀態(tài)機框架來完成短信活動狀態(tài)流轉(zhuǎn)過程惋增。

3.1 Spring Statemachine

enum ActivityState {
    NOT_START(0),
    DATA_PREPARING(1),
    DATA_PREPARED(2),
    DATA_PUSHING(3),
    FINISHED(4);
 
    private int state;
    private ActivityState(int state) {
        this.state = state;
    }
}
 
enum ActEvent {
    ACT_BEGIN, FINISH_DATA_CAL,FINISH_DATA_PREPARE,FINISH_DATA_PUSHING
}
 
@Configuration
@EnableStateMachine
public class StatemachineConfigurer extends EnumStateMachineConfigurerAdapter<ActivityState, ActEvent> {
    @Override
    public void configure(StateMachineStateConfigurer<ActivityState, ActEvent> states)
            throws Exception {
                states
                .withStates()
                .initial(ActivityState.NOT_START)
                .states(EnumSet.allOf(ActivityState.class));
    }
    @Override
    public void configure(StateMachineTransitionConfigurer<ActivityState, ActEvent> transitions)
            throws Exception {
                transitions
                .withExternal()
                .source(ActivityState.NOT_START).target(ActivityState.DATA_PREPARING)
                .event(ActEvent.ACT_BEGIN).action(notice())
                .and()
                .withExternal()
                .source(ActivityState.DATA_PREPARING).target(ActivityState.DATA_PREPARED)
                .event(ActEvent.FINISH_DATA_CAL).action(notice())
                .and()
                .withExternal()
                .source(ActivityState.DATA_PREPARED).target(ActivityState.DATA_PUSHING)
                .event(ActEvent.FINISH_DATA_PREPARE).action(notice())
                .and()
                .withExternal()
                .source(ActivityState.DATA_PUSHING).target(ActivityState.FINISHED)
                .event(ActEvent.FINISH_DATA_PUSHING).action(notice())
                .and() ;
    }
    @Override
    public void configure(StateMachineConfigurationConfigurer<ActivityState, ActEvent> config)
            throws Exception {
        config.withConfiguration()
                .machineId("ActivityStateMachine");
    }
    public Action<ActivityState, ActEvent> notice() {
        return context -> System.out.println("【變更前狀態(tài)】:"+context.getSource().getId()+";【變更后狀態(tài)】:"+context.getTarget().getId());
    }
 
   //測試類
   class DemoApplicationTests {
    @Autowired
    private StateMachine<ActivityState, ActEvent> stateMachine;
 
    @Test
    void contextLoads() {
        stateMachine.start();
        stateMachine.sendEvent(ActEvent.ACT_BEGIN);
        stateMachine.sendEvent(ActEvent.FINISH_DATA_CAL);
        stateMachine.sendEvent(ActEvent.FINISH_DATA_PREPARE);
        stateMachine.sendEvent(ActEvent.FINISH_DATA_PUSHING);
        stateMachine.stop();
    }
}

通過重寫配置模板類的三個configure方法,通過流式Api形式完成狀態(tài)初始化改鲫、狀態(tài)轉(zhuǎn)移的流程以及狀態(tài)機的聲明,實現(xiàn)Java內(nèi)部DSL的狀態(tài)機林束。外部使用狀態(tài)機通過sendEvent事件觸發(fā)像棘,推動狀態(tài)機的自動流轉(zhuǎn)。

優(yōu)勢

  • Spring Statemachine 是 Spring 官方的產(chǎn)品壶冒,具有強大生態(tài)社區(qū)缕题。
  • 功能十分完備,除了支持基本的狀態(tài)機配置外胖腾,還具備可嵌套的子狀態(tài)機烟零、基于zk的分布式狀態(tài)機和外部存儲持久化等豐富的功能特性。

缺陷

  • Spring Statemachine 在每個 statemachine 實例內(nèi)部保存了當前狀態(tài)機上下文相關(guān)的屬性咸作,也就是說是有狀態(tài)的(這一點從觸發(fā)狀態(tài)機流轉(zhuǎn)只需事件作為參數(shù)也可以看出來)锨阿,所以使用單例模式的狀態(tài)機實例不是線程安全的。要保證線程安全性只能每次通過工廠模式創(chuàng)建一個新的狀態(tài)機實例记罚,這種方式在高并發(fā)場景下墅诡,會影響系統(tǒng)整體性能。
  • 代碼層次結(jié)構(gòu)稍顯復雜桐智,二次開發(fā)改造成本大末早,一般場景下也并不需要使用如此多的功能,使用時觀感上顯得比較沉重说庭。

3.2 Squirrel Foundation

public class SmsStatemachineSample {
    // 1. 狀態(tài)定義
     enum ActivityState {
        NOT_START(0),
        DATA_PREPARING(1),
        DATA_PREPARED(2),
        DATA_PUSHING(3),
        FINISHED(4);
 
        private int state;
        private ActivityState(int state) {
            this.state = state;
        }
    }
 
    // 2. 事件定義
    enum ActEvent {
        ACT_BEGIN, FINISH_DATA_CAL,FINISH_DATA_PREPARE,FINISH_DATA_PUSHING
    }
 
    // 3. 狀態(tài)機上下文
    class StatemachineContext {
    }
 
    @StateMachineParameters(stateType=ActivityState.class, eventType=ActEvent.class, contextType=StatemachineContext.class)
    static class SmsStatemachine extends AbstractUntypedStateMachine {
        protected void notice(ActivityState from, ActivityState to, ActEvent event, StatemachineContext context) {
            System.out.println("【變更前狀態(tài)】:"+from+";【變更后狀態(tài)】:"+to);
        }
    }
 
    public static void main(String[] args) {
        // 4. 構(gòu)建狀態(tài)轉(zhuǎn)移
        UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(SmsStatemachine.class);
        builder.externalTransition().from(ActivityState.NOT_START).to(ActivityState.DATA_PREPARING).on(ActEvent.ACT_BEGIN).callMethod("notice");
        builder.externalTransition().from(ActivityState.DATA_PREPARING).to(ActivityState.DATA_PREPARED).on(ActEvent.FINISH_DATA_CAL).callMethod("notice");
        builder.externalTransition().from(ActivityState.DATA_PREPARED).to(ActivityState.DATA_PUSHING).on(ActEvent.FINISH_DATA_PREPARE).callMethod("notice");
        builder.externalTransition().from(ActivityState.DATA_PUSHING).to(ActivityState.FINISHED).on(ActEvent.FINISH_DATA_PUSHING).callMethod("notice");
 
        // 5. 觸發(fā)狀態(tài)機流轉(zhuǎn)
        UntypedStateMachine fsm = builder.newStateMachine(ActivityState.NOT_START);
        fsm.fire(ActEvent.ACT_BEGIN,  null);
        fsm.fire(ActEvent.FINISH_DATA_CAL, null);
        fsm.fire(ActEvent.FINISH_DATA_PREPARE, null);
        fsm.fire(ActEvent.FINISH_DATA_PUSHING, null);
     }
}

squirrel-foundation 是一款輕量級的狀態(tài)機庫然磷,設(shè)計目標是為企業(yè)使用提供輕量級、高度靈活刊驴、可擴展姿搜、易于使用、類型安全和可編程的狀態(tài)機實現(xiàn)。

優(yōu)勢

  • 和目標理念一致痪欲,與 Spring Statemachine 相比悦穿,不依賴于spring框架,設(shè)計實現(xiàn)方面更加輕量业踢,雖然也是有狀態(tài)的設(shè)計栗柒,但是創(chuàng)建狀態(tài)機實例開銷較小,功能上也更加簡潔知举,相對比較適合二次開發(fā)瞬沦。
  • 對應的文檔和測試用例也比較豐富,開發(fā)者上手容易雇锡。

缺陷

  • 過于強調(diào)“約定優(yōu)于配置”的理念逛钻,不少默認性的處理,比如狀態(tài)轉(zhuǎn)移后動作是通過方法名來調(diào)用锰提,不利于操作管理曙痘。
  • 社區(qū)活躍度不高。

3.3 Cola Statemachine

/**
 * 狀態(tài)機工廠類
 */
public class StatusMachineEngine {
    private StatusMachineEngine() {
    }
    private static final Map<OrderTypeEnum, String> STATUS_MACHINE_MAP = new HashMap();
 
    static {
        //短信推送狀態(tài)
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.SMS, "smsStateMachine");
        //PUSH推送狀態(tài)
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.PUSH, "pushStateMachine");
        //......
    }
 
    public static String getMachineEngine(ChannelTypeEnum channelTypeEnum) {
        return STATUS_MACHINE_MAP.get(channelTypeEnum);
    }
 
   /**
     * 觸發(fā)狀態(tài)轉(zhuǎn)移
     * @param channelTypeEnum
     * @param status 當前狀態(tài)
     * @param eventType 觸發(fā)事件
     * @param context 上下文參數(shù)
     */
    public static void fire(ChannelTypeEnum channelTypeEnum, String status, EventType eventType, Context context) {
        StateMachine orderStateMachine = StateMachineFactory.get(STATUS_MACHINE_MAP.get(channelTypeEnum));
        //推動狀態(tài)機進行流轉(zhuǎn)立肘,具體介紹本期先省略
        orderStateMachine.fireEvent(status, eventType, context);
    }
 
/**
 * 短信推送活動狀態(tài)機初始化
 */
@Component
public class SmsStateMachine implements ApplicationListener<ContextRefreshedEvent> {
 
    @Autowired
    private  StatusAction smsStatusAction;
    @Autowired
    private  StatusCondition smsStatusCondition;
 
    //基于DSL構(gòu)建狀態(tài)配置边坤,觸發(fā)事件轉(zhuǎn)移和后續(xù)的動作
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        StateMachineBuilder<String, EventType, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()
                .from(INIT)
                .to(NOT_START)
                .on(EventType.TIME_BEGIN)
                .when(smsStatusAction.checkNotifyCondition())
                .perform(smsStatusAction.doNotifyAction());
        builder.externalTransition()
                .from(NOT_START)
                .to(DATA_PREPARING)
                .on(EventType.CAL_DATA)
                .when(smsStatusCondition.doNotifyAction())
                .perform(smsStatusAction.doNotifyAction());
        builder.externalTransition()
                .from(DATA_PREPARING)
                .to(DATA_PREPARED)
                .on(EventType.PREPARED_DATA)
                .when(smsStatusCondition.doNotifyAction())
                .perform(smsStatusAction.doNotifyAction());
        ...(省略其他狀態(tài))
        builder.build(StatusMachineEngine.getMachineEngine(ChannelTypeEnum.SMS));
    }
 
   //調(diào)用端
   public class Client {
     public static void main(String[] args){
          //構(gòu)建活動上下文
          Context context = new Context(...);
         // 觸發(fā)狀態(tài)流轉(zhuǎn)
          StatusMachineEngine.fire(ChannelTypeEnum.SMS, INIT, EventType.SUBMIT, context);
      }
   }
}

Cola Statemachine 是阿里COLA開源框架里面的一款狀態(tài)機框架,和前面兩者最大的不同就是:無狀態(tài)的設(shè)計——觸發(fā)狀態(tài)機流轉(zhuǎn)時需要把當前狀態(tài)作為入?yún)⒘履辏瑺顟B(tài)機實例中不需要保留當前狀態(tài)上下文消息茧痒,只有一個狀態(tài)機實例,也就直接保證了線程安全性和高性能融蹂。

優(yōu)勢

  • 輕量級無狀態(tài)旺订,安全,性能高超燃。

  • 設(shè)計簡潔区拳,方便擴展。

  • 社區(qū)活躍度較高淋纲。

缺陷

  • 不支持嵌套劳闹、并行等高級功能。

3.4 小結(jié)

三種開源狀態(tài)機框架對比如下:

[圖片上傳失敗...(image-a658c9-1649642767478)]

希望直接利用開源狀態(tài)機能力的系統(tǒng)洽瞬,可以根據(jù)自身業(yè)務(wù)的需求和流程復雜度本涕,進行合適的選型。

四伙窃、營銷自動化業(yè)務(wù)案例實踐

4.1 設(shè)計選型

vivo營銷自動化的業(yè)務(wù)特點是:

  • 運營活動類型多菩颖,業(yè)務(wù)流量大,流程相對簡單为障,性能要求高晦闰。
  • 流程變更頻繁放祟,經(jīng)常需要新增業(yè)務(wù)狀態(tài),需要支持快速新增配置和變更呻右。
  • 在狀態(tài)觸發(fā)后會有多種不同的業(yè)務(wù)操作跪妥,比如狀態(tài)變更后的消息提醒,狀態(tài)完結(jié)后的業(yè)務(wù)處理等声滥,需要支持異步操作和方便擴展眉撵。

針對以上業(yè)務(wù)特點,在實際項目開發(fā)中落塑,我們是基于開源狀態(tài)的實現(xiàn)方案——基于內(nèi)部DSL的方式進行開發(fā)纽疟。同時汲取了以上開源框架的特點,選用了無狀態(tài)高性能憾赁、功能簡潔污朽、支持動作異步執(zhí)行的輕量設(shè)計。

[圖片上傳失敗...(image-d9ba39-1649642767478)]

  • 無狀態(tài)高性能:保證高性能龙考,采用無狀態(tài)的狀態(tài)機設(shè)計蟆肆,只需要一個狀態(tài)機實例就可以進行運轉(zhuǎn)。
  • 功能簡潔:最小設(shè)計原則洲愤,只保留核心的設(shè)計颓芭,比如事件觸發(fā),狀態(tài)的基本流轉(zhuǎn)柬赐,后續(xù)的操作和上下文參數(shù)處理。
  • 動作異步執(zhí)行:針對異步業(yè)務(wù)流程官紫,采用線程池或者消息隊列的方式進行異步解耦肛宋。

4.2 核心流程

  • 沿用開源狀態(tài)機的內(nèi)部DSL流式接口設(shè)計,在應用啟動時掃描狀態(tài)機定義束世;
  • 創(chuàng)建異步處理線程池支持業(yè)務(wù)的后置動作酝陈;
  • 解析狀態(tài)機的DSL配置,初始化狀態(tài)機實例毁涉;
  • 構(gòu)建執(zhí)行上下文沉帮,存放各個狀態(tài)機的實例和其他執(zhí)行過程信息;
  • 狀態(tài)機觸發(fā)時贫堰,根據(jù)觸發(fā)條件和當前狀態(tài)穆壕,自動匹配轉(zhuǎn)移過程,推動狀態(tài)機流轉(zhuǎn)其屏;
  • 執(zhí)行后置同步/異步處理操作喇勋。

[圖片上傳失敗...(image-c2d2a-1649642767478)]

(圖4-1:核心流程設(shè)計)

4.3 實踐思考

1)狀態(tài)機配置可視化,結(jié)合外部DSL的方式(比如JSON的方式偎行,存儲到數(shù)據(jù)庫中)川背,支持更快速的配置贰拿。

2)目前只支持狀態(tài)的簡單流轉(zhuǎn),在流轉(zhuǎn)過程加入流轉(zhuǎn)接口擴展點熄云,應對未來可能出現(xiàn)的復雜場景膨更。

五、總結(jié)

狀態(tài)機是由事件缴允、狀態(tài)荚守、動作三大部分組成。三者的關(guān)系是:事件觸發(fā)狀態(tài)的轉(zhuǎn)移癌椿,狀態(tài)的轉(zhuǎn)移觸發(fā)后續(xù)動作的執(zhí)行健蕊。利用狀態(tài)機進行系統(tǒng)狀態(tài)管理,可以提升業(yè)務(wù)擴展性和內(nèi)聚性踢俄。狀態(tài)機可以使用條件分支判斷缩功、狀態(tài)模式和基于DSL來實現(xiàn),其中更具表達性的DSL也是很多開源狀態(tài)機的實現(xiàn)方式都办〉招浚可以基于開源狀態(tài)機的特點和自身項目需求進行合適的選型,也可以基于前面的方案自定義狀態(tài)機組件琳钉。

本篇是《營銷自動化技術(shù)解密》系列專題文章的第三篇势木,系列文章回顧:

《營銷自動化技術(shù)解密|開篇》

《設(shè)計模式如何提升 vivo 營銷自動化業(yè)務(wù)拓展性|引擎篇01》

后面我們將繼續(xù)帶來系列專題文章的其他內(nèi)容,每一篇文章都會對里面的技術(shù)實踐進行詳盡解析歌懒,敬請期待啦桌。

作者:vivo互聯(lián)網(wǎng)服務(wù)器團隊-Chen Wangrong

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市及皂,隨后出現(xiàn)的幾起案子甫男,更是在濱河造成了極大的恐慌,老刑警劉巖验烧,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件板驳,死亡現(xiàn)場離奇詭異,居然都是意外死亡碍拆,警方通過查閱死者的電腦和手機若治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來感混,“玉大人端幼,你說我怎么就攤上這事『葡埃” “怎么了静暂?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谱秽。 經(jīng)常有香客問我洽蛀,道長摹迷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任郊供,我火速辦了婚禮峡碉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘驮审。我一直安慰自己鲫寄,他們只是感情好,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布疯淫。 她就那樣靜靜地躺著故爵,像睡著了一般畜埋。 火紅的嫁衣襯著肌膚如雪万栅。 梳的紋絲不亂的頭發(fā)上溃卡,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機與錄音币绩,去河邊找鬼蜡秽。 笑死,一個胖子當著我的面吹牛缆镣,可吹牛的內(nèi)容都是我干的芽突。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼董瞻,長吁一口氣:“原來是場噩夢啊……” “哼寞蚌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钠糊,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤睬澡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后眠蚂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡斗躏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年逝慧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啄糙。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡笛臣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隧饼,到底是詐尸還是另有隱情沈堡,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布燕雁,位于F島的核電站诞丽,受9級特大地震影響鲸拥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜僧免,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一刑赶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧懂衩,春花似錦撞叨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至法希,卻和暖如春枷餐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铁材。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工尖淘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人著觉。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓村生,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饼丘。 傳聞我的和親對象是個殘疾皇子趁桃,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容