自己动手造一个状态机

自己动手造一个状态机

  • 引言
  • 有限自动状态机 (FSM)
    • 五要素
    • 应用场景
    • 优势
  • 开源产品
  • 造个轮子
    • 改造点
    • Looplab fsm
      • 示例演示
      • 实现解析
    • 改造过程


引言

有限自动状态机 (Finite-state machine , FSM) 通常用来描述某个具有有限个状态的对象,并且在对象的生命周期中组成了一个状态序列,通过响应外界各种事件完成状态流转。

FSM 被广泛应用于 建模应用行为,硬件电路系统设计,软件工程,编译器,网络协议和计算机语言的研究。


有限自动状态机 (FSM)

五要素

  • 现态 (src state) : 事物当前所处的状态
  • 事件 (event) : 事件就是执行某个操作后触发的条件或者口令,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。
  • 行为 (action) : 事件满足后执行的动作,动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态 (dst state) : 事件满足后要迁往的新状态,‘次态’是相对于‘现态’而言的,‘次态’一旦被激活,就转变成新的‘现态’了。
  • 状态流转 (transition) : 事物从现态变为次态的整个过程。

应用场景

FSM 应用场景满足的规则:

  • 可以用状态来描述事物,并且任一时刻,事物总是处于一种状态
  • 事物拥有的状态总数是有限的
  • 通过触发事物的某些行为,可以导致事物从一种状态迁移到另一种状态
  • 事物状态变化是有规则的。A状态 -> B状态,B状态 -> C状态 ,C状态 -> A状态。
  • 同一种行为,可以将事物从多种状态变为同种状态,但是不同从同种状态变成多种状态。

落地的应用场景:

  • 网络通信协议
  • 订单,服务单,退款场景

优势

  • 代码抽象: 将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元,这样相当于搭建了乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。
  • 简化流程: 业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。
  • 易扩展: 在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。
  • 业务建模: 通过最小粒度的相邻状态拼接,最终组成了业务整体的graph。

开源产品

  • cola-component-statemachine (java)

    • 优点
      • 支持condition (dsl需要再扩展)
      • 事件类型: 内部事件,外部事件
      • interceptor: 进入,退出状态机;进入,退出状态;
    • 缺点
      • 无分布式状态控制
      • 无时间触发
  • squirrel-foundation (java)

    • 优点
      • 支持动作的exit,transition,entry
      • 状态转换过程细分,可以做功能扩展和状态跟踪
      • 没有并发死锁问题
      • 轻量级
    • 缺点
      • 注解方式定义状态和事件,不支持状态和事件枚举
      • interceptor粒度粗
  • Spring statemachine (java)

    • 优点
      • Interceptor ,listener 方便监控,持久化,功能扩展
      • 对象化的状态机配置
      • 分层状态机,解决复杂场景的多状态问题
      • 使用triggers,transitions,guards,actions概念
      • 基于zk的分布式事件监听
      • 状态机配置持久化
      • 时间触发和事件触发
      • 事件类型: 内部事件,外部事件 (内部,外部是相对于状态来说的)
      • 支持spel表达式
    • 缺点
      • 重量级
      • 配合spring使用更方便
      • 单实例的StateMachine存在线程安全问题
  • Looplab fsm (go)

    • 优点
      • 支持Callback,BeforeEvent,LeaveEvent,EnterSatte,AfterEvent
      • 异常感知
      • 对状态的Mutex锁
    • 缺点
      • 无异步类型的event
      • 无分布式状态控制
      • 无condition

造个轮子

改造点

我们本节将基于Looplab fsm (go) 进行改造,改造点主要有以下几个:

  1. 同一个event下,一个现态 , 可流转到不同的次态

传统概念的状态机中,一个src和一个event的组合,只能确定一个且仅有一个的dst,但是经过改造后,一个src和一个event的组合,可能会关联多个dst,这样做并不是改变了状态机的模型,而是通过将相似的event合并,配合条件表达式,也就是组成src,event , 和条件表达式的三元组,唯一的确定可流转的dst。这样做的好处有两点:

  • 简化状态流转的配置
  • 可以将event设计的更贴合业务语义

