“驯服”业务流程:盘点业务开发中的常见流程模式

九层之台,起于垒土。


引言

对于程序员而言,要成为中高级工程师,需要趟过的第一关是:驯服业务流程。“驯服”业务流程,是指设计合理的秩序来组织编程语言指令来实现业务流程,实现健壮的流程闭环。

“软件开发中的存储设计基础” 一文中,阐述了软件开发所必备的存储设计基础。

对于一个功能需求来说,往往存储设计完成之后,随后就是业务流程设计。业务流程设计应该是开发人员接触最多的了。对于一个已上线的系统来说,大部分需求都是对已有业务流程做或大或小的变更,代码修改也是针对业务流程的。要设计健壮的业务流程,需要熟悉常用业务流程模式及常见问题求解。

本文来盘点常用的业务流程模式。

基本概念

名词解释

  • 指令:表达一件原子不可分的事情。比如计算值,加载数到内存。下简称 I。
  • CPU:指令的实际执行者。计算机的“大脑”。
  • 执行单元:CPU 执行指令的运行时实体。一般指进程或线程或协程。下简称 EU。
  • 秩序: 多个指令或流程的执行依赖关系及组合。秩序具有递归结构,即秩序可以嵌套子秩序。下简称 O。
  • 流程: 某个秩序确定的指令或流程在执行单元的执行。流程具有递归结构,即流程可以嵌套子流程。下简称 F。

无论软件流程多么复杂,都可以使用上述五个概念来解释。所有的软件流程,都是按照指定秩序编排的一个或多个流程在一个或多个执行单元的执行,在 CPU 看来就是一系列指令集合的执行。如此而已。

注:这里之所以用“秩序”,而不用“顺序”,是因为顺序有多重语义,容易混淆:

  • 在计算机控制流程里,顺序有专指“指令按先后次序一条一条执行”的含义;
  • 顺序本身也具有通用的次序语义。

为什么要有名词解释?为什么要有像数学那样的“定义”?有人可能觉得有点“刻板”化,实际上名词解释是很重要的。因为汉语的“字”有多义的特征,而人们在沟通时,常常会有“用同一个词却表达着不同含义,用不同的词表达相同的含义”的问题,从而导致各种沟通障碍。定义清晰的名词及解释,有助于后续严谨的推导和流畅的交流。我们 CEO 就强调,在做入侵检测系统时,清晰定义入侵检测系统的每一个名词的语义,能够用少而精练的名词来解释实际入侵场景、行为和结果,是首要任务。

名词在计算机语言里的映射

  • 秩序通常使用控制指令 if, else, while, for, switch 等关键字实现。
  • 流程通常使用计算机编程语言的“函数”或“方法”来是实现。换句话说,“函数”或“方法”是实现流程的语言载体。
  • 执行单元通常是操作系统或编程语言或内置库提供的进程、线程或协程。

执行依赖关系

  • 顺序: 指令 Ia 执行完成后才能执行指令 Ib。
  • 条件: 依赖 Ic 的真假输出,决定执行下一个指令 In。
  • 循环: 依赖 Ic 的真输出,反复执行 Il,直到 Ic 的输出为假。

流程组合模式

复杂流程往往是简单流程的组合。流程组合模式主要有串行模式和并发模式。

  • 串行:通常指在单个 EU 里,多个 F 按照编程语言指令指定的编排秩序来执行,流程之间的执行顺序是确定的。
  • 并发:通常指多个 EU 里的 多个 F 并不一定按照编程语言指令指定的编排秩序来执行,流程之间的执行顺序是不确定的。

举个接力棒的例子。同一支队伍的选手之间是串行的,一个队员抵达下一站交棒后,下一个队员才能出发; 不同队伍之间的选手之间是并发的,即使一个队伍的某个队员暂时领先,这支队伍也未必是先抵达终点。

串行模式

串行模式,就是按指定的编排秩序先后,一个流程一个流程地执行。没有抢占 CPU 调度,没有并发。它是最基本的流程组合模式。

串行模式有五种基本模式:顺序、异常、回调、回滚、重复。

顺序

顺序的形式是 F = Exec(Fa, Fb), Fa 执行完后执行 Fb. Fb 不可能在 Fa 之前执行(虽然指令重排可能导致执行秩序有所变更)。顺序串行流程是最基本的最容易理解的。大多数业务流程实际上都是串行的顺序流程。

异常

异常的形式是 F = Exec(ei, Ei),ei 是异常码,Ei 是异常处理函数。在执行流程 F 时,如果执行出错,异常码为 ei,则执行异常处理函数 Ei。 是在执行串行顺序流程中遇到错误后跳转到另一个流程去处理。异常是一种条件执行依赖关系的处理,但顺序依然是串行的。

回调

回调的形式是 F = call(param, Cf), Cf 是一个回调函数。 在执行顺序流程 F 时,在其中某一步,需要根据传入的不同的回调函数 Cf 来执行不同的流程。实际上,异常处理可以通过回调来实现。异常处理函数作为回调函数 C 传入即可。

回滚

