工作流分布式Activiti部署

wen java案例 3

本文目录导读:

工作流分布式Activiti部署

  1. 方案一:共享数据库 + 分布式锁(最常用)
  2. 方案二:消息队列 + 异步解耦(高吞吐推荐)
  3. 方案三:独立部署 + 远程RPC/HTTP(微服务化)
  4. 方案选择决策树
  5. 部署数据库层注意事项

对于Activiti工作流引擎的分布式部署,核心挑战在于如何保证流程状态的强一致性以及如何管理运行时数据(特别是流程锁)

由于Activiti是有状态的组件(流程实例、任务、变量存储在数据库中),直接进行水平扩展(多节点无状态部署)会面临数据库竞争节点间缓存/锁不一致的问题。

以下是针对Activiti分布式部署的详细方案、架构演进及最佳实践。

  • 不建议将Activiti的多个节点直接对接到同一个数据库 Schema 进行写操作(在7.x版本以前),除非引入外部协调。
  • 推荐架构共享数据库 + 分布式锁(或基于消息队列的异步分发)独立部署 + 远程调用
  • 新版本(7.x+):Activiti Cloud或基于Spring Cloud的架构已原生支持分布式场景。

共享数据库 + 分布式锁(最常用)

这是对现有 Activiti 代码改动最小的方案,核心思想是数据库是唯一的真理源,但通过外部锁来控制并发访问。

架构图

  • 负载均衡器 -> [Activiti Node 1, Activiti Node 2, Activiti Node N]
  • Activiti Node (共享同一套代码,无状态)
  • 共享数据库 (MySQL/PostgreSQL) - 核心状态
  • 分布式锁 (Redis/Redisson 或 Zookeeper)

需要解决的问题

  1. 流程实例的锁(JPA Optimistic Locking):Activiti内部已有 @Version 注解,对数据库行进行乐观锁,当两个节点同时尝试更新同一个流程实例时,后者会抛出OptimisticLockingException必须捕获这个异常并重试
  2. Job Executor(定时器/异步任务):这是最大的坑,标准Activiti的Job Executor是多线程的,且不区分节点。
    • 解决方案必须关闭除一个节点外的所有节点的Job Executor,或者使用全局锁(如Redis锁 + Quartz风格的调度)。
    • 成熟做法:使用 Activiti 7 + QuartzActiviti 6 + RxJava + Redisson,每个Job在执行前尝试获取分布式锁,获取到的节点执行,获取不到的跳过。

代码/配置示例(基于Activiti 7 + Spring Boot)

// 1. 强制关闭内置JobExecutor,只使用外部调度
@Bean
public ProcessEngineConfiguration processEngineConfiguration(DataSource dataSource) {
    SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
    config.setDataSource(dataSource);
    config.setDatabaseSchemaUpdate("true");
    config.setAsyncExecutorActivate(false); // 关键:关闭内置异步执行器
    // ... 其他配置
    return config;
}
// 2. 使用Redisson分布式锁 + 定时任务(模拟JobExecutor)
@Component
public class DistributedJobExecutor {
    private final RedissonClient redissonClient;
    private final ManagementService managementService; // Activiti的服务
    @Scheduled(fixedDelay = 1000) // 1秒轮询一次
    public void acquireAndExecuteJobs() {
        // 获取所有待执行的ACITIVITI JOB
        List<Job> jobs = managementService.createJobQuery().list();
        for (Job job : jobs) {
            RLock lock = redissonClient.getLock("acv-job:" + job.getId());
            try {
                if (lock.tryLock(10, 10, TimeUnit.SECONDS)) {
                    // 执行业务逻辑
                    managementService.executeJob(job.getId());
                }
            } catch (OptimisticLockingException e) {
                // 重试或跳过
            } finally {
                lock.unlock();
            }
        }
    }
}

消息队列 + 异步解耦(高吞吐推荐)

不是让所有节点都访问数据库,而是通过MQ传递“命令”。

