webman自定义进程是订单状态机的执行载体,必须封装为独立进程类继承support\process,通过事件驱动实现原子状态迁移,并依赖redis/mysql保障一致性与跨进程通信。

Webman自定义进程是状态机的执行载体
订单状态机不能塞进HTTP控制器里跑——它需要长期驻留、响应异步事件(支付回调、超时任务、人工操作)、维护连接上下文。Webman的app/Process/目录就是干这事的:每个状态机逻辑应封装为一个独立进程类,继承support\Process,而非Workerman\Worker。
常见错误是直接extends Workerman\Worker,结果进程脱离Webman管理:onWorkerStart不执行、热重载失效、ps aux看不到子进程、SIGTERM收不到。正确写法是:
namespace app\Process;
use support\Process;
use Workerman\Timer;
class OrderStateMachineProcess extends Process
{
public function onWorkerStart($worker)
{
// 这里只启动监听或定时器,不调用 Worker::runAll()
Timer::add(1, [$this, 'checkTimeout']);
}
public function checkTimeout()
{
// 查询待超时订单,触发CANCEL事件
}
}
文件必须放在app/Process/OrderStateMachineProcess.php,命名空间严格匹配,且必须实现onWorkerStart方法。
状态迁移必须走事件驱动,而非主动轮询
订单状态变更不是“查一遍、改一下”就完事。真正的状态机要求:所有变更由明确事件触发(如PAY_SUCCESS、USER_CANCEL、TIMEOUT_AUTO_CANCEL),并经校验后原子执行。否则容易出现“已取消订单又被发货”这类非法流转。
推荐结构:
- 用枚举或常量定义所有
OrderState和OrderEvent,杜绝魔法字符串 - 状态转换规则存Redis哈希表(
order:state:transition),格式为"WAIT_PAY:PAY_SUCCESS" → "PAID" - 接收事件时先查规则,再执行动作(更新DB、发MQ、推WebSocket),最后持久化新状态
- 关键操作加MySQL行锁或Redis Lua脚本,避免并发冲突(例如两个支付回调同时把订单从WAIT_PAY改成PAID)
不要在onMessage里直接update order set status = 'PAID'——这绕过了状态机校验,等于裸奔。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
TCP/WS长连接场景下必须自己管状态与心跳
如果订单状态机要响应WebSocket实时指令(比如客服强制改状态),你拿到的是TcpConnection对象,不是封装好的Request。框架不提供会话上下文、连接池或自动心跳。
必须手动处理:
- 用
connection->id做键,把用户登录态、当前订单ID、最后心跳时间戳存到Redis或进程内数组(注意多Worker时不共享) - 粘包要拆帧:约定前4字节为包长度,或用
\n分隔,否则onMessage可能一次收到多个JSON - 心跳检测不能只靠
onClose:得用Timer::add()定期扫描Redis里超时连接,主动$connection->close() - 推送前必判
$connection->isConnected(),否则PHP会warning甚至crash进程
跨进程通信只能走外部中间件
Webman的每个自定义进程都是独立PHP进程,static变量、全局数组、文件锁全都不互通。HTTP Worker收到支付回调,想通知订单状态机进程处理?不能用pcntl_signal或共享内存。
可行路径只有两条:
- 发消息到
Redis Stream或RabbitMQ,状态机进程作为消费者监听 - HTTP Worker调用状态机暴露的本地HTTP接口(如
http://127.0.0.1:8083/api/order/event),但需加限流和幂等控制
别试图用file_put_contents写日志文件然后轮询——IO瓶颈、竞态、延迟高,线上基本不可用。
最易被忽略的一点:状态机进程重启时,Redis里的连接状态、未处理事件、定时器快照全丢失。必须设计补偿机制——比如用MySQL记录事件消费位点,重启后从断点重放。










