运单状态机设计及全流程异常跟踪整体架构

1. 业务背景

1. 需求

业务方希望能够对运单的揽收、干支运输、派送等各个环节的操作规范进行监控,同时将温湿监控、设备监控、配置监控等孤立的报警项映射到运单环节之上。

运单环节由运单状态映射而得,譬如干支运输环节的典型状态区间是封车–>解封车。

1.2 难点

运单环节映射最大的难点在于,无论是在系统上还是运营上,运单的各个关键状态都不是严格齐全的、有序的,存在大量的漏操作、补操作,很难甚至无法进行完全正确的环节映射——一个环节可能永远不会有对应的终止状态产生。此时,系统上要尽快识别这种异常,做到今早结束。

此外,几百种运单状态前期难以梳理清楚,业务也在不断的变化,这些都使得环节的映射关系随时可能变更。

以上种种要求系统在设计上要能够适应复杂、多变的条件判断,传统编码方式会存在大量的if-else判断,场景考虑不全、后期维护困难。

方案设计毫无眉目之时,翻看设计模式之美这本书,想从中找找灵感,万万没想到居然淘到了宝——状态机。

2. 状态机简介

详见:有限状态机

2.1 定义

有限状态机(Finite State Machine,FSM)简称状态机。状态机有三个组成部分:状态(State)、事件(Event)、动作(Action),事件(转移条件)触发状态的转移和动作的执行。动作的执行不是必须的,可以只转移状态,不指定任何动作。总体而言,状态机是一种用以表示有限个状态以及这些状态之间的转移和动作的执行等行为的数学模型。

状态机可以用公式 State(S) x Event(E) -> Actions (A), State(S’)表示,即在处于状态S的情况下,接收到了事件E,使得状态转移到了S’,同时伴随着动作A的执行。

2.2 举例

在游戏“超级马里奥”中,马里奥的形态转变就是一个状态机。马里奥有小马里奥、超级马里奥、火焰马里奥、斗篷马里奥等形态,在遇到不同的游戏情节时,会发生形态改变,同时产生积分的增减。比如小马里奥吃了蘑菇之后会变成超级马里奥,同时增加100积分。

在超级马里奥中,马里奥的不同形态就是状态机中的“状态”,游戏情节就是状态机中的“事件”,加减积分就是状态——机中的“动作”。

在这里插入图片描述

其中,事件E1~E4分别表示吃蘑菇、获得斗篷、获得火焰、遇到怪物。

3. 运单环节状态机实现

状态

梳理出需要监控的运单环节:揽收环节、干支运输环节、配送环节,由此派生出8个状态:初始状态、揽收开始/结束状态、干支开始/结束状态、派送开始/结束状态、结束状态。

事件、动作及状态转移

将运单状态作为事件输入到状态机中,由此触发运单环节的流转,并执行相应的动作——创建新的环节、结束当前环节、结束当前并创建下一个环节。环节的初始化和结束计算任务比较重量,采用了异步任务进行计算。

在这里插入图片描述

环节状态机的整体流转从揽收到干支、派送,最后到结束状态,处于结束状态的状态机不会在响应任何输入事件。如图所示,在揽收开始的状态下,接收到卸车事件,那么状态机的状态将会转变到揽收结束状态,同时执行结束环节计算任务。此外,由于干支运输和派送环节在实际中可能存在多个,所以干支开始/结束、派送开始/结束这一对状态可以互相转换。

具体实现——查表法

在代码实现上,状态机常见的三种实现方式——分支逻辑法、查表法、状态模式法,查表法比较适应当前场景:状态、事件类型很多、状态转移比较复杂,利用二维数组表示状态转移图,能极大的提高代码的可读性和可维护性。当把数组存在配置文件中,在状态机变更时甚至不需要修改代码。

