关于异步+乐观锁的方案实践
背景介绍:
在在财务审批的时候,遇到需要批量审批的操作,如果不采用异步执行的操作,会出现卡顿,用户使用感受不佳的情况。 并且为了避免一条审批被多个人重复审批导致资金的亏损操作,我在代码中加入的乐观锁的设计思路。
实施思路:
异步执行的设计:
提交异步审批请求的时候,去缓存中存入这个任务,我这里采用的是redis缓存,我这里存入的参数为任务ID、审批数量、完成状态、总条数、成功处理条数、以及待审批编号列表。然后构造一个异步执行器execute,在这里创建异步执行的具体函数,该函数的主要思路就是用for循环,循环执行单条审批的操作,然后执行成功就更新一下redis缓存内容,异常失败内容标记失败。(后续优化,中断的情况下,虽然审批状态已经设置为已审批,但是具体还是未审批的状态,后续需要修改成审批状态加支付状态,得出是否处于异常状态)
乐观锁的设计:
我学过来的乐观锁设计应该是这样的:在修改数据之前,先获取原始数据a,然后执行修改操作,在替换原本数据之前,比对修改对象与获取到的数据a是否相等,如果相等就进行操作,如果不相等就修改失败。 我这里的在数据库中设置的审批状态的变量,所以采用的策略方式就修改为,审批之前先判断审批把这个审批状态修改为审批,这就是抢到的这资源,然后就可以具体执行业务流程,之后cbs审批失败再把审批状态修改成失败。
具体实现:
控制器部分就不讲了,直接开始代码函数部分:
提交任务 submitReviewAuditBatch
校验通过后生成 执行任务taskId, 构造 批量审批任务对象(状态=处理中,计数清零), 经批量审批存储函数 写入 Redis(key 前缀 store:batch:,TTL 24 小时)。 立刻调用 异步执行器.execute(...),不等待,返回 {taskId, taskStatus, totalCount}。
异步执行器 execute
@Async("BatchExecte") 挂在独立 Bean 上,避免同类 this 调用导致异步失效。线程池:核心 2、最大 5、队列 100、拒绝策略 CallerRunsPolicy。执行器只负责切线程,真正干活调用 processReviewAuditBatch,具体审批函数。
批量处理 processReviewAuditBatch
从 Redis 取出任务;for 每个审核 ID 调 reviewOneInBatch;appendBatchResult 后立刻 save。全部结束后 taskStatus=已完成。循环外异常才 taskStatus=失败 并写 errorMessage。单条审核失败只记在该条 resultType 里,不中断整批。
单条入口 reviewOneInBatch → reviewAudit
空 ID、批次内重复 ID 直接记失败。否则组装单条请求调用 reviewAudit,成功则回写简道云「已提交/不通过」;异常则 feedbackReviewCatch,并映射为:不存在 / 已审核 / 校验失败 / 系统异常。
乐观锁(reviewAudit + Mapper)
先查记录,非待审核直接拒绝。 然后 CAS: 不通过:casRejectIfPending,audit_status=2,写入 reviewer_id 通过:casApproveIfPending,audit_status=1,写入 approver_id;抢到后再 executePayment;成功回填支付报文;失败改为状态 3 并抛「审核通过但对外支付失败
轮询 getReviewAuditBatchTask
按 taskId 读 Redis,转成 CbsPaymentAuditBatchTaskStatusVO(含 successList / failList)。任务不存在或过期返回未找到。
