业务单系统架构设计心得(二):流程编排

阅读说明:

  1. 如果有排版格式问题,请移步 www.yuque.com/mrhuang-ire... 《业务单系统架构设计心得(二):流程编排》,选择宽屏模式效果更佳。
  2. 本文为原创文章,转发请注明出处。如果觉得文章不错,请点赞、收藏、关注一下,您的认可是我写作的动力。

上一节《业务单系统架构设计心得(一)》中,讲到了业务系统分层。本文对流程层继续深究,讨论流程层沉淀出通用组件。

业务复用编排

会用到编排的,大多是有业务复用,流程层根据业务需求串联起不同的子服务,上一节已经比较详细介绍了过程,这里不再详细展开叙述。这里补充一下串联方式,有以下三种方式:

  • 硬编码:在系统中代码写死要串联的服务;
  • 配置式:通过配置的方式进行串联,这种好处是比较灵活;
  • 注解式:java中的注解能够做到标记的作用,通过自定义注解实现串联;

异步查询编排

在实际业务中,一个app页面可能会加载很多信息,以美团外卖商家端为例: 因此,服务端必须一次性返回订单的所有信息,包含订单主信息、商品、结算、配送、用户信息、骑手信息、餐损、退款、客服赔付等。企业通常采用微服务架构,这些不同的模块通常部署在不同的机器上,因而需要发起多次rpc请求数据。如果继续采用同步加载的方式,这将带来一个新的问题,对外请求太多拖垮接口响应时间,商家的用户体验太差。

遇到性能的第一问题是使用线程池,使用多个线程并行处理任务,能够明显改善接口的性能。但是,这不是完美的,使用线程池虽然可以解决异步的问题,但是没有解决组合的问题。我们来对实际业务流程抽象,将流程中每个小步骤看做节点,那么业务可以简化成如下流程。

业务依赖简化版

该简化流程涵盖了实际业务中的可能出现的各种组合情况:

  • 零依赖:节点向下没有依赖,图中有节点CF1、 CF2、CF3。
  • 一元依赖:节点向下有一个依赖项,图中有节点CF4、CF6。
  • 两元或者多元依赖:节点向下有两个或者多个依赖项,图中两元依赖节点有CF5,三元依赖节点有CF7。
  • 两元或者多元被依赖:节点被向上两个或者多个节点依赖,图中有CF3。

显然,对于前后依赖的问题,java线程池没有很好的解决,而这是业务流程中经常会出现的场景。java8之后,新加了CompletableFuture,CompletableFuture就很好的解决了执行依赖的问题。

异步编排执行框架

本节将讲述基于CompletableFuture搭建异步编排执行框架,示例仍以上图业务依赖简化版为例。

1.构建依赖关系

构建依赖关系前,需要将流程进行拆分多个节点,确定节点两两之间的依赖关系。

2.生成依赖树

根据上一步两个节点之间的依赖关系,构建一颗完整的依赖树,这里需要记录下父节点与其依赖的所有子节点的关系。最下层没有依赖的节点是叶子节点,图中CF1, CF2, CF3就是叶子节点。

3.节点执行

节点执行分为两步。第一步,找到叶子节点,叶子节点因为没有依赖,可以直接执行。

4.节点唤醒

在第三步中,被依赖节点执行完时,需要唤醒上层节点,上层节点检查依赖的所有子节点是否都已完成,如果都完成,则执行本节点,直至最上层的节点也完成,整个流程结束。这其中有个关键的一点是,子节点与父节点之间如何唤醒,到这里能够想到通过注册回调事件来实现了。这里使用CompletableFuture来实现,关键代码如下:

java 复制代码
//节点1
CompletableFuture<String> childFuture1 = new CompletableFuture<>();
//节点2
CompletableFuture<String> childFuture2 = new CompletableFuture<>();
//节点3
CompletableFuture<String> childFuture3 = new CompletableFuture<>();

////节点4, 依赖节点1,2,3, 注册回调事件
CompletableFuture<String> childFuture4 = new CompletableFuture<>();


CompletableFuture.allOf(childFuture1, childFuture2, childFuture3)
                // 当node节点所有的父节点结果都构建完成时唤醒node节点
                .whenComplete((Void aVoid, Throwable throwable) -> {
                    {} //节点4的逻辑
                    childFuture4.complete("");
                });


{}//这里执行节点1的逻辑
childFuture1.complete("");


{}  //这里执行节点1的逻辑
childFuture2.complete("");


{} //这里执行节点1的逻辑
childFuture3.complete("");

使用异步编排的几个注意事项

1.CompletableFuture使用自定义线程池。手动传递线程池参数的好处是可以更方便的调节参数,并且可以给不同的业务分配不同的线程池,以求资源隔离,减少不同业务之间的干扰。 2.使用自定义线程池需要传递调用线程的上下文内容。子线程启动时,继承调用线程的上下文内容,比如调用线程的traceId等等,以及业务上塞入的线程ThreadLocal变量。子线程退出时,需要清理掉继承的信息。 3.线程池数量不能过多。线程过多时,同时处理太多请求,实际业务上通过rpc组件请求外部服务,请求将达到io线程上。如果是存在io耗时的请求,将导致io线程一直被占用,影响整个服务的相应,并没有达到异步提高性能的效果。

参考文献:

  1. docs.oracle.com/javase/8/do...
  2. tech.meituan.com/2022/05/12/...
  3. mp.weixin.qq.com/s/TdWgyDTTX...
相关推荐
javaDocker11 小时前
业务架构、数据架构、应用架构和技术架构
架构
JosieBook13 小时前
【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍
架构
.生产的驴14 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
丁总学Java14 小时前
ARM 架构(Advanced RISC Machine)精简指令集计算机(Reduced Instruction Set Computer)
arm开发·架构
ZOMI酱16 小时前
【AI系统】GPU 架构与 CUDA 关系
人工智能·架构
天天扭码1 天前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
余生H1 天前
transformer.js(三):底层架构及性能优化指南
javascript·深度学习·架构·transformer