回滚的形式是 F = call(param, S),S 是保存点函数。在执行顺序流程 F 时,在其中某一步遇到了错误,返回到保存点函数 S 处,执行保存点函数。下一次执行 F 时,从 S 确定的保存点处重续执行。

重复

重复的形式是 F = call(param, C, L),C 是条件函数, L 是循环函数。在执行顺序流程 F 时,检查条件函数 C 的输出是否为真。如果为真,则执行循环函数 L;如果为假,则退出流程。执行 L 之后,再次检查 C 的输出是否为真,决定是否执行 L 或退出。

并发模式

并发模式中,虽然指定了流程的编排秩序,但实际中运行时,由于多个 F 是在多个不同的 EU 里,且不同 F 的执行时间不同,导致 F 之间的执行时序是不确定的。

并发模式有三种主要的基本模式:总-分-总,异步-通知-回调,生产者-消费者。

总-分-总

常用于大数据集处理。先将数据集切分为多个子数据集,使用相同或不同的 F 对多个子数据集并发处理,最终将处理结果汇总起来。

异步-通知-回调

常用于多个流程的通信和协作。主流程创建一个执行单元来执行指定子流程,然后主流程可以继续做其它事情。当子流程执行完成后,可以发送一个通知给主流程,主流程去执行一段指定的流程。

生产者-消费者

多个生产者流程生产某种实体,推入队列,多个消费者从队列中取出实体进行消费。如果队列满了,则生产者等待,直至队列有空;如果队列空了,则消费者等待,直至队列有实体进入。

基本流程模式

大多数业务流程都是串行模式和并发模式的进一步组合。

以下模式都是以当前 EU 为观察视角。

单纯查询型

最基本的流程就是查询或统计指定条件的数据。 业务层串行模式。

  • 设计:设计合适的字段,建立合适的查询条件及索引。
  • 应用:分页排序查询或统计查询。

单纯操作型

某个实体数据的保存、更新或删除。业务层串行模式。

  • 设计: 可返回操作后的实体 ID,供随后使用。
  • 应用:编辑、保存或删除配置项。

命令发送型

EU 发送一条命令给一个外部功能提供者。如果是同步的,那么会根据命令执行的返回结果进行后续流程;如果命令执行的返回是异步的,则可能更新相关状态,然后进行其它流程或退出。就 EU 本身而言是串行的,但就交互的多个 EU 来说是并发的。

  • 设计: 状态设计需要有进行中、完成、失败;需要区分发送指令失败和发送成功但执行失败的两种失败情形。
  • 应用: 与外部服务通信。

表查询型

若干流程(对应实现某个功能或服务)注册在一个含有 key-value 键值的表格 T = T(Ki, Fi) 里, EU 通过 Ki 来查找对应的流程 Fi 。查找并执行 Fi 是串行模式,但 Fi 的执行本身可能是异步的。

  • 设计: 功能注册机制。
  • 应用: 异常处理,功能路由。

一致读写型

在两个或多个数据源之间进行同步读写。

  • 查询: 先查缓存,缓存命中则直接返回;缓存未命中则查询数据库。

  • 更新: 先写数据到数据库,再同步到缓存或删除缓存。

  • 应用:缓存读写。

异步任务型

提交一个任务到 EU 池,EU 池启动一个 EU 执行任务。 常用于耗时长的后台任务。

  • 设计:需要做好异步的衔接和流程的闭环。
  • 应用:导出任务;扫描任务;高并发场景。

异步回调型

提交一个任务到 EU 池,EU 池起一个 EU 执行流程 F。流程 F 完成后,发送一个通知,某个 EU 接到通知执行指定回调流程 Cf。

  • 设计:避免嵌套回调层次过多,建议不超过两层。
  • 应用:文件上传回调; RPC 调用回调。

批量型

将一个数据集分解成 N 个批次,每个批次包含一个子数据集。每批次处理一个子数据集。各批次之间是串行的,在一个 EU 里执行。批量处理的用途在于:1. 若必要,可先展示部分数据结果;2. 避免一次性加载大量数据到内存里导致系统崩溃。

  • 设计: 每批量的子数据集大小要仔细确定,兼顾性能和内存占用开销;可添加处理进度展示。
  • 应用:大数据量的计算与汇总。

流处理型

从一个数据流中获取一个或多个数据,执行某个流程; 遍历一个数据流,执行一系列转换或聚合操作。

  • 设计: 可持续运行,数据集无长度限制。
  • 应用: 数据上报的(准)实时消费。

并发汇总型

将一个数据集分解为 N 个子数据集。针对每一个子数据集,起一个 EU 去执行查询或操作。各个 EU 的执行是并发的互不影响的。最后汇总所有 EU 的执行结果(可选)。并发的用途在于利用多核的力量提升性能。在某些场景下,并发具备很好的设计清晰性,容易理解流程。并发和批量可以结合使用。

  • 设计:获取执行结果时要加超时;异常捕获要做好。
  • 应用:Map-Reduce ; 获取大量业务对象的详情。

加锁型

申请锁 L 成功,执行流程 F ,然后释放锁 L。