以下单场景为例:
在这里插入图片描述
订单处于 “下单” 状态,当接收到 “创建订单” 事件时。根据订单类型的不同可以分为0元单和非0元单,传统的FSM会将两种类型的订单创建定义为两个不同的event : “创建0元订单” 和 “创建非0元订单” ,但是在bfsm中,可以只定义一个 “创建订单” 的 event ,配合条件表达式判断订单类型,将状态流转到不同的dst 。这样可以简化配置,同时也不需要将 “创建订单” 这个event做更细粒度的拆解。


  1. 匹配表达式

根据src 和 event ,能够匹配到一组 dst ,通过匹配表达式执行复杂匹配逻辑,每个匹配条件被满足后对应一个dst,在状态流转的过程中,会按照表达式的注册顺序依次进行匹配,最终会匹配执行结果为true的表达式所对应的dst ;如果所有匹配表达式执行结果都为false,那么状态不会发生流转。


  1. 可合并多场景的状态转移配置

可以将多个场景的状态转移配置合并,不合并也可以正常使用。


  1. 加锁状态流转

为应对高并发场景,支持基于redis分布式锁的状态转移,对状态转移,通过锁定状态转移的实体对象(通常为订单id,服务单id等),锁定事件fire过程,保证高并发场景下,同一实体对象的状态流程串行执行。另外,支持用户自定义锁的实现。


  1. 多对多状态配置

简化配置,提供多状态到多状态的流转配置。


  1. 状态配置的图化

基于状态流转配置,在线展示状态转移图。


Looplab fsm

示例演示

Looplab fsm 一个简单的使用示例如下所示:

