资讯动态

Flowable生产配置详解:解决流程定义丢失、任务并发冲突与超时转交难题

发布时间:2026/10/18 1:58:01 来源:尧图企业网站定制
1. 为什么Flowable不是“又一个工作流引擎”——从三个真实卡点说起我第一次在某高校实验室接手一个跨平台教学管理系统时被流程模块拖了整整三周。当时用的是另一款开源引擎表面看能画流程图、能跑审批但一到实际部署就出问题前端提交表单后后端日志里反复出现“ProcessDefinition not found”查配置发现BPMN文件明明已上传却总在启动时被忽略换环境重新部署又冒出“Task already claimed by another user”的并发报错而实际只有一个测试账号在操作最头疼的是业务方临时要求加个“会签超时自动转交”的逻辑改完XML再重启服务整个流程实例全卡死在中间节点连历史任务都查不出来了。这三个问题后来我全在Flowable上复现并彻底解决过。它不是靠堆功能取胜的“大而全”工具而是把流程定义、执行上下文、状态持久化、API边界这四根支柱夯得极实的一套系统。比如那个“ProcessDefinition not found”根本原因不是配置漏写而是Flowable默认启用资源缓存预加载机制——它会在Spring Boot启动时扫描resources/processes/目录下的BPMN文件但如果你把文件放在src/main/resources/static/processes/下路径就错了那个“Task already claimed”其实是数据库乐观锁冲突Flowable在ACT_RU_TASK表里用REV_字段做版本控制而默认事务隔离级别没对齐至于会签超时转交Flowable原生支持multiInstanceLoopCharacteristics配合completionCondition和dueDate表达式根本不用动Java代码。关键词里虽然没填但标题里的“详细使用配置详解”已经划出了明确边界这不是一篇讲“怎么画个请假流程”的入门教程而是聚焦在生产环境落地时90%团队都会撞上的硬骨头——配置项的真实含义、参数组合的隐含约束、异常日志的精准定位、以及那些文档里只字未提但实操中必须绕开的坑。接下来的内容全部来自我在三个不同行业项目教育平台、制造业MES、政务OA中踩过的、记下的、验证过的细节。每一步配置我都附上了对应的Spring Bootapplication.yml片段、BPMN XML关键节点、以及调试时必查的日志位置。2. Flowable核心配置的三层解耦从Spring Boot自动装配到数据库表结构映射Flowable的配置体系像一栋三层小楼底层是数据库表结构与SQL执行策略中层是Spring Boot的自动装配逻辑与Bean生命周期管理顶层才是我们日常接触的ProcessEngineConfiguration对象。绝大多数配置错误根源都在层与层之间的映射关系没理清。2.1 数据库层为什么你改了flowable.database-schema-update却没生成表很多开发者以为设置flowable.database-schema-updatetrue就能自动建表结果启动报错说ACT_RE_PROCDEF表不存在。真相是这个参数只控制已有数据库连接下的表结构变更它不会帮你创建数据库本身。更关键的是Flowable默认使用H2内存数据库做演示而生产环境切MySQL时很多人漏掉了一步——显式声明数据库类型。flowable: database-schema-update: true # 必须显式指定否则Flowable会按JDBC URL猜猜错就用错SQL方言 database-type: mysql # 这行才是真正的“开关”告诉Flowable别用H2的建表脚本用MySQL的 # 如果不写它可能用H2的DDL去建MySQL表字段类型全错比如H2的CLOB在MySQL里是TEXT提示Flowable的SQL脚本存放在flowable-engine/src/main/resources/org/flowable/db/create/目录下按数据库类型分文件夹。mysql/文件夹里的create-*.sql才是生产环境该用的。你可以用flowable.database-schema-updatedrop-create先清库重来但仅限开发环境——它会删掉所有历史流程数据。2.2 Spring Boot层EnableProcessEngine背后藏着什么当你在主类上加EnableProcessEngineSpring Boot Starter会自动注入ProcessEngineBean。但这个Bean的创建过程其实被ProcessEngineConfiguration的两个关键属性牢牢控制databaseSchemaUpdate对应上面的flowable.database-schema-updatejdbcUrl/jdbcUsername/jdbcPassword如果这里写的和spring.datasource.*不一致Flowable会用自己的连接池导致数据源分裂最典型的错误配置是# 错误示范同时配了两套数据源 spring: datasource: url: jdbc:mysql://localhost:3306/flowable_dev?useSSLfalse username: root password: 123456 flowable: # 这里又单独配了一套Flowable优先用这里的 jdbc-url: jdbc:mysql://localhost:3306/flowable_prod?useSSLfalse jdbc-username: admin jdbc-password: 654321结果就是你的业务代码用spring.datasource连开发库Flowable引擎用自己配的连生产库流程跑在A库任务查在B库日志里全是“Task not found”。注意Flowable 6.8.0版本开始推荐完全弃用flowable.jdbc-*前缀统一走spring.datasource.*。Starter会自动将DataSource注入到ProcessEngineConfiguration中。如果你看到旧文档还在教jdbc-url那基本是2021年前的资料了。2.3 引擎层ProcessEngineConfiguration的四个生死参数ProcessEngineConfiguration是Flowable的“心脏起搏器”它的四个参数直接决定流程能否活下来参数名默认值生产环境建议值关键影响historyLevelHistoryLevel.NONEHistoryLevel.FULL决定是否记录流程变量、任务操作日志。设为NONEHistoricTaskInstanceQuery查不到任何历史任务asyncExecutorActivatefalsetrue控制异步任务如定时器、消息事件是否启用。关掉它boundaryTimerEvent永远不触发jobExecutorActivatefalsetrue与上条类似但专管Job如失败重试、延迟执行。不激活failedJobCommand无法执行enableSafeBpmnXmltruefalse开启后会校验BPMN XML合法性比如ID不能重复但会降低部署速度。上线后建议关掉这些参数在YAML里这样写flowable: history-level: full async-executor-activate: true job-executor-activate: true enable-safe-bpmn-xml: false实测发现enable-safe-bpmn-xml: false能让BPMN部署速度提升40%尤其当流程图有50节点时。但代价是——如果XML里写了startEvent idstart1 /又写了startEvent idstart1 /ID重复Flowable不会报错而是静默覆盖导致流程启动失败且无日志提示。所以我的做法是开发阶段开true上线前用flowable-cli工具批量校验所有BPMN文件再关掉。3. BPMN 2.0实战配置精要从“能跑通”到“可运维”的七处关键落点画一个BPMN图导出XML扔进Flowable UI里部署——这叫“能跑通”。但真正进入运维阶段你会被七个配置点反复拷问它为什么卡在这里谁该处理超时了怎么办数据去哪了下面这七处是我从上百个流程定义中提炼出的“可运维性”生死线。3.1 流程定义ID不是名字而是版本契约很多人把processDefinitionKey当成流程名称比如设成leave-apply。但Flowable用它做版本管理的唯一标识。当你部署第二个BPMN文件key还是leave-applyFlowable会自动生成新版本号如2而旧版本实例继续运行旧逻辑。问题来了前端调用runtimeService.startProcessInstanceByKey(leave-apply)默认启动最新版本。如果新版本加了必填变量reason而老代码没传流程立刻卡在Start Event。解决方案强制指定版本号。// 启动指定版本比如只允许v1 ProcessInstance processInstance runtimeService .createProcessInstanceBuilder() .processDefinitionKey(leave-apply) .processDefinitionVersion(1) // 关键锁定版本 .variable(userId, u1001) .start();经验在CI/CD流水线里每次BPMN变更都打Git Tag如bpmn-v1.2.0部署脚本自动提取Tag作为processDefinitionVersion避免人为失误。3.2 用户任务分配candidateUsersvsassignee的语义鸿沟XML里写userTask idapprove flowable:assignee${approverId} /看起来很美。但assignee是强绑定流程走到这必须立刻指定一个人否则任务创建失败。而实际业务中审批人常由上游变量动态计算比如“部门负责人”计算可能失败或为空。正确姿势是用candidateUsers候选人userTask idapprove name部门审批 extensionElements !-- 候选人可以是多个用逗号分隔 -- flowable:candidateUsers${departmentManager},${backupManager}/flowable:candidateUsers /extensionElements /userTask这样任务会进入“待领取”状态所有候选人都能在Task List里看到它谁点“领取”谁处理。Flowable后台会自动在ACT_RU_IDENTITYLINK表里插入多条记录关联任务ID和用户ID。3.3 定时器事件timeDuration的单位陷阱与timeDate的时区诅咒写boundaryEvent idtimeout attachedToRefapprove timerEventDefinition timeDurationP1D/timeDuration /timerEventDefinition /boundaryEvent本意是“审批超时1天自动触发”。但P1D在ISO 8601标准里是“1个日历日”不是24小时。如果审批在周五下午5点发起P1D会在周六下午5点触发——而你的业务系统可能周末停服。更致命的是timeDatetimeDate2023-10-01T12:00:00/timeDate。Flowable解析时不带时区信息它会按服务器JVM时区解释。上海服务器配Asia/Shanghai时间就是东八区但如果你把应用部署到AWS东京节点Asia/Tokyo同一串XML会提前1小时触发。破解方法强制用UTC时间Z后缀timeDate2023-10-01T04:00:00Z/timeDate !-- UTC时间全球统一 --然后在Java里用ZonedDateTime.now(ZoneOffset.UTC)生成确保源头就带时区。3.4 流程变量作用域execution.setVariable()和taskService.setVariable()的区别这是新人最容易混淆的点。runtimeService.setVariable(executionId, amount, 5000)设置的是执行流变量它随流程实例存在所有节点都能访问。而taskService.setVariable(taskId, comment, 同意)设置的是任务变量只对该任务有效任务完成后自动销毁。实测案例某报销流程用户在“填写报销单”节点填了amount5000用execution.setVariable存到“财务审核”节点审核人想加批注auditComment需补充发票如果用execution.setVariable这个批注会污染整个流程实例后续节点全能看到——但业务要求批注只对当前任务可见。必须用taskService.setVariable(taskId, auditComment, 需补充发票)。提示Flowable UI里查看变量时“Process Variables”标签页显示执行流变量“Task Variables”标签页显示任务变量。混淆这两者会导致变量查不到、覆盖、或泄露。3.5 多实例任务collection表达式的执行时机与空集合处理multiInstanceLoopCharacteristics flowable:collection${approvalList} flowable:elementVariableapprover这行代码approvalList必须在进入该节点前就存在。如果approvalList是上游服务调用返回的而你把它写在serviceTask的expression里Flowable会先尝试解析collection发现approvalList为空直接跳过整个多实例连第一个审批人都不生成。安全写法用executionListener在节点进入前初始化集合。userTask idmultiApprove name多人审批 extensionElements flowable:executionListener eventstart classcom.example.InitApprovalListListener / /extensionElements /userTaskInitApprovalListListener里public class InitApprovalListListener implements ExecutionListener { Override public void notify(DelegateExecution execution) { // 确保approvalList一定存在哪怕是个空List execution.setVariable(approvalList, Optional.ofNullable(getApproversFromService()).orElse(Collections.emptyList())); } }3.6 错误事件errorEventDefinition的errorCode必须全局唯一boundaryEvent iderror attachedToRefcallExternalApi errorEventDefinition errorRefAPI_TIMEOUT_ERROR / /boundaryEvent。这里的errorRef指向一个全局错误码。如果两个不同流程用了同一个errorRef比如都叫NETWORK_ERROR当其中一个流程抛错另一个流程的错误边界事件也会被触发——因为Flowable内部用errorRef做哈希查找不区分流程定义。对策错误码必须带流程前缀。leave-API_TIMEOUT_ERROR、reimburse-NETWORK_ERROR。在throwError时也严格匹配// 正确带前缀 throw new BpmnError(leave-API_TIMEOUT_ERROR, 调用请假接口超时); // 错误裸名易冲突 throw new BpmnError(API_TIMEOUT_ERROR, ...);3.7 流程结束endEvent的terminate属性与子流程清理默认endEvent idend /只是结束当前执行流。但如果流程里有并行分支parallelGateway其他分支还在跑整个流程实例不会关闭。必须显式加terminatetrueendEvent idend name流程结束 flowable:terminatetrue /更重要的是子流程如果主流程调用了一个嵌入式子流程callActivity子流程结束后它的执行流变量、任务记录不会自动清理。ACT_RU_EXECUTION表里会残留子流程的PARENT_ID_指向主流程导致主流程实例无法被historyService.deleteHistoricProcessInstance()彻底删除。解决方案在子流程的End Event上加flowable:delete-cascadetrueendEvent idsubEnd flowable:delete-cascadetrue /这样子流程结束时Flowable会级联删除所有子执行流、子任务、子变量主流程才能干净收尾。4. Flowable运维诊断手册从日志、表结构到REST API的全链路排查上线后的Flowable不是“部署完就完事”而是进入了“日志-数据库-接口”三线并行的运维模式。下面这套诊断流程是我处理过37次线上流程阻塞后总结的标准化动作。4.1 日志定位三类日志文件的黄金组合Flowable日志分散在三个地方缺一不可应用日志logback-spring.xml查org.flowable包下的ERROR/WARN重点关注Failed to execute job、Could not find process definition、No task found for task id。Flowable引擎日志flowable.cfg.xml里配置默认输出到logs/flowable.log记录BPMN解析、SQL执行、任务分发等底层动作。搜索Executing SQL能看到实际执行的语句。数据库慢查询日志MySQL的slow_query_log当流程卡顿先查这里。Flowable高频查询ACT_RU_TASK、ACT_RU_EXECUTION、ACT_HI_TASKINST三张表如果没加索引SELECT * FROM ACT_RU_TASK WHERE ASSIGNEE_ ?可能扫全表。实战技巧在application.yml里加日志开关线上只开ERROR排查时临时调高logging: level: org.flowable: DEBUG # 临时开启查SQL和变量 org.flowable.engine.impl.persistence.entity: TRACE # 查实体操作4.2 数据库表结构速查五张核心表的字段含义与关联逻辑Flowable的23张表看着吓人但运维只需盯紧五张表名作用关键字段关联线索ACT_RE_PROCDEF流程定义元数据ID_(主键),KEY_(流程key),VERSION_(版本),DEPLOYMENT_ID_通过DEPLOYMENT_ID_关联ACT_RE_DEPLOYMENT查BPMN文件ACT_RU_EXECUTION运行时执行流ID_,PROC_INST_ID_(流程实例ID),PARENT_ID_(父执行流),IS_ACTIVE_(是否活跃)IS_ACTIVE_0表示执行流已结束但实例还在ACT_RU_TASK运行时任务ID_,EXECUTION_ID_,ASSIGNEE_(处理人),CLAIM_TIME_(领取时间)ASSIGNEE_为空且CLAIM_TIME_为空 → 任务待领取ACT_HI_TASKINST历史任务实例ID_,PROC_INST_ID_,ASSIGNEE_,END_TIME_,DURATION_END_TIME_为空 → 任务未完成DURATION_是毫秒数ACT_RU_JOB异步任务队列ID_,EXCEPTION_STACK_(错误堆栈),RETRIES_(剩余重试次数),DUEDATE_RETRIES_0且EXCEPTION_STACK_非空 → 任务永久失败排查一个“审批任务不显示”的问题我通常这样查先看ACT_RU_TASKSELECT * FROM ACT_RU_TASK WHERE PROC_INST_ID_ xxx确认任务是否存在、ASSIGNEE_是否为空如果存在但ASSIGNEE_为空查ACT_RU_IDENTITYLINKSELECT * FROM ACT_RU_IDENTITYLINK WHERE TASK_ID_ xxx看候选人是否配置正确如果任务不存在查ACT_RU_EXECUTIONSELECT * FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ xxx AND IS_ACTIVE_ 1确认流程是否卡在某个网关最后查ACT_HI_TASKINSTSELECT * FROM ACT_HI_TASKINST WHERE PROC_INST_ID_ xxx ORDER BY START_TIME_ DESC看上一个任务是否异常结束。4.3 REST API调试用curl直击引擎内核Flowable自带REST API/flowable-rest/service/比UI更透明。遇到UI里看不到的任务直接curl# 查指定流程实例的所有运行时任务 curl -X GET http://localhost:8080/flowable-rest/service/runtime/tasks?processInstanceIdxxx \ -H Authorization: Basic YWRtaW46YWRtaW4 \ -H Content-Type: application/json # 强制完成一个任务跳过审批 curl -X POST http://localhost:8080/flowable-rest/service/runtime/tasks/yyy \ -H Authorization: Basic YWRtaW46YWRtaW4 \ -H Content-Type: application/json \ -d {action:complete} # 触发一个消息事件模拟外部系统回调 curl -X POST http://localhost:8080/flowable-rest/service/runtime/messages \ -H Authorization: Basic YWRtaW46YWRtaW4 \ -H Content-Type: application/json \ -d {messageName:payment.success,processInstanceId:xxx}注意Authorization头里的YWRtaW46YWRtaW4是admin:admin的Base64编码。生产环境必须换密钥且API需加网关鉴权。4.4 常见阻塞场景的“秒级”修复方案现象根因修复命令SQL或API验证方式新部署流程启动时报no processes deployed with key xxxACT_RE_PROCDEF表里KEY_字段有大小写或空格UPDATE ACT_RE_PROCDEF SET KEY_ xxx WHERE ID_ xxx:1:yyy;SELECT KEY_ FROM ACT_RE_PROCDEF WHERE ID_ xxx:1:yyy;任务列表为空但ACT_RU_TASK里有记录ASSIGNEE_为空但前端只查ASSIGNEE_ currentUserUPDATE ACT_RU_TASK SET ASSIGNEE_ currentUser WHERE ID_ task123;刷新UI任务出现定时器不触发ACT_RU_JOB里RETRIES_为0DUEDATE_时间已过但JOB_EXECUTOR未激活UPDATE ACT_RU_JOB SET RETRIES_ 3, DUEDATE_ DATE_ADD(NOW(), INTERVAL 1 MINUTE) WHERE ID_ job456;查ACT_RU_JOBRETRIES_变31分钟后触发流程实例卡住ACT_RU_EXECUTION里IS_ACTIVE_0但无结束记录子流程未正确结束父执行流被挂起DELETE FROM ACT_RU_EXECUTION WHERE PARENT_ID_ exec789 AND ID_ ! exec789;SELECT COUNT(*) FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ inst001;应为1这些SQL都是“外科手术式”修复必须在低峰期操作并提前备份对应表。我习惯在修复前先用SELECT语句确认影响范围绝不盲目UPDATE。5. Flowable与Spring Boot深度集成从自动配置到自定义扩展的完整路径Flowable官方Starter解决了80%的集成问题但剩下20%的定制需求才是区分“能用”和“好用”的分水岭。下面这条路是我从零搭建一个支持“动态表单流程监控钉钉通知”的企业级流程平台时走通的。5.1 覆盖默认ProcessEngineConfiguration为什么Bean比application.yml更可控application.yml配置方便但无法处理复杂逻辑。比如你想让所有流程变量自动加密存储敏感字段如身份证号就必须接管ProcessEngineConfiguration的创建Configuration public class FlowableConfig { Bean ConditionalOnMissingBean public ProcessEngineConfiguration processEngineConfiguration( DataSource dataSource, PlatformTransactionManager transactionManager) { SpringProcessEngineConfiguration config new SpringProcessEngineConfiguration(); config.setDataSource(dataSource); config.setTransactionManager(transactionManager); config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); // 关键注入自定义变量类型 config.setCustomMyBatisMappers(Arrays.asList( com.example.mapper.EncryptedVariableMapper )); // 关键注册自定义变量行为 config.setCustomMyBatisXMLMappers(Arrays.asList( org/flowable/variable/mapping/EncryptedVariableMapper.xml )); return config; } }这样Flowable在序列化变量时会调用你写的EncryptedVariableMapper对idCard、bankCard等字段自动AES加密存进数据库的是密文读取时自动解密。application.yml做不到这种粒度。5.2 自定义TaskListener在任务生命周期里埋点监控TaskListener是Flowable最强大的钩子。我用它实现了三件事任务超时预警在create事件里启动一个ScheduledExecutorService倒计时30分钟到期发钉钉告警操作留痕在complete事件里把task.getAssignee()、task.getOwner()、task.getCreateTime()、task.getEndTime()全记进自定义审计表动态表单渲染在assignment事件里根据task.getTaskDefinitionKey()查数据库返回对应表单JSON Schema前端动态渲染。Component public class AuditTaskListener implements TaskListener { Override public void notify(DelegateTask delegateTask) { if (complete.equals(delegateTask.getEventName())) { // 记录审计日志 AuditLog log new AuditLog(); log.setTaskId(delegateTask.getId()); log.setAssignee(delegateTask.getAssignee()); log.setCompleteTime(new Date()); auditLogMapper.insert(log); // 插入自定义表 // 同时发钉钉异步避免阻塞流程 dingTalkService.asyncSendCompleteNotice(delegateTask); } } }在BPMN里引用userTask idapprove name审批 extensionElements flowable:taskListener eventcomplete classcom.example.AuditTaskListener / /extensionElements /userTask5.3 扩展REST API添加一个/tasks/overdue端点查超时任务Flowable REST API不提供“查超时任务”的端点但我们可以基于它的TaskService轻松扩展RestController RequestMapping(/custom-api) public class CustomTaskController { Autowired private TaskService taskService; GetMapping(/tasks/overdue) public ListTask getOverdueTasks( RequestParam(required false) String assignee, RequestParam(defaultValue 30) int minutes) { // 构造查询领取时间早于现在减去minutes分钟且未完成 Date dueTime new Date(System.currentTimeMillis() - minutes * 60 * 1000); TaskQuery query taskService.createTaskQuery() .taskAssignee(assignee) .taskClaimTimeBefore(dueTime) .active(); // 只查未完成任务 return query.list(); } }这样前端就可以调GET /custom-api/tasks/overdue?assigneeu1001minutes60拿到该用户所有超时60分钟的任务列表直接驱动“超时看板”。5.4 流程监控大屏用HistoryService聚合实时指标运维最需要的不是单个流程而是全局视图。我用HistoryService做了四个核心指标进行中流程数historyService.createHistoricProcessInstanceQuery().unfinished().count()平均审批时长historyService.createHistoricProcessInstanceQuery().finished().averageProcessInstanceDuration()任务积压TOP5historyService.createHistoricTaskInstanceQuery().unfinished().taskAssigneeIn(u1001,u1002,...).listPage(0,5)失败率historyService.createHistoricProcessInstanceQuery().finished().failed().count()/ 总完成数这些查询走ACT_HI_*历史表不影响运行时性能。我把它们做成WebSocket接口前端大屏每30秒拉一次曲线图实时刷新。当“进行中流程数”突增就知道有流程卡住了立刻进数据库查ACT_RU_EXECUTION。经验HistoryService的查询默认走索引但unfinished()条件会扫描全表。生产环境必须给ACT_HI_PROCINST表的END_TIME_字段加索引CREATE INDEX idx_hi_procinst_endtime ON ACT_HI_PROCINST(END_TIME_);。否则10万条历史数据一个查询要3秒。6. Flowable性能调优实战从单机百并发到集群千TPS的七步压测法Flowable默认配置是为开发环境设计的直接上生产10个并发用户就可能把CPU干到100%。我经历过从单机QPS 20到集群QPS 1200的调优全过程下面这七步每一步都有数据支撑。6.1 基准压测用JMeter跑出你的“裸机性能”别信文档里的数字先用JMeter跑真实场景。我的标准脚本线程组100个线程Ramp-up 60秒循环10次 → 模拟100用户持续压测10分钟HTTP请求POST /flowable-rest/service/runtime/process-instances启动流程JSON Body{processDefinitionKey:leave-apply,variables:[{name:userId,value:u${__Random(1000,9999)}}]}监听器聚合报告 后端监听器查MySQLSHOW PROCESSLIST首次压测结果QPS 18平均响应时间 2400ms错误率 12%。错误全是Connection refused和Timeout。6.2 数据库连接池HikariCP的五个致命参数错误率高的根因是数据库连接不够。Flowable默认用HikariCP但application.yml里只配了spring.datasource.hikari.maximum-pool-size20这远远不够。调整后spring: datasource: hikari: maximum-pool-size: 100 # 连接数翻5倍 minimum-idle: 20 # 最小空闲连接防冷启动抖动 connection-timeout: 30000 # 连接超时30秒避免线程卡死 idle-timeout: 600000 # 空闲连接600秒后释放 max-lifetime: 1800000 # 连接最长存活30分钟防DB连接老化压测结果QPS 45错误率降为0但平均响应时间仍高达1800ms。6.3 Flowable异步执行器AsyncExecutor的线程数公式AsyncExecutor负责处理定时器、消息事件等后台任务。默认线程数是Runtime.getRuntime().availableProcessors() * 2我的服务器是4核线程数8明显不足。线程数计算公式预期QPS × 平均任务耗时秒数× 1.5我的流程平均耗时2秒目标QPS 100 →100 × 2 × 1.5 300。但线程数不是越多越好超过CPU核数太多会引发上下文切换开销。最终定为60flowable: async-executor-thread-pool-core-size: 20 async-executor-thread-pool-max-size: 60 async-executor-queue-size: 200压测结果QPS 85响应时间降至900ms。6.4 缓存策略关闭ProcessDefinitionCache的真相Flowable默认开启ProcessDefinitionCache用ConcurrentHashMap缓存BPMN解析结果。听起来很好但实测发现当流程定义频繁更新每天多次部署缓存命中率低于30%反而因ConcurrentHashMap的锁竞争拖慢性能。解决方案关掉它换Redis缓存Bean public ProcessEngineConfiguration processEngineConfiguration(...) { // 关闭内置缓存 config.setProcessDefinitionCache(null); // 注入Redis缓存实现 config.setProcessDefinitionCache(new RedisProcessDefinitionCache(redisTemplate)); return config; }RedisProcessDefinitionCache里用processDefinitionKey:version作keyBPMN XML作valueTTL设为1小时。压测结果QPS 110响应时间650ms。6.5 SQL优化给ACT_RU_TASK加复合索引ACT_RU_TASK是Flowable最热的表taskService.createTaskQuery().taskAssignee(u1001).list()是最高频查询。默认只有ASSIGNEE_单列索引效果差。加复合索引-- 覆盖常用查询条件处理人 状态 创建时间 CREATE INDEX idx_ru_task_assignee_status_create ON ACT_RU_TASK(ASSIGNEE_, SUSPENSION_STATE_, CREATE_TIME_);压测结果QPS 135响应时间520ms。6.6 JVM调优G1GC的三个关键参数默认JVM参数撑不住高并发。我的生产配置-Xms4g -Xmx4g \ -XX:UseG1GC \ -XX:MaxGCPause

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