swoole做rpc需闭环整合协程、协议设计、连接管理与错误传播:不能直接用swoole\client(同步阻塞、无连接池、无请求id绑定);必须基于协程socket+channel实现长连接与请求分发;需处理tcp粘包(固定头+循环读取);协程上下文须用co::getcontext()透传;超时需服务端主动中断;服务注册发现为必选项,至少用redis心跳实现。

面试中问 Swoole 做 RPC,不是考你会不会写 Swoole\Server 启一个端口,而是看你能不能把「协程、协议设计、连接管理、错误传播」这几件事串起来闭环落地。
为什么不能直接用 Swoole\Client 做 RPC 客户端
很多人一上来就写 $client->connect() + $client->send(),这在单次调用或压测脚本里能跑通,但实际 RPC 场景下会立刻崩:
-
Swoole\Client是同步阻塞模型,recv()会卡住当前协程,高并发时大量协程挂起,吞吐骤降 - 没有内置连接池,每次调用都新建 TCP 连接,开销大且易触发
TIME_WAIT耗尽端口 - 无法自动重试、超时控制粒度粗(只支持全局
timeout),出错后难以区分是网络抖动还是服务不可用 - 不支持请求 ID 绑定,多个并发请求返回时无法准确匹配响应(即缺少“请求-响应”上下文)
真正可用的客户端必须基于 Swoole\Coroutine\Http\Client 或封装 Swoole\Coroutine\Socket,手动维护长连接 + 协程 Channel 做请求分发。
onReceive 回调里必须处理粘包和半包
TCP 是流式协议,onReceive 收到的数据长度完全不可控——可能一次来两个请求,也可能一个请求被切成三段。不处理就必然解析失败。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 必须定义固定长度消息头(如前 4 字节为 body 长度),先读够头,再按长度读 body
- 不能依赖
strlen($data)判断是否收全,要用socket_recv循环读取直到满足长度 - 协程环境下推荐用
Swoole\Coroutine\Socket::recv()配合unpack('N', $header)解包,避免阻塞整个 worker - 如果用 JSON 协议,别直接
json_decode($data)—— 没收完就 decode 必然返回null,且无错误提示
协程上下文丢失是 RPC 最隐蔽的坑
RPC 调用链里常需要透传 trace_id、用户身份、超时时间等上下文,但 PHP 协程切换时,全局变量、静态属性、$_SERVER 都不会自动继承。
-
Co::getContext()和Co::setContext()是唯一可靠方式,必须在收到请求时存入,在调用业务逻辑前取出 - 别在服务端方法里直接读
$_SESSION或$_COOKIE—— HTTP 上下文在 TCP RPC 里根本不存在 - 超时控制不能只靠客户端
recv()超时:服务端业务逻辑若卡死(比如 DB 死锁),必须用Co::sleep()+Co::cancel()主动中断,否则协程永久占用 - 日志打点要带协程 ID(
Co::getuid()),否则所有请求日志混在一起,根本没法排查
服务注册与发现不是加分项,是必选项
面试官如果说“手写一个 RPC”,默认考察的是可运行的最小闭环。没注册中心的 RPC 就是硬编码 IP+端口,上线即失效。
- 最低成本方案:用 Redis 的
SET key value EX 30 NX做服务心跳注册,KEYS rpc:service:*做临时发现(注意线上禁用 KEYS) - 客户端首次调用前必须拉取一次服务列表,后续用定时
GET检查存活,而不是每次调用都查 Redis - 别把服务发现逻辑写死在客户端代码里——要抽象成
RegistryInterface,方便替换为 Etcd 或 Nacos - 负载均衡策略至少实现轮询(RoundRobin)和随机(Random),权重配置可以先不写,但接口要预留
weight字段
真正难的不是写通一条调用链,而是让几十个服务节点在动态扩缩容、网络分区、进程重启时,依然能维持正确的服务寻址和流量分发——这个意识比代码更重要。