func main() {var afterFinishCalled boolfsm := fsm.NewFSM(// 初态 "start",// 状态流转图fsm.Events{// 事件名 / 现态 / 次态// 现态 + 事件 = 次态{Name: "run", Src: []string{"start"}, Dst: "end"},{Name: "finish", Src: []string{"end"}, Dst: "finished"},{Name: "reset", Src: []string{"end", "finished"}, Dst: "start"},},// 回调接口集合fsm.Callbacks{// 在进入end状态前,会回调该接口"enter_end": func(ctx context.Context, e *fsm.Event) {if err := e.FSM.Event(ctx, "finish"); err != nil {fmt.Println(err)}},// 再离开finish状态时,会回调该接口"after_finish": func(ctx context.Context, e *fsm.Event) {afterFinishCalled = trueif e.Src != "end" {panic(fmt.Sprintf("source should have been 'end' but was '%s'", e.Src))}if err := e.FSM.Event(ctx, "reset"); err != nil {fmt.Println(err)}},},)// 触发run事件if err := fsm.Event(context.Background(), "run"); err != nil {panic(fmt.Sprintf("Error encountered when triggering the run event: %v", err))}if !afterFinishCalled {panic(fmt.Sprintf("After finish callback should have run, current state: '%s'", fsm.Current()))}// 查看当前状态  currentState := fsm.Current()if currentState != "start" {panic(fmt.Sprintf("expected state to be 'start', was '%s'", currentState))}fmt.Println("Successfully ran state machine.")
}

实现解析

Looplab fsm 只支持 event ,state 二元组状态流转方式,所以整理实现流程比较简单,如下图所示:

在这里插入图片描述
在这里插入图片描述

Looplab fsm 核心代码都位于 fsm.go 文件中,具体实现大家可以去阅读该源文件进行学习。


改造过程

改造的具体代码实现此处就不贴出来了,只给出流程图级别的改造说明:

在这里插入图片描述
加锁:

  • 上图省去了加锁保护细节,此处的加锁需要替换为redis分布式锁了,当然加锁这块还是可以好好优化一下的,不然高并发场景下,锁的争抢会成为瓶颈。

异常处理:

  • 状态机内部的错误会通过error的形式抛给业务方
  • 业务方的calllback函数执行异常时,需要业务方通过cancel方法主动通知状态机结束此次状态流转,但是不能再状态变更后的AfterTransCallback中调用cancel回调,因为此时状态已经发生了变更。

表达式:

  • 基于govaluate实现

多场景状态转移配置合并:

  • 可以通过场景隔离,同时抽取状态转移配置全局化,实现多场景状态转移配置合并
    在这里插入图片描述

每种场景下的配置伪代码如下:

FSMConf := map[string]FsmDesc{"场景名1" : {// 当前场景下的全局回调接口BeforeTransCallbackAfterTransCallback// 支持多状态到多状态流转TransDesc: []TransDesc{{// 事件名EventName// 现态集合Src []string{}// 属于本次状态流转过程中的局部回调接口BeforeTransCallbackAfterTransCallback// 表达式集合Matchers []Matcher{// 表达式,次态,回调接口{Condition,Dst,BeforeMatchCallback,AfterMatchCallback}}}} },"场景2" : {}
}

FSM 初始化过程也分为了两步:

  • 初始化全局配置
fsm.Init(FSMConf)
  • 创建状态机实例
fsm.NewFSM("场景名","初态")

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/639758.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flink基本概念

1. Flink关键组件: 这里首先要说明一下“客户端”。其实客户端并不是处理系统的一部分,它只负责作业的提交。具体来说,就是调用程序的 main 方法,将代码转换成“数据流图”(Dataflow Graph),并最终生成作业…

堆详解与优先级队列

导言: 我们知道队列是一种先进先出(FIFO)的数据结构,但是现实情况中,操作的数据有可能会有优先级,优先级高的数据要先出队。例如,医院的军人优先等等。而为此应运而生的就是优先级队列,java中可以使用Prio…

力扣hot100 相交链表 超全注释 满级表达

Problem: 160. 相交链表 文章目录 思路复杂度💖 Ac Code 思路 👨‍🏫 参考题解 👩‍🏫 参考图解 复杂度 时间复杂度: O ( n m ) O(nm) O(nm) 空间复杂度: 添加空间复杂度, 示例: O ( 1 ) O(1) O(…

详谈c++智能指针!!!

文章目录 前言一、智能指针的发展历史1.C 98/03 的尝试——std::auto_ptr2.std::unique_ptr3.std::shared_ptr4.std::weak_ptr5.智能指针的大小6.智能指针使用注意事项 二、智能指针的模拟实现三、C11和boost中智能指针的关系 前言 C/C 语言最为人所诟病的特性之一就是存在内存…

Docker是什么

docker本质 Docker 本质其实是 LXC 之类的增强版,它本身不是容器,而是容器的易用工具。容器是 linux 内核中的技术,Docker 只是把这种技术在使用上简易普及了。Docker 在早期的版本其核心就是 LXC 的二次封装发行版。 Docker 作为容器技术的…

开发第一个Flutter App需要注意什么

Flutter这些年发展的很快,特别是在 Google 持续的加持下,Flutter SDK 的版本号已经来到了 3开头,也正式开始对 Windows、macOS 和 Linux 桌面环境提供支持。如果从 Flutter 特有的优势来看,我个人认为主要是它已经几乎和原生的性能…

换手机后:旧手机备忘录怎么导入新手机里?

现在新手机层出不穷,大家都爱换手机来体验新功能,但在换手机的时候,数据传输是非常麻烦的一件事情。 每次换手机,就像是搬一次家。老房子里的点点滴滴,那些重要的、不重要的,都得一一打包,再在…

DSP Bootloader

DSP Bootloader Refer: DSP Bootloader开发思路讲解

字符串展开(Python)

展开字符串中用-压缩的连续小写字母或者数字,不是压缩形式的-不用理会,-没有压缩字符的去除-。 (笔记模板由python脚本于2024年01月21日 18:18:19创建,本篇笔记适合熟悉 p y t h o n python python字符串和列表的coder翻阅) 【学习的细节是欢…

SAP屏幕开发之Listbox下拉列表

文章目录 前言一、案例介绍二、静态下拉列表 a.绘制并设置属性 b.两种属性区别以及效果展示 三、动态下拉列表 a.绘制下拉列表 b.调用函数绑定 四、总结 前言 这篇文章给大家介绍一下SAP Dialog程序中 Listbox控件 的使用&#xf…

如何搭建MariaDB并实现无公网ip环境远程连接本地数据库

🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​💫个人格言:“没有罗马,那就自己创造罗马~” 文章目录 1. 配置MariaDB数据库1.1 安装MariaDB数据库1.2 测试局域网内远程连接 2. 内网穿透2.1 创建隧道映射…

多级缓存

一、多级缓存 传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: •请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时&#xff…

【Ubuntu】Ubuntu安装微信

1. 优麒麟 Wine (“Wine Is Not an Emulator(Wine不是一个模拟器)” 的缩写)是一个能够在多种 POSIX-compliant 操作系统(诸如 Linux,Mac OSX 及 BSD 等)上运行 Windows 应用的兼容层。银河麒麟的操作系统也是基于Ubu…

Android状态栏布局隐藏的方法

1.问题如下,安卓布局很不协调 2.先将ActionBar设置为NoActionBar 先打开styles.xml 3.使用工具类 /*** StatusBar 工具类*/ public class StatusBarUtil {/*** 设置状态栏全透明** param activity 需要设置的activity*/public static void setTransparent(Activit…

【大数据】流处理基础概念(一):Dataflow 编程基础、并行流处理

流处理基础概念(一):Dataflow 编程基础、并行流处理 1.Dataflow 编程基础1.1 Dataflow 图1.2 数据并行和任务并行1.3 数据交换策略 2.并行流处理2.1 延迟与吞吐2.1.1 延迟2.1.2 吞吐2.1.3 延迟与吞吐 2.2 数据流上的操作2.2.1 数据接入和数据…

【江科大】STM32:(超级详细)定时器输出比较

文章目录 输出比较单元特点 高级定时器:均有4个通道 PWM简介PWM(Pulse Width Modulation)脉冲宽度调制输出比较通道PWM基本结构基本定时器 参数计算捕获/比较通道的输出部分详细介绍如下: 舵机介绍硬件电路 直流电机介绍&#xff…

LLM自回归解码

在自然语言处理(NLP)中,大型语言模型(LLM)如Transformer进行推理时,自回归解码是一种生成文本的方式。在自回归解码中,模型在生成下一个单词时会依赖于它之前生成的单词。 使用自回归解码的公式…

SPE-Single Pair Ethernet单对以太网测试那些事儿

SPE-Single Pair Ethernet单对以太网测试哪些事?SPE标准IEEE802.3再网上溯源的话是从ISO/IEC11801-X series演变而来。 IEEE802.3cg 10Base-T1 10mbt/s 15m-1000m 0.1mHz-20mHz IEEE802.3bw 100Base-T1 100mbt/s 15m 0.3mHz-66mHz IEEE802.3bp 1000…

k8s-认证授权 14

Kubernetes的认证授权分为认证(鉴定用户身份)、授权(操作权限许可鉴别)、准入控制(资源对象操作时实现更精细的许可检查)三个阶段。 Authentication(认证) 认证方式现共有8种&…

Pandas.Series.describe() 统计学描述 详解 含代码 含测试数据集 随Pandas版本持续更新

关于Pandas版本: 本文基于 pandas2.1.2 编写。 关于本文内容更新: 随着pandas的stable版本更迭,本文持续更新,不断完善补充。 传送门: Pandas API参考目录 传送门: Pandas 版本更新及新特性 传送门&…