此外,由于只有在状态机状态发生改变时,才需要执行动作。对ACTION_TABLE进行了优化,纵坐标由事件变化为下一个状态,即由当前状态和转移后的状态决定执行的事件类型。


    /**
     *  状态转移矩阵,横坐标表示当前状态,纵坐标表示接收到的运单状态变化事件,值为新状态。
     */
    private static final WaybillMajorStateEnum[][] TRANSITION_TABLE = {
            // S_626         S_640         S10           S_170         S_450         S_460         S_470         S_520         S16           S110          S130          S150           S133          S160          S580          S_790         S_860         S_1100        S530          S540          S630          S635          S_330         S_340         S_1890
            {  INIT_STAT,    LS_START ,    LS_FINISH,    LS_FINISH,    GZ_START ,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    PS_START ,    PS_FINISH,    END_STATE,     PS_FINISH,    END_STATE,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH,    END_STATE,    END_STATE,    GZ_START ,    GZ_FINISH,    LS_FINISH},
            {  LS_START ,    LS_START ,    LS_FINISH,    LS_FINISH,    GZ_START ,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    PS_START ,    PS_FINISH,    END_STATE,     PS_FINISH,    END_STATE,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH,    END_STATE,    END_STATE,    GZ_START ,    GZ_FINISH,    LS_FINISH},
            {  LS_FINISH,    LS_FINISH,    LS_FINISH,    LS_FINISH,    GZ_START ,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    PS_START ,    PS_FINISH,    END_STATE,     PS_FINISH,    END_STATE,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH,    END_STATE,    END_STATE,    GZ_START ,    GZ_FINISH,    LS_FINISH},
            {  GZ_START ,    GZ_START ,    GZ_START ,    GZ_START ,    GZ_START ,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    PS_START ,    PS_FINISH,    END_STATE,     PS_FINISH,    END_STATE,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH,    END_STATE,    END_STATE,    GZ_START ,    GZ_FINISH,    GZ_START },
            {  GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    GZ_START ,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    GZ_FINISH,    PS_START ,    PS_FINISH,    END_STATE,     PS_FINISH,    END_STATE,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH,    END_STATE,    END_STATE,    GZ_START ,    GZ_FINISH,    GZ_FINISH},
            {  PS_START ,    PS_START ,    PS_START ,    PS_START ,    PS_START ,    PS_START ,    PS_START ,    PS_START ,    PS_START,     PS_START ,    PS_FINISH,    END_STATE,     PS_FINISH,    END_STATE,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH,    END_STATE,    END_STATE,    PS_START ,    PS_START ,    PS_START },
            {  PS_FINISH,    PS_FINISH,    PS_FINISH,    PS_FINISH,    PS_FINISH,    PS_FINISH,    PS_FINISH,    PS_FINISH,    PS_FINISH,    PS_START ,    PS_FINISH,    END_STATE,     PS_FINISH,    END_STATE,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH,    END_STATE,    END_STATE,    PS_FINISH,    PS_FINISH,    PS_FINISH},
            {  END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,     END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE,    END_STATE},
    };


    /**
     * 只有在状态发生转移时,才有操作需要执行,故横坐标表示原状态,纵坐标表示转移后的状态。
     */
    private static final int[][] ACTION_TABLE = {
            //  INIT_STATE       LS_START         LS_END           GZ_START          GZ_END           PS_START         PS_END           END_STATE
            {   DO_NOTHING  ,    START_NODE  ,    DO_NOTHING  ,    START_NODE  ,     DO_NOTHING  ,    START_NODE  ,    DO_NOTHING  ,    DO_NOTHING  }, // INIT_STATE
            {   DO_NOTHING  ,    DO_NOTHING  ,    FINISH_NODE ,    FINISH_START,     FINISH_NODE ,    FINISH_START,    FINISH_NODE ,    FINISH_NODE }, // LS_START
            {   DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,    START_NODE  ,     DO_NOTHING  ,    START_NODE  ,    DO_NOTHING  ,    DO_NOTHING  }, // LS_END
            {   DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,     FINISH_NODE ,    FINISH_START,    FINISH_NODE ,    FINISH_NODE }, // GZ_START
            {   DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,    START_NODE  ,     DO_NOTHING  ,    START_NODE  ,    DO_NOTHING  ,    DO_NOTHING  }, // GZ_END
            {   DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,     DO_NOTHING  ,    DO_NOTHING  ,    FINISH_NODE ,    FINISH_NODE }, // PS_START
            {   DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,     DO_NOTHING  ,    START_NODE  ,    DO_NOTHING  ,    DO_NOTHING  }, // PS_END
            {   DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,     DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  ,    DO_NOTHING  }, // END_STATE
    };

4. 整体架构

整体架构上主要分为两层:单据状态层和任务计算层。

单据状态层从MQ接收运单状态变化消息,以批次为单位进行处理状态变化并生成环节:

  1. 从Redis批量获取缓存并初始化状态机
  2. 输入运单变化事件产生状态转移、封装待执行动作为异步任务
  3. 数据批量落库ES、状态机状态批量更新回写Redis
  4. 发送异步批量任务

任务计算层执行上层下发的环节初始化和终止任务,计算相关的指标。

在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值