流程

  1. Client -> 发起请求到任何一个节点 -> 节点将意图(如“启动流程X”)发送到MQ (RabbitMQ/Kafka)。
  2. MQ Consumer -> 唯一的“Activiti Worker”节点消费消息。
  3. Activiti Worker -> 操作数据库 -> 状态变更 -> 发送事件(流程完成、任务创建)到另一个MQ。
  4. 业务节点 -> 消费事件 -> 执行业务逻辑(如发送邮件)。

优点:完全避免数据库锁竞争,流程引擎变成了单点消费者(逻辑上单节点,物理上可以水平扩展Consumer Group,但保证同一时间只有一个处理同一个流程实例)。 缺点:异步带来的延迟(毫秒级);需要额外维护MQ。

独立部署 + 远程RPC/HTTP(微服务化)

这是最彻底且有官方支持的方案(Activiti Cloud),将Activiti作为一个独立的流程引擎微服务

架构

[Service A] ---> [Activiti REST API] ---> [Activiti Engine Node 1]  (读)
[Service B] ---> [Activiti REST API] ---> [Activiti Engine Node 2]  (写,但使用读写分离)
                [共享 DB]

官方推荐:Activiti Cloud (基于Spring Cloud)。

  • 组件分离
    • activiti-cloud-services-audit(审计日志)
    • activiti-cloud-services-connectors(连接器)
    • activiti-cloud-runtime-bundle(运行时BPMN引擎)
  • 特点
    • 每个流程应用部署为一个独立的Spring Boot应用(Runtime Bundle)。
    • 通过 RabbitMQKafka 进行事件驱动通信。
    • 原生支持分布式事务(使用Spring Cloud Stream)。

配置示例(Activiti Cloud Runtime Bundle)

# application.yml
spring:
  activiti:
    # 关键配置:使用RabbitMQ消息总线
    cloud:
      application:
        name: my-process-app
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  datasource:
    url: jdbc:postgresql://db-shared:5432/activiti?reWriteBatchedInserts=true
    # 所有Runtime Bundle节点共享这个数据库

方案选择决策树

场景 推荐方案 理由
低TPS ( < 100/s),单区域 共享库+分布式锁 最简单,无需额外MQ,使用Redisson对Job加锁即可。
高TPS ( > 500/s),对一致性要求极高 MQ+异步Worker 避免锁冲突,将并发转列队,写操作只在单个Worker链上。
微服务架构成熟,团队能力较强 Activiti Cloud 原生支持云原生,事件驱动,灰度发布,组件隔离。
仅查询量大,写操作少 方案一 + 只读副本 配置数据源读写分离,读走从库,写走主库。

部署数据库层注意事项

无论哪种方案,数据库是瓶颈。

  • 数据库连接池:每个节点配置hikari.maximum-pool-size为节点数,总连接数不超过数据库上限,4个节点,每个节点池大小15,总连接60。
  • Deadlock处理:Activiti内部获取锁的顺序是固定的(先流程实例锁,后任务锁),在数据库层面调优innodb_deadlock_detect,应用层做好重试(@Retryable)。
  • 表空间优化
    • ACT_RU_*(运行时表)频繁读写,使用RAM磁盘或SSD。
    • ACT_HI_*(历史表)定期归档,使用分区表。
    • ACT_GE_BYTEARRAY(二进制流)单独存放或使用对象存储。
  1. 不要裸写多节点:直接开启多台Activiti完全不加锁,会导致Job重复执行、数据库死锁。
  2. Job Executor是核心:80%的分布式问题源于Job Executor竞争,要么外挂Quartz,要么用Redis锁。
  3. 数据库是基石:使用READ COMMITTED隔离级别,开启innodb_status_output监控死锁。
  4. Activiti 7+ 原生支持:如果从零开始,直接使用Activiti Cloud或Spring Boot + 消息驱动的方式,避免踩旧版本的坑。

根据你的业务规模和对实时性的要求,选择方案一或方案三(微服务化)最为稳妥。

抱歉,评论功能暂时关闭!