微创新与稳定性的权衡
之前做过一个项目,业务最高峰CPU使用率也才50%,是一个IO密集型的应用。里面涉及一些业务编排,所以为了提高CPU使用率,我有两个方案:一个是简单的梳理将任务可并行的采用并行流、额外线程池等方式做并行;另外一个方案是采用基于DAG有向无环图的任务调度。采用并行的方式,改造代码在几十行;采用DAG方案改造代码在几百行,自己觉得也不复杂,但跟别人讲时,感觉理解成本还是有点高,加上并行方案已经可以将最高峰CPU使用率提高到80%多,最终权衡必要性不高,还引入了额外的复杂性,增加维护成本,所以我自己否定了这个方案。但这并不妨碍我对DAG有了一定的理解:
DAG任务调度
DAG任务调度系统的核心概念是将任务表示为一个有向无环图(DAG),其中每个节点表示一个任务,每条边表示一个任务之间的依赖关系。DAG任务调度系统的主要优势在于它可以有效地管理和执行依赖关系复杂的多任务系统,并且可以在大规模分布式环境中运行。所以我们的简单业务编排确实并不需要这样复杂的设计。
设计DAG,主要要设计三块。一块是节点,代表的是任务。任务对象主要关注任务的执行;
//定义一个Executor接口
//代表一个可执行的任务,execute代表任务的执行
public interface Executor {boolean execute();
}
/*
* 定义一个Executor接口的实现Task
*id:任务id
*name:任务名
*state:任务状态,简化为0:未执行,1:已执行
*hasExecuted返回任务是否已执行
*/
public class Task implements Executor{private Long id;private String name;private int state;public Task(Long id, String name, int state) {this.id = id;this.name = name;this.state = state;}public boolean execute() {System.out.println("Task id: [" + id + "], " + "task name: [" + name +"] is running");state = 1;return true;}public boolean hasExecuted() {return state == 1;}
}
第二块是图,里面要做两件事,一件是管理任务,一件事管理任务之间的依赖,反应到图上就是要有节点和节点之间的连线;
//任务图,这个类使用了邻接表来表示有向无环图。tasks是顶点集合,也就是任务集合。
//map是任务依赖关系集合。key是一个任务,value是它的前置任务集合。
//一个任务执行的前提是它在map中没有以它作为key的entry,或者是它的前置任务集合中的任务都是已执行的状态。public class Digraph {private Set<Task> tasks;private Map<Task, Set<Task>> map;public Digraph() {this.tasks = new HashSet<Task>();this.map = new HashMap<Task, Set<Task>>();}public void addEdge(Task task, Task prev) {if (!tasks.contains(task) || !tasks.contains(prev)) {throw new IllegalArgumentException();}Set<Task> prevs = map.get(task);if (prevs == null) {prevs = new HashSet<Task>();map.put(task, prevs);}if (prevs.contains(prev)) {throw new IllegalArgumentException();}prevs.add(prev);}public void addTask(Task task) {if (tasks.contains(task)) {throw new IllegalArgumentException();}tasks.add(task);}public void remove(Task task) {if (!tasks.contains(task)) {return;}if (map.containsKey(task)) {map.remove(task);}for (Set<Task> set : map.values()) {if (set.contains(task)) {set.remove(task);}}}public Set<Task> getTasks() {return tasks;}public void setTasks(Set<Task> tasks) {this.tasks = tasks;}public Map<Task, Set<Task>> getMap() {return map;}public void setMap(Map<Task, Set<Task>> map) {this.map = map;}
}
第三块是调度,就是获取任务列表,并按照它们之间的依赖关系来执行。
//调度器,就是遍历任务集合,找出待执行的任务集合,
//放到一个List中,再串行执行(若考虑性能,可优化为并行执行)。
//若List为空,说明所有任务都已执行,则这一次任务调度结束。
public class Scheduler {public void schedule(Digraph digraph) {while (true) {List<Task> todo = new ArrayList<Task>();for (Task task : digraph.getTasks()) {if (!task.hasExecuted()) {Set<Task> prevs = digraph.getMap().get(task);if (prevs != null && !prevs.isEmpty()) {boolean toAdd = true;for (Task task1 : prevs) {if (!task1.hasExecuted()) {toAdd = false;break;}}if (toAdd) {todo.add(task);}} else {todo.add(task);}}}if (!todo.isEmpty()) {for (Task task : todo) {if (!task.execute()) {throw new RuntimeException();}}} else {break;}}}public static void main(String[] args) {Digraph digraph = new Digraph();Task task1 = new Task(1L, "task1", 0);Task task2 = new Task(2L, "task2", 0);Task task3 = new Task(3L, "task3", 0);Task task4 = new Task(4L, "task4", 0);Task task5 = new Task(5L, "task5", 0);Task task6 = new Task(6L, "task6", 0);digraph.addTask(task1);digraph.addTask(task2);digraph.addTask(task3);digraph.addTask(task4);digraph.addTask(task5);digraph.addTask(task6);digraph.addEdge(task1, task2);digraph.addEdge(task1, task5);digraph.addEdge(task6, task2);digraph.addEdge(task2, task3);digraph.addEdge(task2, task4);Scheduler scheduler = new Scheduler();scheduler.schedule(digraph);}
}
是不是也不是很复杂,但是添加删除任务和添加删除依赖需要页面可视化管理,添加多了就容易乱。特别是作为一个平台:用户没有问题,所有用户的误操作问题都可以通过减少操作的复杂性来规避。如果将来真有必要使用DAG任务调度,界面设计至少要将节点和依赖以图形化的方式展示出来,让用户一目了然。
工作流引擎
我们的项目涉及的业务编排,有的同事叫这个是工作流。我就仔细的思考了一下,这个到底是不是工作流。我的理解,就是一个责任链搞定的事情。而工作流是复杂版本的状态机。这个工作流的“流”字更多不是流程,而是流转。如果没有复杂的状态流转就不应该当成工作流来看,增加问题的复杂性。说我们的项目是工作流从道理上讲也不是不对,但就好像说:橘子是一个对象。 对,但没有什么指导意义。
现在基于BPMN2.0协议的工作流引擎很受推崇。Activiti,Flowable都是它的实现。BPMN2.0协议中元素的主要分类为,事件-任务-连线-网关。
一个流程必须包含一个事件(如:开始事件)和至少一个结束(事件)。其中网关的作用是流程流转逻辑的控制。任务则分很多类型,他们各司其职,所有节点均由连线联系起来。
网关分为三类:互斥网关(Exclusive Gateway),又称排他网关,他有且仅有一个有效出口。并行网关(Parallel Gateway),他的所有出口都会被执行。包容性网关(Inclusive Gateway),只要满足条件的出口都会执行。是不是很像DAG有向无环图的一个节点到其他节点的路径?排他网关就是只有一条路径到下一个节点;并行网关就是到下一排节点都有路径;包容性网关就是到下一排节点部分有路径。
这就对了,工作流必须是DAG的。它和任务调度不同在于工作流没有强调调度。但工作流终究要被执行的,实时被执行就是实时被调度;在大数据工作流里也经常见到被周期性调度的情况。
有限状态机
刚才提到工作流是复杂版本的状态机,有没有简单的状态机呢?很多。比如咱们经常见到的用枚举来实现的。定义一个枚举,里面有成功和失败两个状态,他们之间的转换也是状态机。在金融支付领域,支付状态有 支付中,支付成功,支付失败,冲正中,冲正完成…… 状态流转就会比较复杂。可以用状态模式来管理。
状态模式
状态模式是我之前非常喜欢用的来避免大量if else的方法。
根据 GoF 的定义,状态模式的三个核心角色分别是:
环境(Context):它定义了客户端所感兴趣的接口,并维护一个当前状态,在具体状态类中实现该接口的各个具体操作。
抽象状态(State):它定义了一个接口,用于封装环境对象中不同状态对应的行为。
具体状态(Concrete State):它实现了抽象状态接口,封装了不同状态下对环境对象的响应行为。
下面是一个简单实现:
// 定义抽象状态接口
interface State {void handle();
}// 定义具体状态类
class ConcreteState1 implements State {@Overridepublic void handle() {System.out.println("当前状态为 State1.");}
}class ConcreteState2 implements State {@Overridepublic void handle() {System.out.println("当前状态为 State2.");}
}// 定义环境类
class Context {private State state;public void setState(State state) {this.state = state;}public void request() {state.handle();}
}public class StatePatternDemo {public static void main(String[] args) {// 创建状态对象State state1 = new ConcreteState1();State state2 = new ConcreteState2();// 创建环境对象Context context = new Context();context.setState(state1);context.request();context.setState(state2);context.request();}
}
总结
在工作中,有两顶思考帽:一顶是项目可持续性的帽子,要对项目负责,要使用合适的技术;一顶是让项目与时俱进的帽子,过时老套的技术降低了项目的吸引力,可能面临吸引不到更优秀的人才。其实项目中可以不使用某技术本身,却可以使用其思想,比如DAG任务调度的思想梳理清楚依赖,让能执行的尽早执行,提高运行效率。并行化也一定程度的增加了并发度,达到了效果。再举个例子,咱们平时提到的分布式事务,分布式事务的框架工作中很少用,但是分布式事务的思想却随处可见。比如支付时,如果失败超时,则返回失败并发起冲正,确保支付款退回给消费者,这就是一种补偿性事务的思想,就是这么简单。思考有了,工具是次要的。
声明:本文中使用的代码均为网上拷贝,不是本文重点,只做解释说明用。
相关文章:

微创新与稳定性的权衡
之前做过一个项目,业务最高峰CPU使用率也才50%,是一个IO密集型的应用。里面涉及一些业务编排,所以为了提高CPU使用率,我有两个方案:一个是简单的梳理将任务可并行的采用并行流、额外线程池等方式做并行;另外…...

对回调函数的各种讲解说明
有没有跟我师弟一样的童靴~,学习和使用ROS节点时,对其中的callback函数一直摸不着头脑的,以下这么多回调函数的讲解,挨个看,你总会懂的O.o 回调函数怎么调用,如何定义回调函数: 回调函数怎么调用,如何定义…...
Java多线程:创建多线程的三种方式
在Java中,有三种方式创建多线程,继承类Thread,继承接口Runnable,继承接口Callable。其中Thread和Runnable需要重写方法run,方法run没有返回值;Callable需要重写方法call,方法call可以返回值。 …...
Unity中打印信息的两种方式
不继承MonoBehaviour的普通C#类中打印信息: 使用Debug类的方法: Unity提供了Debug类,其中包含了一些用于打印信息的静态方法。以下是常用的几种方法: Debug.Log(message):打印普通信息。Debug.LogWarning(message)&a…...

给定n个字符串s[1...n], 求有多少个数对(i, j), 满足i < j 且 s[i] + s[j] == s[j] + s[i]?
题目 思路: 对于字符串a,b, (a.size() < b.size()), 考虑对字符串b满足什么条件: 由1、3可知a是b的前后缀,由2知b有一个周期是3,即a.size(),所以b是用多个a拼接而成的,有因为a是b的前后缀&…...
Linux磁盘空间与文件大小查看命令详解
1. 查看磁盘空间大小 在Linux系统中,有多个命令可以用来查看磁盘空间的使用情况。最常用的命令是df(disk free)。 df -hdf命令的 -h 选项以人类可读的方式显示磁盘空间,该命令将显示文件系统的使用情况、剩余空间等信息。 2. 查看…...

网络通信过程的一些基础问题
客户端A在和服务器进行TCP/IP通信时,发送和接收数据使用的是同一个端口吗? 这个问题可以这样来思考:在客户端A与服务器B建立连接时,A需要指定一个端口a向服务器发送数据。当服务器接收到A的报文时,从报文头部解析出A的…...

STL——stack容器和queue容器详解
目录 💡stack 💡基本概念 常用接口 💡queue 💡基本概念 💡常用接口 💡stack 💡基本概念 栈(stack):一种特殊的线性表,其只允许在固定的一端…...

django websocket实现聊天室功能
注意事项channel版本 django2.x 需要匹配安装 channels 2 django3.x 需要匹配安装 channels 3 Django3.2.4 channels3.0.3 Django3.2.* channels3.0.2 Django4.2 channles3.0.5 是因为最新版channels默认不带daphne服务器 直接用命令 python manage.py runsever 默认运行的是w…...

软件测评中心▏性能测试之压力测试、负载测试的区别和联系简析
在如今的信息时代,软件已经成为人们日常工作和生活不可或缺的一部分。然而,随着软件的发展和应用范围的不断扩大,软件性能的优劣也成为了影响用户使用体验的重要因素。 软件性能测试即对软件在不同条件下的性能进行评估和验证的过程。通过模…...
Go 语言 panic 和 recover 详解
panic() 和 recover() 是 Go 语言中用于处理错误的两个重要函数。panic() 函数用于中止程序并引发panic,而 recover() 函数用于捕获panic并恢复程序的执行。 什么是panic和recover? panic panic() 函数用于中止程序并引发panic。panic() 函数可以接收…...

NAND Separate Command Address (SCA) 接口数据传输解读
在采用Separate Command Address (SCA) 接口的存储产品中,DQ input burst和DQ output burst又是什么样的策略呢? DQ Input Burst: 在读取操作期间,数据以一种快速并行的方式通过DQ总线传送到控制器。在SCA接口下,虽然命令和地址信…...

彻底认识Unity ui设计中Space - Overlay、Screen Space - Camera和World Space三种模式
文章目录 简述Screen Space - Overlay优点缺点 Screen Space - Camera优点缺点 World Space优点缺点 简述 用Unity中开发了很久,但是对unity UI管理中Canvas组件的Render Mode有三种主要类型:Screen Space - Overlay、Screen Space - Camera和World Spa…...

档案数字化怎样快速整理资料
对于机构和组织来说,档案数字化是一个重要的信息管理和保护措施。要快速整理资料进行档案数字化,可以遵循以下步骤: 1. 准备工具和设备:确保有一台计算机、扫描仪和相关软件。 2. 分类和组织资料:先将资料分类…...

面试算法100:三角形中最小路径之和
题目 在一个由数字组成的三角形中,第1行有1个数字,第2行有2个数字,以此类推,第n行有n个数字。例如,下图是一个包含4行数字的三角形。如果每步只能前往下一行中相邻的数字,请计算从三角形顶部到底部的路径经…...

androj studio安装及运行源码
抖音教学视频 目录 1、 jdk安装 2、下载安装androj studio 3 、打开源码安装运行相关组件 4、 安装模拟器 1、 jdk安装 安卓项目也是java开发的,运行在虚拟机上,安装jdk及运行的时候,就会自动生成虚拟机, jdk前面已经讲过&…...

【Web】token机制
🍎个人博客:个人主页 🏆个人专栏:Web ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 机制基本: 优势: 结语 我的其他博客 前言 在当今互联网时代,安全、高效的用户身份验证和资源授…...
JVM 11 调优指南:如何进行JVM调优,JVM调优参数
JVM 11的优化指南:如何进行JVM调优,以及JVM调优参数有哪些”这篇文章将包含JVM 11调优的核心概念、重要性、调优参数,并提供12个实用的代码示例,每个示例都会结合JVM调优参数和Java代码 本文已收录于,我的技术网站 dd…...

横版动作闯关游戏:幽灵之歌 GHOST SONG 中文版
在洛里安荒凉的卫星上,一件长期休眠的死亡服从沉睡中醒来。踏上发现自我、古老谜团和宇宙骇物的氛围2D冒险之旅。探索蜿蜒的洞穴,获得新的能力来揭开这个外星世界埋藏已久的秘密。 游戏特点 发现地下之物 探索这个广阔而美丽如画,充满密室和诡…...

【C++】:C++中的STL序列式容器vector源码剖析
⛅️一 vector概述 vector的使用语法可以参考文章: 总的来说:vector是可变大小数组 特点: 支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢 元素保存在连续的内存空间中,因此通过下标取值非常快 在容器中间位置添加…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...