ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

高并发高集成下的BPM流程平台优化与运营复盘

高并发高集成下的BPM流程平台优化与运营复盘 去年一整年我的主要精力都扑在了一家大型地产集团的BPM流程平台上。说实话在接手之前我也觉得BPM不就是一个“审批流引擎”直到集团几十个区域公司、上千个流程模板、日均几十万次流程事件同时跑起来才发现真正的难点从来不是“能不能走到下一步”而是高并发下如何稳得住、高集成下如何不扯皮。这一年我手上的关键词始终是三个BPM、高并发、高集成。这篇文章不打算讲抽象理论而是把我亲历的架构调整、性能调优、集成治理和运营优化完整复盘一遍给同样在做企业流程平台的同行一些参考。1. 项目背景与核心痛点高并发、高集成下的BPM运营优化1.1 大型地产企业的流程平台到底承载了什么在大多数人的印象里BPM就是OA里的请假审批、报销审批好像没什么技术含量。但我接手后才意识到大型地产集团的BPM远比这个复杂。集团的流程平台当时已经积累了1200多个流程模板覆盖投融资、招采、成本、工程、营销、人事、行政、财务等所有条线。它不是简单的“审批意见流转”而是多个业务系统之间真正的业务载体。举个例子工程付款流程从合同系统发起付款申请BPM负责编排审批节点经过项目成本复核、区域财务审核、集团资金计划校验最后触发银企直连系统完成付款。这个流程不能只是“领导点一下同意”就完了每一步都要调用外部系统校准数据。再比如营销定价流程一张定价表从案场发起经过城市公司、区域、集团三层审批审批通过后必须回写销售ERP系统销售端才能继续用这个价格去认购签约。可以这么说BPM在大型地产集团里不是锦上添花的工具而是连接主数据、合同、资金、销售、财务系统的业务中枢。一旦它卡顿整个集团的一线业务都会停摆。所以“高并发、高集成”这两个词对我而言不是PPT上的技术词汇而是每天早上打开监控面板时不得不面对的残酷现实。1.2 高并发不是空话而要落到具体业务节奏地产行业有一个非常明显的特征业务节奏呈现极强的波峰波谷。开盘季、月底付款、年终关账、集中入职、集中采购每一个节点都会让流程平台瞬间承压。我印象最深的场景是开盘季的“认筹转认购”流程。某项目开盘当天2小时内从0涨到2000个流程实例。你以为2000不多问题是这2000个实例要并行发起每个实例还要去销售系统查询房源状态和客户信息并发一上来接口响应就迅速恶化。用户端表现为页面转圈、点击提交无反应、重复点击后生成多条重复流程。后台表现为数据库行锁明显、流程引擎的运行时表持续膨胀、对外部系统的调用超时率飙升。另一个典型场景是年底的预算调整和付款集中审批。财务共享中心的人手是固定的但12月最后一周的待办量能比平时翻三倍。这还不算系统压力更多是业务体验问题同一个审批人同时收到几十条待办每条待办打开都要等好几秒自然会产生大量投诉。高并发不是说服务器CPU有多高而是大量短事务快速堆积把数据库连接池、线程池和外部系统接口全部打满。这时候光看CPU已经看不出问题必须针对业务节奏做预判和削峰。1.3 高集成是大型地产集团的“基础设施”大型地产集团的系统架构用“五花八门”来形容一点不过分。核心财务可能跑在SAP上成本管理可能用明源销售端有自己的CRM人力有单独的eHR再加上主数据平台、预算系统、合同系统、移动办公App少说也有几十个系统。BPM如果想成为真正的流程中枢就必须和这些系统打通。我们的流程平台早年是一个个点对点接口去接的。合同系统调BPMBPM调财务系统财务系统再回调BPM链路一长问题就来了。某个环节因为网络抖动超时整条链路就得重试重试没有幂等控制下游就可能重复入账。后来统计了一下平台对接的外部系统超过15个维护的接口超过500个。高集成带来的问题比高并发更隐蔽因为并发问题至少能通过响应时间感知而集成问题往往等到业务对不上账才发现。高集成的本质是一切都在互相依赖。BPM完成一个审批节点后需要通知下游系统下游系统更新完状态后又要回调BPM推进下一步。这样一个双向依赖的模型如果不在接口规范、数据一致性、异常补偿上下足功夫迟早会出大事故。所以高集成不是研发部门自己的事而是企业整体的“基础设施”规划。1.4 运营优化不是一次性的而是一个年度主题既然标题叫“年度实践”就说明我这一年做的不是一次上线而是一个持续运营的过程。最初我把优化等同于性能调优后来才意识到高并发下的稳定性、高集成下的一致性、流程数量的治理、团队协作的规范全是运营优化的一部分。我给自己定了一个优化框架第一性能维度解决流程引擎执行慢、用户响应慢的问题第二稳定性维度解决接口超时、数据库死锁、消息积压的问题第三一致性维度解决流程和业务系统状态不同步的问题第四可运维维度建立监控告警和问题追溯能力。有了这个框架我才能把一年的工作拆解出优先级而不是每天跟着告警邮件到处救火。如果只把优化理解为“换一个更快的服务器”那一定走不远的。大型企业BPM真正的难点是业务节奏不可控集成依赖不可控流程数量不可控。运营优化的核心就是在这些不可控之间建立工程上的确定性。2. 核心架构决策从流程引擎到集成层的取舍2.1 流程引擎选型为什么选择Flowable并二次开发流程平台要应对高并发底层流程引擎就是地基。我们早期用的是某个商业BPM产品功能很全但二次开发困难而且它在高并发下的表现越来越差。后来集团决定基于开源流程引擎重新搭建。我们认真评估过Activiti、Flowable、Camunda最终选择了Flowable 6/7作为基础版本。选择Flowable的理由有几点第一社区活跃踩坑后能找到大量案例第二API非常丰富支持多实例、并行会签、子流程、异步延续器等高级特性第三它的表设计相对合理运行时表和历史表分离方便做性能扩展第四基于Spring Boot二次开发非常顺手我们可以把流程引擎完全嵌入自己的微服务体系中。但生产环境绝对不能直接用原生引擎。原生Flowable默认把所有操作都当作事务来处理流程启动、节点完成、网关跳转都在一个大事务里。并发高时这个大事务就是灾难。我们做了大量改造引擎运行时表与业务表分库核心操作拆分事务边界启动流程和异步任务解耦。现在回头总结选型只是第一步选完之后怎么做工程改造才是真正拉开差距的地方。2.2 异步化改造怎么把同步阻塞变成事件驱动高并发优化第一件事就是减少流程引擎线程里的阻塞。一开始用户提交流程后后台要同步完成流程实例创建、调用外部系统、生成待办通知。外部系统接口如果两秒才有响应用户就必须盯着页面等两秒。高峰期几千个人同时操作线程池全部卡在等待外部系统响应上。改造思路很明确用户感知的核心动作走同步其他辅助动作全部异步化。具体来说用户点击提交后系统只做必要的参数校验、唯一性检查、流程实例基础数据写入然后立刻返回“提交成功”。后续的完整流程实例创建、外部系统调用、待办通知发送全部发到Kafka由异步消费者去执行。这里有一个很容易踩的坑不能为了异步而异步。如果流程的下一个节点必须依赖外部系统返回结果就不能盲目丢到消息队列里。我们的原则是必须同步校验的业务动作比如预算占用校验、房源状态校验保留同步调用可以稍后执行的通知、数据推送、子流程启动改成异步。另外异步消息必须设计好重试和死信机制否则消息丢失后流程会一直卡在原地业务用户根本不知道发生了什么。2.3 集成层设计API网关、消息队列与事件契约高集成场景下最怕的不是接口多而是调用关系乱成一团。我们最初是点对点直连A系统直接发HTTP请求给B系统B系统再直连C系统。一旦出现故障排查链路非常痛苦。后来引入了统一的API网关所有系统之间的接口调用都通过网关转发不允许直连。这样做的好处是第一所有调用都有日志能快速定位是谁在什么时间调了哪个接口第二网关层可以做统一的鉴权、签名校验、限流和熔断第三接口升级时可以在网关层做灰度路由不用让每个上游系统都改配置。从BPM角度我们不仅把流程引擎封装成服务还把对外能力全部收敛到网关保证流程平台的入口和出口都有监控。消息层面我们定义了统一事件结构所有跨系统事件都包含事件ID、事件类型、payload版本、业务流水号、时间戳。消费方如果遇到不认识的payload版本不能直接解析崩溃而是先把消息丢进死信队列由人工介入。我们还用JSON Schema做事件契约任何字段变更必须先向后兼容。这样即使有某个系统版本落后也不会因为事件结构变化而停止工作。2.4 数据库分库分表与数据归档策略BPM引擎的表很多运行时表ACT_RU_*在流程执行过程中会反复读写是高并发下最容易出现锁竞争的地方。如果流程实例数量持续上涨又不做归档运行库会越来越大查询会越来越慢索引会失效死锁概率也会明显上升。我们做了三件事。第一运行时库和历史库分离流程一旦结束就由定时任务把相关数据迁移到历史库运行库只保留活跃流程数据。第二通过Flyway管理数据库版本每天定时清理已经完成超过30天的流程实例把运行时表的记录数控制在百万量级以内。第三对任务表、待办表按照“租户ID创建时间”建立复合索引确保按用户查询待办的SQL能走索引。分库分表这件事没有想象中那么复杂但也不能不做。对于日均几十万次流程事件的大型企业运行库如果不控制体积连接池再大也没用。数据库是流程平台的最终瓶颈所有调优到最后都会落在SQL和锁上面。所以宁可前期多花点时间做归档和索引设计也不要等生产环境报警了再抢救。3. 高并发调优从线程池到数据库锁的实战记录3.1 线程模型与Java多线程隐患我们的BPM服务是用Java写的高并发环境下Java多线程模型是绕不开的话题。最开始为了提升吞吐量我把流程引擎的执行线程池调得非常大结果适得其反线程一多每个线程都在抢CPU和数据库连接上下文切换开销暴涨数据库连接池直接被占满系统整体吞吐反而下降。后来我用了比较克制的线程池配置核心线程50最大线程200队列容量1000拒绝策略选择CallerRunsPolicy。CallerRunsPolicy的意思是当任务队列满了以后不让任务直接丢弃而是让提交任务的线程自己执行这个任务。虽说会拖慢提交速度但至少保证流程事件不丢这对BPM平台来说比短暂延迟更重要。实际配置如下ExecutorService flowExecutor new ThreadPoolExecutor( 50, 200, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(1000), new NamedThreadFactory(flow-worker-, true), new CallerRunsPolicy() );Java多线程容易踩的坑太多了。比如SimpleDateFormat不是线程安全的我们有一个定时任务因为用了共享的SimpleDateFormat导致生成的日期错乱再比如ThreadLocal用完没有清理在高并发线程池复用下数据串到了下一个用户还有无界队列一旦任务积压内存就持续增长最后直接OOM。这些坑我都踩过现在团队的代码规范里明确要求线程池必须命名队列必须有界异常必须记录ThreadLocal必须用try-finally清理。3.2 数据库连接池、死锁和待办表优化BPM高并发最核心的问题通常不在业务代码而在数据库。我们上线初期经常收到“待办列表打开很慢”的投诉查了半天发现不是接口慢而是待办表太大了SQL没有走索引导致全表扫描。解决方案并不神秘。第一在数据源配置上调优连接池最大连接数150初始连接20获取连接超时5秒避免线程无限等待。第二任务表更新操作都带上乐观锁每次UPDATE都会带上期望的版本号如果版本不对说明有其他线程改过直接放弃更新并做补偿。第三把待办表拆成主表和明细表主表只存“用户ID、流程实例ID、节点ID、到达时间”这种高频查询的信息明细表保存表单数据避免每次查询都去读大字段。当时最典型的一个死锁场景是并行会签一个父流程节点下面有10个子流程同时推进子流程结束时要更新父流程实例的状态结果10个线程同时更新同一行数据数据库行锁竞争非常激烈。我们的解决办法是把“子流程结束回调”放到异步队列中处理而不是让10个线程同步去更新父流程。这个调整看似简单但直接消除了大部分死锁告警。3.3 幂等防重与Redis实践高并发场景下重复提交无法避免。用户在页面卡住时本能反应就是多点击几次提交。我们的流程平台如果没有幂等保护同一个请假单可能会生成三条重复审批流程。还有一个隐患是MQ重投消费者如果处理成功但返回超时MQ就会把同一条消息再投一次。所以我们做了两层幂等。第一层在接入层每次请求必须带业务流水号比如“付款申请单号操作类型”网关先用Redis做setnx如果这个key已经存在直接返回“重复提交请勿重复操作”。第二层在消费端本地数据库中建了一张“事件处理记录表”每条消息的事件ID唯一。消费前先查询是否处理过处理过就直接跳过。这套机制在高并发下非常有效。尤其是流程结束后的外部系统回调最容易因为重试造成重复调用。我们在回调接口里强制要求带上sourceBizId下游系统记录已处理的sourceBizId重复请求直接返回之前的结果。完善幂等之后因重复导致的单据错乱几乎归零了。3.4 压测方法与参数修正做高并发优化不能只看代码必须压测。我们当时的做法是搭建了一套和生产环境一致的压测环境用JMeter编写剧本模拟真实用户的比例30%用户并发发起流程50%用户处理待办20%用户查询流程详情。压测并发从100逐步提高到1000每一档都跑20分钟观察各项指标。压测需要盯几个核心指标TP99响应时间要小于3秒错误率小于0.1%数据库连接池使用率不超过70%Kafka消费积压数不能越来越高。压测中我们发现一个特别典型的性能瓶颈每个审批人打开“待办列表”时系统都会重新统计待办数量人数一多这条统计SQL就成为热点。我们把“待办数量”改成Redis缓存并且把待办详情查询和待办列表接口拆开压测后TP99直接从6.2秒降到了1.8秒。压测的价值不在于测得“吗能扛多少并发”而在于发现哪些组件是真正的瓶颈。我们这一年压测过三次每次都能找出新的问题第一次发现连接池不够第二次发现死锁严重第三次发现某个外部系统接口成了瓶颈。如果不压测这些问题只会在真实业务高峰期爆发到那时排查成本就太高了。4. 高集成实战接口稳定性、数据一致性与补偿机制4.1 统一接口出口网关、签名与错误码高集成场景下BPM每天都在跟一堆外部系统打交道。接口维护量一大如果没有统一的规范和出口排查一个问题可能要从早上查到晚上。我们后来把所有外部接口都收敛到API网关上网关负责鉴权、签名校验、限流、熔断和审计日志。签名算法用的是HMAC-SHA256调用方需要携带publicKey、timestamp、nonce防止重放攻击。每个接口的错误码统一标准化0000表示成功1001表示参数错误1002表示签名错误2001表示系统异常。不要小看这个错误码规范以前没有统一规范时A系统报“ERROR”B系统报“FAIL”这就很难分清是谁的问题。统一之后任何一个调用方拿到的错误码都能直接映射到对应处理逻辑。同时我们要求每个接口必须提供文档字段变更必须向后兼容。这个规矩看着简单执行起来很难。因为业务系统会非常自然地提出“这个字段类型能不能改一下”一旦改掉下游就崩了。我们的回复永远是可以加字段不允许改类型不允许改含义不允许删字段。这是高集成环境下必须守住的底线。4.2 最终一致性本地消息表加定时任务跨系统的流程数据往往做不到强一致。比如流程结束后通知ERP生成凭证如果ERP系统刚好停机BPM总不能把整个流程回滚吧。这种情况下我们采用“本地消息表加定时任务”的模式实现最终一致性。具体步骤是第一BPM在本地库建一张“集成消息流水表”记录目标系统、payload、消息状态。第二业务操作和消息流水写在同一个本地事务里保证业务成功则消息一定落库。第三定时任务每分钟扫描状态为“待发送”的消息调用目标系统接口。第四调用成功后把消息状态改成“已发送”失败则记录错误信息按指数退避策略重试超过5次进入人工告警队列。有人会觉得既然有Kafka为什么还要本地消息表因为Kafka只管消息投递管不了“消息被消费后业务是否成功”。本地消息表的优势在于消息状态和业务状态始终可以对照万一出问题我们可以很清楚地看到哪个系统哪条消息没有处理成功。双写机制虽然有点老但在企业集成场景下可靠性和可追溯性比技术新颖更重要。4.3 流程引擎回写业务系统的幂等控制流程结束后回写业务系统是高集成链路里最容易出错的一环。以付款流程为例BPM审批通过后要通知财务系统“更新付款状态”。如果消息重投了一次财务系统就收到两次同样的指令。如果没有幂等控制财务系统可能重复触发付款这是绝对不允许的。我们的做法是在回写接口的请求参数里强制要求带上“sourceBizId”也就是业务单据的唯一标识。接收方会维护一张已处理流水表每次收到回调先查这个sourceBizId是否处理过处理过就直接返回上次的处理结果不再重复执行业务逻辑。这样即使Kafka重投5次财务系统也只会真正处理第一次请求。这套幂等协议后来被我们用在了所有回写接口上。核心原则只有一条凡是可能重试的请求都必须设计成幂等的。高并发环境下重试是天经地义的不做幂等就是给自己埋雷。4.4 版本升级与灰度发布集成不可怕可怕的是不兼容变更高集成最怕什么最怕一个系统悄悄改了接口格式其他系统全部翻车。有一件事让我印象深刻我们一个核心流程的审批字段从“项目编码”改成了“项目编码分期编码”以为只是加了一个字段结果两个上下游系统无法识别新格式导致当天所有相关流程都出现数据错误。从那以后我们定死了版本发布规矩所有对外接口必须保留版本号比如 /api/v1/approve 和 /api/v2/approve旧版本至少保留一个过渡周期。新接口先灰度发布只切10%流量观察1到2小时确认无问题再逐步放量。每个接口发布前还要做一次新旧版本数据对比测试确保返回结果一致。高集成环境下最大的风险不是新功能做不出来而是改动引发的连锁反应。灰度发布等于给系统加了一道保险虽然流程繁琐一点但相比生产事故的代价这点繁琐完全可以接受。5. 年度运营复盘从救火队员到流程治理5.1 监控技术指标和业务指标分开看运营优化这一年我最深的体会是监控绝不能只盯技术指标。以前我们盯着CPU、内存、磁盘以为这些指标正常系统就正常。但是经常出现的情况是技术指标一切正常业务用户却已经骂声一片。原因很简单很多瓶颈不在服务器资源而在数据库锁和外部系统依赖。后来我们建立了双层监控体系。技术层面监控QPS、TP99、错误率、Kafka消费积压、数据库连接池使用率、锁等待时间、GC暂停业务层面监控流程启动数、待办平均处理时长、节点超时率、流程驳回率、集成接口调用成功率、人工处理最多的节点Top10。只有把这两层指标结合起来才能既看到系统状态也看到业务状态。下面是一张当时告警配置的简化表可以参考指标类别指标名称告警阈值技术流程引擎线程池活跃线程数超过80%持续5分钟技术数据库锁等待时间超过200毫秒技术Kafka消费延迟积压超过1000条业务待办超时率超过20%业务集成接口调用失败率超过1%这套监控体系最大的作用不是等到故障发生再报警而是帮助我们把很多潜在问题提前消灭。比如待办超时率连续几天升高我们就能反推出某个节点的处理效率有问题提前和业务部门沟通流程优化而不是等用户投诉了才去查。5.2 一次真实故障复盘夜间全量同步引发的接口风暴这里记录一次真实的故障。有一天晚上10点ERP系统发起了“全量组织架构同步”几千个部门、几万个人事数据被一次性推到BPM。BPM在接收数据时每解析一条记录就去调用主数据系统获取辅助属性结果主数据系统被这波并发打挂BPM的集成线程池也被占满。第二天早上所有人上班后正常流程都阻塞了用户打电话抱怨系统瘫痪。复盘后发现了三个问题第一全量同步任务没有限流一股脑全部塞了进来第二BPM在接收入口是直接同步解析和调用下游没有走异步第三集成线程池和流程引擎线程池共用一套集成侧把执行线程吃光了导致核心流程无法处理。修复方案也很有针对性全量同步任务改成专用的消息topic拆分成多个批次每批100条逐批处理解析结果先写入临时表再由独立任务异步写入运行时表集成线程池和流程引擎线程池彻底物理隔离。这个案例告诉我们高集成场景下的“并发”和互联网高并发不太一样它往往来自一个不起眼的定时任务却能把整个链路拖垮。5.3 流程治理的几项硬约束BPM平台运营久了流程数量会野蛮生长。如果不做治理每多一个流程模板就多一份维护成本和集成风险。我们这一年最后落地了几条硬性约束第一所有新流程必须经过评审阶段性的“一次性流程”尽量不建第二核心流程禁止在流程内部写复杂计算逻辑金额测算、时间计算必须放在接入层或由业务系统完成第三并行网关的分支数不超过5路避免开启太多子流程导致系统并发失控第四流程版本不允许直接替换必须走新版本灰度第五超过6个月没有实例的历史流程版本统一归档。这些约束刚开始业务部门不理解觉得IT管得太多。但运行半年后流程卡顿和接口问题明显减少业务部门才反过来认可。流程平台运营优化做到最后不只是在改代码而是在建立一套所有人共同遵守的规则。我自己这一年最大的体会是BPM在高并发、高集成环境下的运营优化不是凭空堆技术而是要对业务节奏有感知对集成链路有敬畏对流程资产有治理。先把连接池和线程池配好把幂等和补偿做全把监控告警建起来把流程标准化立起来比盲目引入一个更重的中间件有效得多。如果现在让我重来一次我不会先动手写代码而是先把业务发生频率最高的前十个流程拉出来跑一遍压测找到真正被拖垮的那一环。BPM运营不是一个“做完的项目”而是长期运营的科目。这就是我这一年最朴素的经验。
返回列表