activiti异步执行
一点历史
永无休止的一件事是,Activiti在某些非常大的规模的大型组织中的使用方式。 过去,这导致了各种优化和重构,其中包括异步执行器-替换旧的作业执行器。 对于未启动的用户:这些执行器在流程实例中处理计时器和异步继续。 特别是在过去的两年中,我们已经看到它的使用大大增加。 异步执行器的引入极大地提高了性能。 但是,去年在巴黎举行的社区活动中,我们了解到,在处理大量工作时,执行者使用的查询可能会导致需要进行表扫描。 这永远不是一件好事。
因此,我们知道在完成版本6之前,我们确实想做一件事,那就是重构异步执行器,使它使用的所有查询都变得非常简单。 这确实意味着我们必须将作业数据拆分为与不同类型和状态匹配的各种表,同时仍要使API与以前的Activiti版本兼容。
在过去的几个月中,我们一直在做这些事情(包括许多其他事情),并取得了一些不错的结果以及一些新的不错的API,这些API丰富了该平台。 我可以在“新的”异步执行程序的工作方式上写另一个博客,但是昨天我已经完成了文档工作,因此,如果您对所有工作方式都感兴趣, 请查看在线文档或检查工具 栏上 的源代码。 v6分支 。
架构设计当然受我们从过去两种实现中学到的知识的影响,但同时也受消息队列系统中概念的影响很大。 设计目标之一是插入消息队列并运行它应该非常容易,因为我们有一种直觉,认为这将对性能有所帮助。
因此,我们做到了。 由于新的体系结构,使异步执行程序与消息队列一起工作几乎是微不足道的。 如果您对实现感兴趣, 我还会在文档中添加有关此主题的部分。
而且,当然,您知道我,我只是想将这两个执行程序实现相互比较基准��
基准项目
您可以找到我在Github上使用的代码: https : //github.com/jbarrez/queue-based-async-executor-benchmark
基本上,它的操作是使用配置属性文件运行Main.java。
- 使用适当的配置启动流程引擎(我最近在网上看到一些Activiti基准测试,这些基准在不使用适当的连接池数据源的情况下对Activiti的性能进行了基准测试。
- 如果以“生产者”身份运行,将启动10000个流程实例,每10毫秒一个。 定期的统计信息将被打印到控制台上。
- 如果以“执行程序”身份运行,则将流程引擎配置为启用异步执行程序。
- 可以有任意数量的生产者/执行者,但是所有生产者/执行者都进入同一个数据库。
项目中使用的流程定义如下:
需要注意的重要一点(在图中不可见)是,在此重要的流程定义中,所有服务任务都是异步的。 并行派生之后的服务任务与加入的并行网关一样配置为互斥的 。 这里有两个计时器,其中用户任务上的一个是1秒,子流程上的一个是50分钟。 总而言之,当启动流程实例时,它导致需要执行27个作业才能到达终点。 对于10000个实例,这意味着我们正在有效测试270 000个作业的吞吐量。
请注意,与任何基准测试一样,原始数据说明了一切,但不是全部。 这一切都取决于服务器硬件,实际的流程定义和许多其他细节。 但是,如果在完全相同的硬件上执行完全相同的代码,相对数字会给我们带来很多启发。 阅读下一部分时,请记住这一点。
测试环境
所有基准测试都是在Amazon Web Services(AWS)上运行的,生产者/执行者使用EC2服务器,r3.4xlarge(16个vCPU,16个vCPU)上的数据库使用RDS PostgresQL (因为Postgres是一个很棒的数据库,非常容易设置) 122 GiB内存)。
使用以下EC2配置
- RDS(postgres):r3.4xlarge(16个vCPU,122 GiB内存)
- 生产者引擎:c3.4xlarge(16个vCPU,30 GiB内存)
- 执行器引擎:c3.8xlarge(32个vCPU,60 GiB内存)
所有服务器都在欧盟西部地区运行。 因此,所有测试结果都具有真实的网络延迟(没有运行在localhost基准测试上的延迟,因此跳过了网上经常看到的联网)。 当运行上面的项目时,JVM分配了8GB的空间。
我们将使用的指标是作业的吞吐量 ,以作业/秒表示。 简而言之,在测试运行之后,我们验证数据库中的数据是否正确(即10K完成的流程实例),并采用第一个开始时间和最后一个结束时间,这使我们获得了x秒的时间。 则吞吐量为x / 270000(众所周知,每个流程实例等于27个作业)。
基线测量
基准测试的第一件事是“基准”,即由线程池支持的常规异步执行程序(即v5中异步执行程序的改进设计)。 在此测试中,我们使用了2台服务器,并进行了以下配置(注意:6.0.0.Beta3实际上是快照版本):
一个 | 乙 | C | d | |
Activiti版本 | 6.0.0.Beta3 | 6.0.0.Beta3 | 6.0.0.Beta3 | 5.21.0 |
生产者引擎 | 1个 | 1个 | 1个 | 1个 |
执行器引擎 | 1个 | 1个 | 2 | 2 |
#池中的线程 | 32 | 10 | 10 | 10 |
阻塞队列大小 | 256 | 100 | 100 | 100 |
一些有趣的观察:
我认为配置A会比配置B更好,因为这台机器毕竟有32个CPU,因此将线程池的线程数与此匹配是合理的。 但是,配置B的设置非常相似,除了只有10个线程和较小的阻塞队列之外,它的性能大大提高(310 vs 210作业/秒)。 一个可能的解释可能是32个线程争用太多? 我确实记得当初选择默认值“ 10”时,我们进行了一些基准测试,其中10是吞吐量最佳的“魔术数字”(但我认为这取决于所使用的机器。)
我希望添加另一个执行程序节点会产生更大的影响,毕竟我们要添加32个CPU的计算机,但是收益很小(310到326)。 我们将学习原因,并在本文的稍后阶段进行修复。
使用Activiti版本5.21.0的配置D使用与配置C相同的设置。但是,改进的版本6的异步执行程序显然在这里胜出(326 vs 266)。 这当然是我们希望的:-)。
到目前为止,我们最好的结果是每秒326个作业 (并使用两台服务器)。
基准线的变化
鉴于以上设置,可以询问运行混合的生产者/执行者时产生的影响。 这是Activiti引擎默认的运行方式:引擎将同时负责启动流程实例并立即执行它们。 这是配置E (与配置C相同,除了两个引擎现在都是生产者/执行者),结果如下所示。 而且显然性能较差。 一种解释可能是该机器已经每10毫秒使用10个线程来启动流程实例,这可能导致与异步执行器的10个线程相当多的争用。 可能可以对该设置进行很多调整以获得更好的数字,但这不是此博客的目标。 但是结果仍然很有趣。
因此,考虑到两个执行器引擎胜于一个执行器引擎,合乎逻辑的事情是尝试三个执行器。 这是配置F。
类似于从一个执行程序到两个执行程序,吞吐量提高了。 但不是以一种壮观的线性方式。
介绍基于消息队列的异步执行器
现在该切换到基于消息队列的异步执行器了,现在我们有了基准编号。 我选择了最新版本的ActiveMQ ,因为我对此很熟悉,并且设置起来非常容易。 我没有花时间调整ActiveMQ,切换持久性策略或尝试替代方法。 因此,那里也可能会有一些利润。
在基准项目中,我将Spring与以下配置一起使用: https : //github.com/jbarrez/queue-based-async-executor-benchmark/blob/master/src/main/java/org/activiti/MyConfigMessageExecutor.java 。 之所以选择Spring,是因为MessageListenerContainer提供了一种简单的方法来使消息队列侦听器可以很好地与多个线程一起工作(JBoss这样的应用程序服务器可以为您提供其他功能)。 更具体地说,MessageListenerContainer的concurrenConsumers设置允许设置用于以智能方式监听消息的线程数。 是的,此类确实具有许多可能会更好地影响结果的属性,但这又不是重点。 请记住相对数字。
对于此配置,我们使用与config C类似的设置(到目前为止,我们在两台服务器上的最佳结果),称为config G:1个生产者引擎,2个执行者引擎。 请注意,我们现在还在混合中添加了“队列服务器”,它使用的是c3.8xlarge机器(32个vCPU,60 GiB RAM),类似于执行引擎服务器。
结果在下面…简直太棒了:在等效设置(但带有额外的消息队列服务器)中的消息队列异步执行程序比基于线程池的异步执行程序快四倍 。
一个小的实现说明:我们不得不切换到UUID ID生成器 ,因为吞吐量对于默认值而言太高了。 请记住,UUID生成器比默认生成器慢,结果甚至更棒(因为我们在这里实际上谈论的是毫秒)。
有趣的观察!
如果您运行基准测试项目,则会看到它会定期吐出一些统计信息,以便您可以跟踪系统中有多少作业,计时器,用户任务,历史活动实例,流程实例等。
在运行消息队列设置时,从这些数字中可以很清楚地看出一种模式。 基于线程池的异步执行器可以更快地完成流程实例(例如,大约1分钟后,我们看到一批流程实例已完成),而对于基于消息的异步执行器,流程实例实际上都是在最后一次大爆发中完成的。 这表明后者将更多地分散流程实例活动的执行,而基于线程的活动将继续进行直到完成为止。
团队中的一些讨论导致了对此的解释:基于线程池的线程将始终将下一个异步作业传递给执行器,而基于消息的线程则将其放在队列中,那里已经有数千条消息在等待。 现在添加一个事实,我们对流程实例有很多排它异步作业,这意味着对于基于线程池的异步作业,许多线程试图获取流程实例锁,但是由于执行了排他异步操作而失败。 但是,这项工作没有获得 ,很快就重新开始了。 对于基于消息队列的消息队列,将它们再次添加到消息队列的末尾。 其中有数千条其他消息正在等待。 回到执行此特定消息时,排他锁很可能已经很久了。
这导致在基于线程池的异步执行程序中进行一些重构:删除并重新插入作业,而不是简单地释放作业的锁定,从而有效地模拟了队列行为。 这是修复程序: https : //github.com/Activiti/Activiti/commit/d08a247570336c872bb17ce513c1fb95b3ba47a2#diff-bd9c7efdb4c57462f6fe71641b280942R212 。
在与config C完全相同的设置(称为config H(1个生产者,2个执行者))中对这些基准进行基准测试,这表明我们此简单的解决方案将吞吐量提高了34%! 现在我们有了一个新的基准
更好的消息队列异步执行器结果
因此,在消息队列结果(配置G)中,我们使用了10个线程的相当保守的设置来侦听消息。 想法是我们还有10个线程池用于线程池。 当然,消息队列使用者从根本上不同于轮询线程:此类使用者与队列具有持久连接,而队列代理实际上将工作推给其使用者。 这应该更有效。 因此,我们尝试了以下配置,在这些配置中,我们改变了使用者(从而消耗了线程)和执行程序节点的数量。
一世 | Ĵ | ķ | 大号 | |
生产者引擎 | 1个 | 1个 | 1个 | 1个 |
执行器引擎 | 2 | 2 | 3 | 3 |
#消费者/引擎 | 32 | 64 | 32 | 64 |
因此,一个不错的观察结果是,添加更多的消费者是超级有效的。 我们正在达到2222.9作业/秒的吞吐量 。 如果您问我,那是极快的速度,是基于线程池的异步执行器的五倍。
可悲的是,将更多的执行器机器添加到组合中实际上对性能不利。 我认为瓶颈现在已成为数据库,以及它如何处理大规模进行的所有并发。 当然,我根本不调整数据库 ,而只是常规RDS postgres实例。 或尝试使用Aurora或Oracle(在我以前的基准测试中获得最好的结果)。 但是,这里的重点是相对数量 ,而不是挤出吞吐量的最后一部分。 我认为相对数字点已经确定为��
结论
数字说明了一切:基于新消息队列的异步执行器击败了基于线程池的异步执行器。 这是否意味着您必须立即切换? 不, 常规的异步执行器也非常快(每秒436作业仍然很快),但是更重要的是,设置非常简单,因为Activiti引擎可以处理所有事情。 在项目中添加消息队列意味着更多的复杂性:可能会失败或崩溃的另一件事,额外的监视,维护等。但是,当您执行大量 (我的意思是“很多”)异步工作时,您会重新达到默认异步执行器可以执行的操作的限制,很高兴知道还有替代方法。
我们还要忘记这里得出的另一个结论:版本6中的新异步执行程序实现是对版本5的重大改进!
进一步的工作
当前的实现仅是Spring / JMS。 但是,该实现很容易移植到其他系统和/或协议(应用程序服务器,STOMP,AMPQ,AWS SQS等)。 对于将成为流行的下一个选择的反馈,我们深表感谢。
有趣的是,这种基于消息队列的异步执行器使实现“优先级队列”非常简单。 优先级队列是我们许多大型用户所要求的功能:提供特定的流程定义/实例/在特定条件下/ ...优先级与常规作业相比。 容易想象如何设置多个队列和/或分配更少或更多的使用者以赋予某些用例优先级。
翻译自: https://www.javacodegeeks.com/2016/07/benchmarking-message-queue-based-activiti-async-executor.html
activiti异步执行