导读:本期聚焦于小伙伴创作的《如何用异步通知与等待机制解决人工介入导致的流程延迟问题》,敬请观看详情。把人工审批挂进自动化流水线时,最头疼的是线程干等把资源占满。直接让主流程阻塞等待工单结果,并发一高服务就假死。正确做法是用消息队列发异步通知,再配合带超时的等待原语挂起任务上下文。比如用数据库状态字段加定时轮询,或借助Redis过期事件回调唤醒。这样人工处理慢也不会拖垮系统,还能在超时后自动流转到兜底分支。本文从原理到代码讲清怎么落地这套机制。

在后台业务系统里,很多核心链路必须留出人工审核、复核或确认的环节。一旦把人工介入直接写进同步调用里,请求线程就会一直卡住,直到人点完按钮。人多的时候,线程池被占满,正常自动任务也进不来。异步通知与等待的思路是把“等人”这件事从主干移除,让流程先挂起,等人处理完再被通知唤醒,或者超时后走默认路径。

如何用异步通知与等待机制解决人工介入导致的流程延迟问题

为什么同步等待人工必定拖垮系统

最直观的写法是在接口里调用一个审核方法,里面循环查询数据库里的审核状态,直到变成通过或拒绝。这种做法在测试环境只有一个用户时没问题,但上线后并发一来就暴露出致命缺陷。每个挂起的请求都占用一个容器线程,而人工审核可能要几分钟甚至几小时,线程长时间不能释放,连接池和线程池迅速耗尽。

除了资源占用,同步等待还让超时和兜底变得极难处理。如果用户在两天后才处理,当初的HTTP连接早断开,前端也不可能真等这么久。业务上又要求“超时自动拒绝”或“超时升级给主管”,这些逻辑塞进一个阻塞循环里既丑陋又容易出bug。本质问题是:人工响应时间和机器响应时间差了几个数量级,却用了同一套阻塞模型。

我们用一段典型的问题代码说明。下面这段Java伪代码把线程睡在循环里,生产环境绝对不能用。

// 错误示例:同步阻塞等待人工审核
public ReviewResult waitManualReview(long orderId) {
    while (true) {
        Status s = dao.queryStatus(orderId);
        if (s == Status.PASS) {
            return ReviewResult.PASS;
        }
        if (s == Status.REJECT) {
            return ReviewResult.REJECT;
        }
        try {
            Thread.sleep(5000); // 每5秒查一次,线程一直被占着
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

异步通知与等待的核心实现模式

可行的方案是把“任务”和“线程”解耦。流程执行到人工节点时,把当前上下文落地到数据库或Redis,标记状态为WAITING,然后直接返回给调用方一个受理号。人工在后台操作页面点击通过后,后端接口修改状态,并往消息队列发一条通知,或者触发一个回调接口。另一边,等待方可以是定时任务轮询,也可以是被消息唤醒的消费者。

如果用数据库状态加定时轮询,结构非常清晰。一张process_instance表有status、update_time字段。人工页面提交后更新status=PASS。一个每十秒跑一次的调度器扫出status=WAITING且超过超时时间的实例,按规则流转。没超时的继续等。这种办法缺点是有最多十秒延迟,但对多数业务够用,且不需要引入MQ。

更及时的作法是配合消息队列。人工操作接口里发一个事件到topic,消费者收到后根据业务ID把等待中的流程唤醒继续执行。下面用Python演示一个基于Redis键值与过期回调思路的简化版等待逻辑,真实项目里可把redis换成RabbitMQ。

import redis
import time

r = redis.Redis(host='127.0.0.1', port=6379, db=0)

def suspend_task(task_id, timeout=3600):
    # 把任务设为等待,并设置过期时间作为超时兜底
    r.set(f'task:{task_id}', 'WAITING', ex=timeout)
    print(f'任务 {task_id} 已挂起,等待人工或超时')

def manual_finish(task_id, result):
    # 人工处理完,写结果并删掉等待标记
    r.set(f'result:{task_id}', result)
    r.delete(f'task:{task_id}')
    # 实际可在此发布消息通知消费者
    print(f'人工已处理 {task_id},结果 {result}')

def wait_with_notify(task_id):
    while True:
        if not r.exists(f'task:{task_id}'):
            val = r.get(f'result:{task_id}')
            if val:
                return val.decode()
            else:
                return 'TIMEOUT_DEFAULT' # 超时后被清理,走默认
        time.sleep(2)

超时兜底与状态一致性保障

人工介入最怕的是“没人管”。所以等待机制必须带超时。超时后系统不能 silently 卡死,而要自动流转到拒绝、升级或告警。上面Redis的ex参数就是最简单兜底:键消失即代表超时。数据库轮询方案则在SQL里用update_time < now()-3600来捞超时记录。

另一个关键是状态一致性。人工页面和等待方可能同时操作,要用乐观锁或数据库事务防止重复流转。比如在把WAITING改成PASS时,用update ... where status='WAITING'并判断影响行数,行数为0说明已被别人处理。消息通知可能因为网络重发,消费者要幂等,根据task_id先查是否已结束。

下表列出两种常见实现的取舍,方便按团队基础设施选择。

方案实时性复杂度适用场景
数据库轮询秒级延迟中小流量,无MQ
消息队列通知毫秒级高并发,要求及时唤醒

最后提醒,人工介入节点要在前端明确展示“已提交,等待审核”,不要让用户重复点。后端用异步通知解耦后,整体吞吐量能提升几十倍,因为机器线程不再被人类节奏拖死。

异步通知流程编排人工介入修改时间:2026-08-13 18:48:42

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。