在 think-swoole 官方默认的配置模式中,RPC 服务(如第三步配置的 class 数组)是在服务启动时一次性加载的常驻内存数据。如果需要实现动态绑定(即无需重启 Swoole 进程即可动态新增、删除或修改可调用的 RPC 服务),我们不能将类写死在配置文件里。
在 ThinkPHP6 下,可以通过以下两种优雅的方案来实现 RPC 服务的动态绑定:
方案一:借助 TP6 依赖注入与自定义 RPC 代理类(推荐)
该方案利用一个固定的“核心网关类”注册到 config/swoole.php 中,在这个网关类内部利用 TP6 的容器机制,动态解析并调用实际的业务服务。
1. 配置文件保持静态
在 config/swoole.php 中只绑定一个总网关类(如 gateway):
php
'rpc' => [
'server' => [
'enable' => true,
'host' => '0.0.0.0',
'port' => 9000,
'class' => [
'gateway' => \app\rpc\RpcGateway::class, // 所有的 RPC 请求统一进到这里
],
],
],
请谨慎使用此类代码。
2. 编写动态路由网关
创建 app\rpc\RpcGateway.php。在此处读取动态配置(例如数据库、Redis 或动态配置文件),实现按需实例化:
php
namespace app\rpc;
use think\Container;
use think\exception\ClassNotFoundException;
class RpcGateway
{
/**
* 统一动态魔术调用方法
* 当客户端调用任何未显式定义的方法时触发
*/
public function __call($name, $arguments)
{
// $name 对应客户端传过来的“服务标识.方法名”,例如:user_getInfo
// 或者从 $arguments 结构中解构出目标服务
// 示例:约定客户端第一个参数传服务名,第二个参数传方法名,第三个为实际参数
[$serviceAlias, $methodName, $realParams] = $arguments;
// 动态获取服务映射关系(这里可以从 Redis 或数据库读取)
// 比如从 Redis 读取:$serviceClass = Cache::store('redis')->get("rpc:service:{$serviceAlias}");
$serviceClass = $this->getDynamicServiceClass($serviceAlias);
if (!$serviceClass || !class_exists($serviceClass)) {
throw new ClassNotFoundException('RPC服务未注册或不存在: ' . $serviceAlias);
}
// 使用 TP 容器动态实例化,支持依赖注入
$serviceInstance = Container::getInstance()->make($serviceClass);
if (!method_exists($serviceInstance, $methodName)) {
throw new \BadMethodCallException('RPC服务方法不存在: ' . $methodName);
}
// 动态调用实际业务
return call_user_func_array([$serviceInstance, $methodName], (array)$realParams);
}
/**
* 模拟从动态介质读取映射关系
*/
private function getDynamicServiceClass(string $alias): ?string
{
// 实际开发中,你可以查库表或查 Redis
$map = [
'user' => \app\rpc\User::class,
'order' => \app\rpc\Order::class,
];
return $map[$alias] ?? null;
}
}
请谨慎使用此类代码。
方案二:基于高级 onRequest 挂载模式动态实例化(最灵活)
如果你正在使用前文提到的 onRequest 挂载 JsonRPC 方案,动态绑定更加自然。因为每次 HTTP 请求进来时,代码都是实时解析的,我们只需要把映射表放到常驻内存的内存表(Swoole\Table)或 Redis 中即可。
1. 动态服务注册中心(使用 Redis 示例)
当后台管理员新增一个 RPC 服务类,或者上传了新的微服务模块时,通过后台直接向 Redis 写入一条记录,无需重启服务器:
php
// 后台注册新服务代码片段
$serviceAlias = 'goods';
$className = 'app\rpc\GoodsService';
// 写入 Redis 缓存
\think\facade\Cache::store('redis')->set("rpc:routes:{$serviceAlias}", $className);
请谨慎使用此类代码。
2. 服务端事件监听器动态读取
在你的 JsonRpcServerListener 中,修改原先死代码解析类的逻辑:
php
// 4. 原先的代码:$className = "app\\rpc\\" . ucfirst($serviceName);
// 改为从 Redis 动态读取类名:
$className = \think\facade\Cache::store('redis')->get("rpc:routes:{$serviceName}");
if (!$className || !class_exists($className)) {
$this->sendError($response, -32601, 'RPC Service Not Registered', $id);
return;
}
// 5. 动态执行(保持不变)
$serviceInstance = container($className);
$result = call_user_func_array([$serviceInstance, $actionName], $params);
请谨慎使用此类代码。
💡 核心避坑指南
类文件热重载(Hot Reload):虽然你动态绑定了“类名”,但如果你修改了该类文件里面的具体代码逻辑,由于 Swoole 的常驻内存特性,已经加载过内存的类不会自动更新。你必须结合
think-swoole的hot_reload热重载配置(监听文件变动自动重启 Worker 进程),或者手动执行php think swoole reload。多节点同步:如果是分布式集群,动态绑定的映射关系必须存放在公共介质(如 Redis)中,不能存在某个单机的本地文件(如 runtime 缓存),确保所有节点读取到的动态绑定关系一致。