- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
JSON-RPC 是一种基于 JSON 格式的轻量级 RPC 协议标准,易于使用和阅读。在 Hyperf 中由hyperf/json-rpc组件实现,可自定义基于 HTTP 协议传输,或直接基于 TCP 协议传输。本篇指南将带你完整掌握在 Hyperf 中定义服务提供者(ServiceProvider)、服务消费者(ServiceConsumer)、发布服务到服务中心(Consul / Nacos)、返回 PHP 对象以及使用连接池化 Transporter 的完整流程,并深入到源码层面理解协议注册、路由生成与错误码体系,使你能快速搭建可运行的微服务 RPC 链路。
JSON-RPC 在 Hyperf 中的定位
Hyperf 的 RPC 体系由多个组件协同组成:
- hyperf/json-rpc:负责 JSON-RPC 协议的解析与封装,提供
Packer(数据打包器)、DataFormatter(数据格式化器)、Transporter(数据传输器)等协议处理核心; - hyperf/rpc-server:服务端组件,负责将
#[RpcService]注解类注册为可调用的路由; - hyperf/rpc-client:客户端组件,提供
AbstractServiceClient基类与动态代理机制,让调用远程服务如同调用本地方法。
三个组件的职责划分清晰:json-rpc解决"协议怎么讲",rpc-server解决"服务怎么被暴露",rpc-client解决"客户端怎么消费"。
安装
安装 JSON-RPC 协议处理组件:
composer require hyperf/json-rpc该组件仅是 JSON-RPC 的协议处理组件,通常还需要配合rpc-server或rpc-client来满足服务端和客户端场景,如同时使用则都需要安装。
要使用 JSON-RPC 服务端:
composer require hyperf/rpc-server要使用 JSON-RPC 客户端:
composer require hyperf/rpc-client若需要将服务发布到服务中心(Consul / Nacos),还需安装对应的服务治理组件(见下文"发布到服务中心"一节)。
角色与服务契约
服务有两种角色:服务提供者(ServiceProvider),为其它服务提供服务的服务;服务消费者(ServiceConsumer),依赖其它服务的服务。一个服务既可能是提供者,同时又是消费者。
两者之间通过服务契约来定义和约束接口的调用。在 Hyperf 中,服务契约可直接理解为一个接口类(Interface),通常这个接口类会同时出现在提供者和消费者的代码中。
定义服务提供者
目前仅支持通过注解形式定义服务提供者,后续迭代会增加配置形式。可以通过#[RpcService]注解对一个类进行定义即可发布这个服务:
<?php namespace App\JsonRpc; use Hyperf\RpcServer\Annotation\RpcService; /** * 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性 */ #[RpcService(name: "CalculatorService", protocol: "jsonrpc-http", server: "jsonrpc-http")] class CalculatorService implements CalculatorServiceInterface { // 实现一个加法方法,这里简单的认为参数都是 int 类型 public function add(int $a, int $b): int { // 这里是服务方法的具体实现 return $a + $b; } }使用
#[RpcService]注解需use Hyperf\RpcServer\Annotation\RpcService;命名空间。
#[RpcService]注解的四个参数
从源码 src/rpc-server/src/Annotation/RpcService.php 可以看到,注解类定义了四个属性:
| 参数 | 说明 | 默认值 |
|---|---|---|
name | 定义该服务的名称,全局唯一即可,Hyperf 会根据该属性生成对应的 ID 注册到服务中心 | '' |
protocol | 定义该服务暴露的协议,目前仅支持jsonrpc-http、jsonrpc、jsonrpc-tcp-length-check,分别对应于 HTTP 协议和 TCP 协议下的两种形态 | jsonrpc-http |
server | 绑定该服务类发布所要承载的Server,对应config/autoload/server.php文件内servers下所对应的name,意味着需要定义一个对应的Server | jsonrpc-http |
publishTo | 定义该服务所要发布的服务中心,目前仅支持consul、nacos或为空。为空时代表不发布到服务中心,需手动处理服务发现问题 | '' |
关于protocol参数,这里的值对应在Hyperf\Rpc\ProtocolManager中注册的协议的key,它们本质上都是 JSON-RPC 协议,区别在于数据格式化、数据打包、数据传输器等不同。server参数则要求配置文件config/autoload/server.php中存在同名 Server,否则启动会报错——TcpServer 在初始化时会遍历server.servers查找匹配的配置项,找不到则抛出InvalidArgumentException(见 src/json-rpc/src/TcpServer.php)。
路由如何生成
从源码 src/rpc-server/src/Router/DispatcherFactory.php 可以看到,注解路由的注册过程为:遍历注解收集器,找到带有#[RpcService]的类,通过反射获取该类所有公共方法(跳过__开头的方法),用PathGenerator基于服务名与方法名生成路径并注册到对应server的路由收集器中,同时触发AfterPathRegister事件——这正是服务注册监听器(RegisterServiceListener)的挂载点。也就是说,服务类中的每个公共方法都会自动成为一个可被远程调用的 RPC 方法。
定义 JSON RPC Server
HTTP Server(适配jsonrpc-http协议)
在config/autoload/server.php中配置:
<?php use Hyperf\Server\Server; use Hyperf\Server\Event; return [ // 这里省略了该文件的其它配置 'servers' => [ [ 'name' => 'jsonrpc-http', 'type' => Server::SERVER_HTTP, 'host' => '0.0.0.0', 'port' => 9504, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_REQUEST => [\Hyperf\JsonRpc\HttpServer::class, 'onRequest'], ], ], ], ];HttpServer继承自 Hyperf 的 HTTP Server 基类,构造时从ProtocolManager中取出jsonrpc-http协议对应的Packer与DataFormatter(见 src/json-rpc/src/HttpServer.php)。它还会检查content-type是否为application/json,并校验请求体中是否包含jsonrpc、method、params三个关键字段,不满足时直接返回 JSON-RPC 标准错误响应。值得注意的是,它还内置了对 Consul 健康检查的兼容:当user-agent为Consul Health Check时直接放行,不解析协议体(见 src/json-rpc/src/HttpServer.php)。
TCP Server(适配jsonrpc协议)
<?php use Hyperf\Server\Server; use Hyperf\Server\Event; return [ // 这里省略了该文件的其它配置 'servers' => [ [ 'name' => 'jsonrpc', 'type' => Server::SERVER_BASE, 'host' => '0.0.0.0', 'port' => 9503, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_RECEIVE => [\Hyperf\JsonRpc\TcpServer::class, 'onReceive'], ], 'settings' => [ 'open_eof_split' => true, 'package_eof' => "\r\n", 'package_max_length' => 1024 * 1024 * 2, ], ], ], ];该配置采用EOF 分隔方式界定数据包边界:以"\r\n"作为 JSON 消息的结束标记,package_max_length限制单包最大 2MB,防止异常数据撑爆内存。
TCP Server(适配jsonrpc-tcp-length-check协议)
jsonrpc-tcp-length-check是jsonrpc的扩展协议,采用长度字段方式界定数据包边界,只需修改对应settings即可切换:
<?php use Hyperf\Server\Server; use Hyperf\Server\Event; return [ // 这里省略了该文件的其它配置 'servers' => [ [ 'name' => 'jsonrpc', 'type' => Server::SERVER_BASE, 'host' => '0.0.0.0', 'port' => 9503, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_RECEIVE => [\Hyperf\JsonRpc\TcpServer::class, 'onReceive'], ], 'settings' => [ 'open_length_check' => true, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'package_max_length' => 1024 * 1024 * 2, ], ], ], ];配置项含义:open_length_check开启长度校验;package_length_type为N,表示长度字段为 4 字节无符号大端整数(对应pack('N', ...));package_length_offset为 0,表示长度字段位于包首;package_body_offset为 4,表示消息体从第 4 字节开始。
有意思的是,服务端并不需要为两种 TCP 协议分别配置不同的 Server:从 src/json-rpc/src/TcpServer.php 可以看到,TcpServer在初始化协议时会检查settings.open_length_check,为真则自动选用jsonrpc-tcp-length-check协议及其对应的JsonLengthPacker,否则使用jsonrpc协议。对应地,JsonLengthPacker的pack()方法会先用pack($this->type, strlen($data))写入 4 字节长度头,再拼接 JSON 正文(见 src/json-rpc/src/Packer/JsonLengthPacker.php)。
发布到服务中心
目前仅支持发布服务到consul、nacos,后续会增加其它服务中心。
发布服务到 Consul:通过composer require hyperf/service-governance-consul引用组件(如果已安装则可忽略该步骤),然后在config/autoload/services.php配置文件内配置drivers.consul配置即可。
发布服务到 Nacos 类似:通过composer require hyperf/service-governance-nacos引用组件,然后在config/autoload/services.php配置文件内配置drivers.nacos配置,示例如下:
<?php return [ 'enable' => [ 'discovery' => true, 'register' => true, ], 'consumers' => [], 'providers' => [], 'drivers' => [ 'consul' => [ 'uri' => 'http://127.0.0.1:8500', 'token' => '', ], 'nacos' => [ // nacos server url like https://nacos.hyperf.io, Priority is higher than host:port // 'url' => '', // The nacos host info 'host' => '127.0.0.1', 'port' => 8848, // The nacos account info 'username' => null, 'password' => null, 'guzzle' => [ 'config' => null, ], 'group_name' => 'api', 'namespace_id' => 'namespace_id', 'heartbeat' => 5, ], ], ];配置完成后,在启动服务时,Hyperf 会自动地将#[RpcService]定义了publishTo属性为consul或nacos的服务注册到对应的服务中心去。其实现机制是:RegisterServiceListener监听AfterPathRegister事件,当路由注册完成后自动把服务信息写入ServiceManager(见 src/json-rpc/src/Listener/RegisterServiceListener.php),由服务治理驱动(如service-governance-consul)完成实际注册。
目前仅支持
jsonrpc和jsonrpc-http协议发布到服务中心去,其它协议尚未实现服务注册。
定义服务消费者
一个服务消费者可以理解为就是一个客户端类,但在 Hyperf 中无需处理连接和请求相关的事情,只需要进行一些鉴定配置即可。
自动创建代理消费者类
可通过在config/autoload/services.php配置文件内进行一些简单配置,即可通过动态代理自动创建消费者类:
<?php return [ // 此处省略了其它同层级的配置 'consumers' => [ [ // name 需与服务提供者的 name 属性相同 'name' => 'CalculatorService', // 服务接口名,可选,默认值等于 name 配置的值,如果 name 直接定义为接口类则可忽略此行配置,如 name 为字符串则需要配置 service 对应到接口类 'service' => \App\JsonRpc\CalculatorServiceInterface::class, // 对应容器对象 ID,可选,默认值等于 service 配置的值,用来定义依赖注入的 key 'id' => \App\JsonRpc\CalculatorServiceInterface::class, // 服务提供者的服务协议,可选,默认值为 jsonrpc-http // 可选 jsonrpc-http jsonrpc jsonrpc-tcp-length-check 'protocol' => 'jsonrpc-http', // 负载均衡算法,可选,默认值为 random 'load_balancer' => 'random', // 这个消费者要从哪个服务中心获取节点信息,如不配置则不会从服务中心获取节点信息 'registry' => [ 'protocol' => 'consul', 'address' => 'http://127.0.0.1:8500', ], // 如果没有指定上面的 registry 配置,即为直接对指定的节点进行消费,通过下面的 nodes 参数来配置服务提供者的节点信息 'nodes' => [ ['host' => '127.0.0.1', 'port' => 9504], ], // 配置项,会影响到 Packer 和 Transporter 'options' => [ 'connect_timeout' => 5.0, 'recv_timeout' => 5.0, 'settings' => [ // 根据协议不同,区分配置 'open_eof_split' => true, 'package_eof' => "\r\n", // 'open_length_check' => true, // 'package_length_type' => 'N', // 'package_length_offset' => 0, // 'package_body_offset' => 4, ], // 重试次数,默认值为 2,收包超时不进行重试。暂只支持 JsonRpcPoolTransporter 'retry_count' => 2, // 重试间隔,毫秒 'retry_interval' => 100, // 使用多路复用 RPC 时的心跳间隔,null 为不触发心跳 'heartbeat' => 30, // 当使用 JsonRpcPoolTransporter 时会用到以下配置 'pool' => [ 'min_connections' => 1, 'max_connections' => 32, 'connect_timeout' => 10.0, 'wait_timeout' => 3.0, 'heartbeat' => -1, 'max_idle_time' => 60.0, ], ], ] ], ];关键配置项说明:
name需与服务提供者的name属性相同;service/id均为可选:service默认值等于name,id默认值等于service。当服务提供者以接口类名作为服务名发布时,消费端只需设置name为接口类名即可,无需再设置id和service;registry与nodes二选一:配置registry则从服务中心动态获取节点;不配置则直接对nodes中指定的节点进行消费;options中的connect_timeout、recv_timeout会直接影响Transporter的行为。从 src/json-rpc/src/JsonRpcTransporter.php 源码可见,两个超时默认值均为5.0秒,settings会原样透传给底层 Socket 创建(src/json-rpc/src/JsonRpcTransporter.php),因此 EOF 与长度校验相关配置必须与服务端保持一致。
在应用启动时会自动创建客户端类的代理对象,并在容器中使用配置项id的值(如果未设置,会使用配置项service值代替)来添加绑定关系,这样就和手工编写的客户端类一样,通过注入CalculatorServiceInterface接口来直接使用客户端。
当服务提供者使用接口类名发布服务名,在服务消费端只需要设置配置项
name值为接口类名,不需要重复设置配置项id和service。
手动创建消费者类
如对消费者类有更多的需求,可通过手动创建一个消费者类来实现,只需定义一个类及相关属性即可:
<?php namespace App\JsonRpc; use Hyperf\RpcClient\AbstractServiceClient; class CalculatorServiceConsumer extends AbstractServiceClient implements CalculatorServiceInterface { /** * 定义对应服务提供者的服务名称 */ protected string $serviceName = 'CalculatorService'; /** * 定义对应服务提供者的服务协议 */ protected string $protocol = 'jsonrpc-http'; public function add(int $a, int $b): int { return $this->__request(__FUNCTION__, compact('a', 'b')); } }然后还需要在配置文件定义一个配置标记要从何服务中心获取节点信息,位于config/autoload/services.php(如不存在可自行创建):
<?php return [ // 此处省略了其它同层级的配置 'consumers' => [ [ // 对应消费者类的 $serviceName 'name' => 'CalculatorService', // 这个消费者要从哪个服务中心获取节点信息,如不配置则不会从服务中心获取节点信息 'registry' => [ 'protocol' => 'consul', 'address' => 'http://127.0.0.1:8500', ], // 如果没有指定上面的 registry 配置,即为直接对指定的节点进行消费,通过下面的 nodes 参数来配置服务提供者的节点信息 'nodes' => [ ['host' => '127.0.0.1', 'port' => 9504], ], ] ], ];这样便可以通过CalculatorService类来实现对服务的消费了。为了让这里的关系逻辑更加合理,还应该在config/autoload/dependencies.php内定义CalculatorServiceInterface和CalculatorServiceConsumer的关系,示例如下:
return [ App\JsonRpc\CalculatorServiceInterface::class => App\JsonRpc\CalculatorServiceConsumer::class, ];这样便可以通过注入CalculatorServiceInterface接口来使用客户端了。
从源码看,手动创建的消费者类继承自 src/rpc-client/src/AbstractServiceClient.php:基类默认定义了serviceName、protocol(默认jsonrpc-http)、loadBalancer(默认random)等属性,构造时会从容器中取出Protocol、LoadBalancerManager,用createNodes()生成节点列表并创建负载均衡器,最终组装出Client(内含Packer与Transporter)。registry配置正是驱动createNodes()从服务中心(如 Consul、Nacos)拉取节点的来源,这一点在测试用例 src/rpc-client/tests/AbstractServiceClientTest.php 中有明确验证:模拟注册中心返回多个节点后,createNodes()会生成对应数量的Node对象。
配置复用
通常来说,一个服务消费者会同时消费多个服务提供者,当通过服务中心来发现服务提供者时,config/autoload/services.php配置文件内就可能会重复配置很多次registry配置。由于服务中心通常是统一的,可以通过PHP 变量或循环等 PHP 代码来实现配置文件的生成。
通过 PHP 变量生成配置
<?php $registry = [ 'protocol' => 'consul', 'address' => 'http://127.0.0.1:8500', ]; return [ // 下面的 FooService 和 BarService 仅示例多服务,并不是在文档示例中真实存在的 'consumers' => [ [ 'name' => 'FooService', 'registry' => $registry, ], [ 'name' => 'BarService', 'registry' => $registry, ] ], ];通过循环生成配置
<?php return [ // 此处省略了其它同层级的配置 'consumers' => value(function () { $consumers = []; // 这里示例自动创建代理消费者类的配置形式,顾存在 name 和 service 两个配置项,这里的做法不是唯一的,仅说明可以通过 PHP 代码来生成配置 // 下面的 FooServiceInterface 和 BarServiceInterface 仅示例多服务,并不是在文档示例中真实存在的 $services = [ 'FooService' => App\JsonRpc\FooServiceInterface::class, 'BarService' => App\JsonRpc\BarServiceInterface::class, ]; foreach ($services as $name => $interface) { $consumers[] = [ 'name' => $name, 'service' => $interface, 'registry' => [ 'protocol' => 'consul', 'address' => 'http://127.0.0.1:8500', ] ]; } return $consumers; }), ];由于services.php本身是 PHP 文件,Hyperf 会以require方式加载,因此可以在配置中直接使用变量、循环乃至value()闭包来动态生成消费者列表,避免大量重复粘贴。
返回 PHP 对象
当框架导入symfony/serializer (^5.0)和symfony/property-access (^5.0)后,并在dependencies.php中配置一下映射关系:
use Hyperf\Serializer\SerializerFactory; use Hyperf\Serializer\Serializer; return [ Hyperf\Contract\NormalizerInterface::class => new SerializerFactory(Serializer::class), ];NormalizerInterface就会支持对象的序列化和反序列化。暂时不支持MathValue[]这种对象数组。
定义返回对象:
<?php declare(strict_types=1); namespace App\JsonRpc; class MathValue { public $value; public function __construct($value) { $this->value = $value; } }改写接口文件:
<?php declare(strict_types=1); namespace App\JsonRpc; interface CalculatorServiceInterface { public function sum(MathValue $v1, MathValue $v2): MathValue; }控制器中调用:
<?php use Hyperf\Context\ApplicationContext; use App\JsonRpc\CalculatorServiceInterface; use App\JsonRpc\MathValue; $client = ApplicationContext::getContainer()->get(CalculatorServiceInterface::class); /** @var MathValue $result */ $result = $client->sum(new MathValue(1), new MathValue(2)); var_dump($result->value);从源码角度看,json-rpc组件提供了JsonRpcNormalizer(src/json-rpc/src/JsonRpcNormalizer.php)作为NormalizerInterface的默认实现,它负责在请求发出前把参数对象序列化、在响应回来后把数据反序列化为接口中声明的对象类型。测试目录中的IntegerValue存根类(src/json-rpc/tests/Stub/IntegerValue.php)即用于验证这类对象参数/返回值的传输链路。
使用 JsonRpcPoolTransporter
框架提供了基于连接池的Transporter,可以有效避免高并发时建立过多连接的问题。可以通过替换JsonRpcTransporter的方式,使用JsonRpcPoolTransporter。
修改dependencies.php文件:
<?php declare(strict_types=1); use Hyperf\JsonRpc\JsonRpcPoolTransporter; use Hyperf\JsonRpc\JsonRpcTransporter; return [ JsonRpcTransporter::class => JsonRpcPoolTransporter::class, ];两种 Transporter 的差异从源码中可以看得非常清楚:
JsonRpcTransporter(src/json-rpc/src/JsonRpcTransporter.php):每次请求通过协程上下文缓存连接(Context::has/Context::set),实现协程内连接复用,但高并发时仍可能为每个协程建立新连接;JsonRpcPoolTransporter(src/json-rpc/src/JsonRpcPoolTransporter.php):通过PoolFactory获取连接池,连接用完通过defer()归还,并内置了retry_count(默认 2 次)与retry_interval(默认 100 毫秒)的重试逻辑。特别地,当异常码为SOCKET_ETIMEDOUT(收包超时)时不进行重试,避免超时场景下的重复请求放大(见 src/json-rpc/src/JsonRpcPoolTransporter.php)。
池化连接的相关参数(min_connections、max_connections、connect_timeout、wait_timeout、heartbeat、max_idle_time)在消费者配置的options.pool中定义,其默认值与源码中$config属性一致(src/json-rpc/src/JsonRpcPoolTransporter.php)。对应地,src/json-rpc/tests/JsonRpcPoolTransporterTest.php 中通过RpcPoolStub等存根验证了池化连接的获取与释放流程。
附:JSON-RPC 标准错误码
在协议层面,Hyperf 的ResponseBuilder严格遵循 JSON-RPC 2.0 规范定义了标准错误码(见 src/json-rpc/src/ResponseBuilder.php):
| 错误码 | 含义 | 默认消息 |
|---|---|---|
-32700 | 解析错误(Parse error) | Parse error. |
-32600 | 无效请求(Invalid request) | Invalid request. |
-32601 | 方法不存在(Method not found) | Method not found. |
-32602 | 无效参数(Invalid params) | Invalid params. |
-32603 | 内部错误(Internal error) | Internal error. |
-32000 | 服务端错误(Server error) | 取异常消息 |
服务端在请求体缺少jsonrpc、method、params字段、请求头content-type非application/json等场景下会自动构造对应错误响应,错误码映射逻辑见 src/json-rpc/src/ResponseBuilder.php。理解这些错误码有助于在客户端侧快速定位调用失败的原因。
总结
至此,你已经掌握了 Hyperf JSON-RPC 服务的完整链路:通过#[RpcService]注解暴露服务并提供者,通过 HTTP / TCP(EOF 分隔或长度校验)两种传输形态承载协议,可选发布到 Consul / Nacos 服务中心实现服务注册与发现,通过配置文件动态代理或继承AbstractServiceClient实现消费者,配合 Symfony Serializer 实现 PHP 对象级参数传递,再按需切换连接池化 Transporter 应对高并发场景。结合本文给出的源码路径,你可以进一步深入阅读 src/json-rpc、src/rpc-server、src/rpc-client 中的实现与测试用例,构建出属于自己的健壮微服务通信体系。
- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
相关推荐
Hyperf JSON-RPC 组件实战:从服务提供者到消费者的完整微服务通信指南
Hyperf JSON RPC 组件实战:从服务提供者到消费者的完整微服务通信指南 导读 本文以 Hyperf 官方文档 docs/zh hk/json rpc
后端微服务Hyperf JSON RPC 服务实战指南:服务提供者、服务消费者与连接池传输的完整实现
Hyperf JSON RPC 服务实战指南:服务提供者、服务消费者与连接池传输的完整实现 JSON RPC 是一种基于 JSON 格式的轻量级 RPC 协议标
后端Web框架微服务RPC框架异步编程Hyperf JSON RPC 服务实战指南:服务提供者、服务消费者与多协议传输详解
Hyperf JSON RPC 服务实战指南:服务提供者、服务消费者与多协议传输详解 本指南以 docs/zh hk/json rpc.md https://l
后端Web框架微服务RPC框架异步编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考