SaveOrUpdate型

将新旧数据做比对,新的保存,已有的更新。

  • 设计:通常会采用数据版本号机制。
  • 应用:重复性高的大数据量的处理和保存。

定时任务型

起一个定时任务,指定时间或者指定周期去执行。

  • 设计:频率及周期要确定好,确保一次执行的任务能够在指定时间间隔内完成。
  • 应用:定时清理失效数据。

流水线型

有 N 个工序(流程),每个工序执行完成后,修改状态,然后交由下一个工序执行。直到所有工序都执行完成。典型的流水线实现是 PipeLine 模式。

  • 设计: 工序之间的数据交换格式要设计好。
  • 应用: web 请求处理。

轮询型

每隔一段时间执行一次查询操作,直到满足指定要求为止。

  • 设计: 设置好退出条件,避免死循环。
  • 应用: CAS 锁操作。

重试型

重试执行一个任务 N 次,直到执行成功,或者达到指定上限次数为止。

  • 设计: 设置好重试次数和重试间隔;设置失败达到上限次数的兜底流程。
  • 应用: API 查询接口调用重试。

重续型

从上次执行的保存点开始执行。保存点可以是偏移量,也可以是事务的保存点。

  • 设计:偏移量或保存点的选取。
  • 应用:事务;断点续传。

过滤型

  • 判断请求是否符合某个条件。若符合,则忽略。

去重型

  • 判断请求是否已处理过。若已处理,则忽略。

切面型

定义一个切面条件及切面流程。当执行满足该条件时,进入该切面流程。

  • 设计: 兼顾通用和灵活。
  • 应用:拦截器;日志审计。

消息通知型

EU A 发送一个消息给 消息队列 Q, EU B 从消息队列 Q 中接收到这个消息去执行 F。

  • 设计:消息格式要定义好,兼顾通用性、可靠性;避免一次性传输大量数据。
  • 应用:事件驱动型;消息解耦。

生产者-消费者型

N 个 生产者 EU 生成任务添加到一个队列 Q,M 个 消费者 EU 从队列 Q 中取出任务执行。

  • 设计:队列大小;生产者 EU 和消费者 EU 的数量和速率;
  • 应用: 任务协作。

常见业务流程模式

业务流程通常是指实现一个小的功能需求所需的流程编排,该流程编排是由各种基本流程模式组合而成。

查询拼接型

常见的查询接口,就是将多个单纯查询型流程串行组合起来,拼接出最终的数据返回给前端。

  • 设计: 考虑性能和内存开销。
  • 应用:分页功能(分页查询 + 计数查询)。

CRUD型

很多信息管理系统的业务流程,基本是保存、更新、查询、删除流程的串行组合,即单纯查询型和单纯保存型的串行组合。即 CRUD 型流程。实际上,几乎所有的软件系统,基本都是在做 CRUD 的事情。这里 CRUD 的数据源可以是各种数据存储(数据库、缓存、数据仓库等)。

不少程序员似乎有点小看 CRUD 的事情,实际上要做好 CRUD 并不是那么简单的事情。要做好 CRUD 型的流程,关键在于存储设计的质量。

  • 设计:考虑闭环和严谨性。
  • 应用:大部分业务流程。

任务执行型

创建一个任务,保存任务信息,设置任务状态,然后在流程中追踪这个任务的执行和完成。

  • 设计:任务表;任务状态更新。
  • 应用: 操作类、扫描类任务。

子系统交互型

涉及多个子系统之间的交互。

  • 设计:数据一致性保证。
  • 应用: 客户端-服务端系统的通信;(跨团队、部门、企业、地域、国家)子系统的通信。

模板型

定义一个流程模板,模板的某些点可以根据不同场景进入不同的指定流程。

  • 设计: 模板参数和模板流程要将通用的部分提取出来,尽可能少的包含差异部分。
  • 应用: 不同任务或应用的生命周期管理;相似业务的通用流程。

常见流程问题

流程闭环问题

当流程中的某个操作失败后,就会导致整体流程无法完成,停滞在某个点上,无法闭环。

闭环的解决思路:

  • 直接失败。对于不能重试(可能导致资损)的情形,直接失败是最简单有效的办法。
  • 重试、设置最大失败次数。对于可重试场景,可间隔一段时间重试一次,达到最大失败次数则设置为终态失败。
  • 保存点。设置保存点,失败之后可以从保存点重续执行,最终达到终态成功。

业务流程不是一成不变的

大多数开发人员都会认为业务流程非常重要。在我看来,存储设计才至关重要。因为存储设计是基础的相对稳定的,而业务流程往往会因为各种需求的变更或者性能优化而发生变化。业务流程往往不是一成不变的。

在充分理解和夯实了存储基础之后,业务流程是可以灵活加以改造,来适应不同的需求。

小结

本文主要盘点了业务流程所使用到的基本和常见业务流程模式。熟悉这些流程模式后,在业务开发中就能够灵活选取相应的模式及组合来实现业务流程。


posted @ 2022-03-30 08:01  琴水玉  阅读(343)  评论(0编辑  收藏  举报