当前位置: 首页 > news >正文

【基于 Swoole+Hyperf 的微服务实战】第三周·周三 RPC 客户端与自定义负载均衡

【基于 Swoole+Hyperf 的微服务实战】第三周·周三 RPC 客户端与自定义负载均衡

今天我们进入第三周周三,主题是RPC 客户端与自定义负载均衡。昨天我们实现了基本的 JSON-RPC 调用,今天要深入客户端的配置与扩展,让你能掌控多个服务实例之间的流量分配,为生产环境的高可用打下基础。你将亲手实现一个自定义的轮询负载均衡器,并观察请求如何在多个节点间分布。


今日目标

  1. 理解@RpcClient注解的底层代理机制和配置。
  2. 掌握服务消费者配置,能手动指定多个服务节点。
  3. 理解负载均衡器接口Hyperf\LoadBalancer\LoadBalancerInterface,并能编写自己的策略。
  4. 启动多个用户服务节点(通过多端口模拟),验证自定义负载均衡器的效果。
  5. 通过控制台日志和请求计数,观察负载分布,理解均衡算法的重要性。

一、环境准备(约 20 分钟)

继续使用hyperf-app项目,确保容器启动。

docker-composeexecswoolebashcd/var/www/hyperf-app

我们今天需要模拟多个用户服务节点。由于是单机开发,我们可以让同一个 Swoole 进程监听多个端口来模拟多实例。Hyperf 支持在server.php中配置多个服务端,我们就在原有jsonrpc的基础上增加一个jsonrpc2服务。

同时确认已经安装了负载均衡组件,它随hyperf/json-rpc自动安装。


二、知识核心:客户端代理与负载均衡接口(约 1 小时)

1.@RpcClient注解背后

当你写:

#[RpcClient(name:"UserService",protocol:"jsonrpc-http",server:"jsonrpc")]privateUserServiceInterface$userService;

Hyperf 会通过 AOP 生成一个动态代理对象,这个代理实现了UserServiceInterface。当你调用$this->userService->getUserById(1)时,实际执行流程:

  1. 代理对象拦截方法调用。
  2. 消费者配置中获取对应服务名的所有节点信息(nodes)。
  3. 通过负载均衡器选择一个节点。
  4. 使用协程化的 HTTP 或 TCP 客户端发送 JSON-RPC 请求。
  5. 将返回结果反序列化并返回。

消费者配置在config/autoload/services.php,可以配置多个节点:

'consumers'=>[['name'=>'UserService','nodes'=>[['host'=>'127.0.0.1','port'=>9502],['host'=>'127.0.0.1','port'=>9503],],],],

默认的负载均衡器是随机选择,也可以指定为轮询、加权等。

2. 负载均衡器接口

所有负载均衡器必须实现Hyperf\LoadBalancer\LoadBalancerInterface

interfaceLoadBalancerInterface{publicfunctionselect(array$nodes):Node;publicfunctionaddNode(Node$node):void;publicfunctionremoveNode(Node$node):void;publicfunctiongetNodes():array;}

select()是核心,根据算法返回一个Hyperf\LoadBalancer\Node对象。Hyperf 内置了:

  • RandomLoadBalancer:随机
  • RoundRobinLoadBalancer:轮询
  • WeightedRoundRobinLoadBalancer:加权轮询
  • ConsistentHashLoadBalancer:一致性哈希(需要参数)

我们可以自定义一个,例如基于 IP 哈希带权重的自定义算法,然后通过配置指定给消费者。


三、实战:多节点模拟与自定义轮询负载均衡(约 3 小时)

步骤 1:添加第二个 JSON-RPC 服务端口

编辑config/autoload/server.php,在servers数组中增加第二个 JSON-RPC 服务,使用相同回调:

['name'=>'jsonrpc2',// 名称不同'type'=>Server::SERVER_HTTP,'host'=>'0.0.0.0','port'=>9503,'sock_type'=>SWOOLE_SOCK_TCP,'callbacks'=>[Event::ON_REQUEST=>[\Hyperf\JsonRpc\HttpServer::class,'onRequest'],],],

现在应用会同时监听 9502 和 9503 两个端口,都提供 UserService。

步骤 2:配置消费者多节点

编辑config/autoload/services.php,添加两个节点:

<?phpreturn['consumers'=>[['name'=>'UserService','nodes'=>[['host'=>'127.0.0.1','port'=>9502],['host'=>'127.0.0.1','port'=>9503],],],],];

此时如果不清除缓存,RPC 客户端会使用默认的随机负载均衡器。为了验证,我们需要替换为我们自定义的轮询均衡器。

步骤 3:创建自定义负载均衡器:简单轮询

创建app/LoadBalancer/CustomRoundRobin.php

<?phpnamespaceApp\LoadBalancer;useHyperf\LoadBalancer\LoadBalancerInterface;useHyperf\LoadBalancer\Node;classCustomRoundRobinimplementsLoadBalancerInterface{privatearray$nodes=[];privateint$currentIndex=0;publicfunctionselect(array$nodes):Node{if(empty($nodes)){thrownew\RuntimeException('No nodes available');}$count=count($nodes);$node=$nodes[$this->currentIndex%$count];$this->currentIndex=($this->currentIndex+1)%$count;return$node;}publicfunctionaddNode(Node$node):void{$this->nodes[]=$node;}publicfunctionremoveNode(Node$node):void{$key=array_search($node,$this->nodes,true);if($key!==false){unset($this->nodes[$key]);$this->nodes=array_values($this->nodes);}}publicfunctiongetNodes():array{return$this->nodes;}}

说明:我们维护一个$currentIndex,每次select递增,实现轮询。注意select接收的$nodes是由消费者配置传入的节点数组(Node对象),我们可以直接基于它进行索引选择。

步骤 4:配置消费者使用自定义均衡器

services.php中为UserService消费者指定均衡器类:

'consumers'=>[['name'=>'UserService','load_balancer'=>App\LoadBalancer\CustomRoundRobin::class,'nodes'=>[['host'=>'127.0.0.1','port'=>9502],['host'=>'127.0.0.1','port'=>9503],],],],
步骤 5:修改用户服务实现以便区分节点

为了肉眼观察请求被路由到哪个端口,我们让UserService在返回数据中带上当前服务端口。可以通过获取当前请求的端口来实现吗?由于服务端由 Swoole 处理,我们可以在getUserById中简单返回一个静态值,但为了区分,可以在构造函数中接收配置吗?更简单的方法是:在UserService中通过 Swoole 的Server信息获取当前监听端口。但由于服务类是无状态的,获取端口需要依赖注入 Request 对象,在 RPC 回调中可能获取不到 HTTP 请求对象。简单起见,我们让UserService返回一个包含“节点标识”的字段,可以通过添加环境变量或配置文件区分不同的实现。但因为我们是在同一个进程中监听两个端口,使用的是同一个UserService实例,无法直接区分。为了演示效果,我们可以在UserService中随机返回两个端口之一?这样无法证明负载均衡生效。更准确的做法是:在消费者调用时,记录所选节点的信息。我们可以通过切面或中间件在客户端记录。但为了直观,我们可以在文章控制器中,调用 RPC 后打印某个标记。不过这也不能区分节点。

更好的方式:在自定义负载均衡器的select方法中记录日志,每次选择节点时打印节点地址。这样我们就能从日志中清晰看到轮询过程。

修改CustomRoundRobin

publicfunctionselect(array$nodes):Node{if(empty($nodes)){thrownew\RuntimeException('No nodes available');}$count=count($nodes);$node=$nodes[$this->currentIndex%$count];$this->currentIndex=($this->currentIndex+1)%$count;// 记录日志$logger=\Hyperf\Utils\ApplicationContext::getContainer()->get(\Psr\Log\LoggerInterface::class);$logger->info(sprintf("RPC请求路由到节点: %s:%d",$node->host,$node->port));return$node;}

这样每次 RPC 调用时,都会在hyperf.log中输出一行日志,方便观察。

步骤 6:在文章控制器中触发多次调用

为了测试,我们可以在ArticleController中添加一个测试方法,连续多次调用getUserById,然后返回日志提示。或者直接写一个临时接口:

#[RequestMapping(path:'test-lb',methods:'get')]publicfunctiontestLoadBalance(){$logs=[];for($i=0;$i<5;$i++){$author=$this->userService->getUserById(1);// 这里不好直接获取节点,但日志已记录$logs[]=$author;}return['logs'=>$logs,'message'=>'查看运行时日志中路由记录'];}

访问该接口,然后查看runtime/logs/hyperf.log,应该能看到类似:

[INFO] RPC请求路由到节点: 127.0.0.1:9502 [INFO] RPC请求路由到节点: 127.0.0.1:9503 [INFO] RPC请求路由到节点: 127.0.0.1:9502 [INFO] RPC请求路由到节点: 127.0.0.1:9503 [INFO] RPC请求路由到节点: 127.0.0.1:9502

完美证明轮询工作正常。

步骤 7:调整权重模拟生产场景(可选)

我们可以进一步创建一个简单的加权随机负载均衡器,权重来自节点配置的weight属性。在services.php中可以为节点增加weight

'nodes'=>[['host'=>'127.0.0.1','port'=>9502,'weight'=>3],['host'=>'127.0.0.1','port'=>9503,'weight'=>1],],

然后创建App\LoadBalancer\CustomWeightedRandom.php,实现加权随机逻辑。但今天时间有限,我们只做轮询,有兴趣可以课后完成。


四、成果测试与验证(约 1 小时)

1. 测试步骤
  • 重启服务php bin/hyperf.php start
  • 访问http://localhost:9501/articles/test-lb
  • 查看runtime/logs/hyperf.log,确认出现交替的节点记录。
2. 故障转移测试(手动)

停止一个节点(通过修改server.php暂时注释掉jsonrpc2服务,或直接模拟端口不可用),再次请求,观察均衡器是否会报错。如果只有jsonrpc一个节点,负载均衡器会总是选择那个节点。如果两个节点都失效,连接会抛出异常。这为明天的熔断降级做铺垫。

3. 验证清单
检验项方法通过标准
多节点配置查看server.phpservices.php服务启动日志显示 9502 和 9503 监听
自定义负载均衡器访问测试接口,查看日志日志中交替出现两个端口
随机与轮询的区别临时移除自定义均衡器,使用默认随机日志无规律,轮询有明显交替
节点权重处理(可选)实现加权随机权重高的节点被访问次数更多
RPC 调用功能不受影响文章详情仍能获取作者信息返回正常
4. 思考题
  • 为什么需要负载均衡?单节点压力大、无法水平扩展。负载均衡可以将流量分散到多个实例,提高系统吞吐和容错。
  • 轮询的缺陷:如果节点性能不均,轮询可能导致性能差的节点负载过高。加权轮询可解决部分问题,但需要动态调整权重。
  • 一致性哈希:当节点频繁变动时,一致性哈希可以最小化数据重新分布,适合有状态服务(如缓存服务器),但在无状态的 RPC 中多用于保证同一请求始终到同一节点(如灰度发布)。

五、今日作业与学习产出

  1. 提交代码:将自定义负载均衡器、server.phpservices.php、测试控制器提交到 Git。
  2. 扩展实践
    • 实现一个基于 IP 哈希的负载均衡器,让同一客户端 IP 的请求始终路由到同一节点(可以在select中通过上下文获取请求 IP,但 RPC 客户端可能没有请求 IP 概念,可通过传入参数来哈希)。
    • 尝试启动真实的多个 Docker 容器来模拟多实例,然后通过 Nacos 或 Consul 注册中心动态发现节点(提前预习)。
  3. 学习笔记
    • 绘制@RpcClient代理调用流程图,标注负载均衡器的位置。
    • 总结不同负载均衡策略的适用场景(随机、轮询、加权、一致性哈希、最少连接)。
  4. 挑战任务
    • 使用 Swoole 的Channel实现一个简单的健康检查协程,定期检测所有节点是否可用,并动态更新负载均衡器的节点列表(类似服务注册中心的健康检查)。

通过今天的学习,你已经掌握了微服务通信中至关重要的客户端负载均衡能力,能根据业务需求灵活分配流量。明天我们将引入熔断器,让调用链路更加健壮,避免雪崩效应。

http://www.cnnetsun.cn/news/4344741.html

相关文章:

  • 基于51单片机的4位数码管计算器设计与Proteus仿真实现
  • LG 508升十字门冰箱实测:直驱变频、制冰与嵌入安装要点
  • 海康标定工具实战:从内参到手眼标定的视觉项目指南
  • 广东全省岩性分布栅格数据解读与GIS应用指南
  • Hadoop与AI Agent融合:构建西藏旅游数据智能规划系统
  • 腾讯音乐移动客户端笔试复盘:操作系统、网络与算法全解析
  • 飞猪算法岗秋招笔试实战:考点拆解与备考策略全复盘
  • Hokma核心抑制全解析:时间压力下的决策与系统设计实战
  • 单片机计算机毕设之基于 STM32 或 51 单片机的多模式温度报警与远程参数配置系统设计 基于 STM32 或 51 单片机的 NTC 测温与双继电器温控硬件系统设计(022705)
  • 单片机计算机毕设之基于 STM32 或 51 单片机的四路温度采集与手机端控制系统设计 基于 STM32 或 51 单片机的环境多点温度感知声光报警系统设计(022805)
  • Excel/WPS多条件区间查找:XLOOKUP与FILTER函数实战解析
  • 泛微OA从Windows迁移到Linux完整部署实践指南
  • Abaqus热力耦合断裂模拟:从单元选择到Python代码实现全解析
  • 学 Simulink—— 基于粒子群算法(PSO)的电机最大转矩电流比
  • 2026-08-31:统计有根树中不相邻子集的数目。用go语言,给定一棵包含 n 个节点的有根树,节点编号为 0 到 n-1,其中 0 号节点是根。每个节点的父节点由一个数组 parent 给出,根节
  • 物控核心三张表:从跟单到规划,实现物料精准管控
  • 终别【牛客tracker 每日一题】
  • 卷帘门三维建模全流程:SolidWorks参数化设计与运动仿真实战
  • TVA具身智能架构:认知图谱构建与子目标分解推理机制
  • 西门子Variant变量介绍
  • mpx原型工具实战:PX与PT换算及悬浮窗尺寸最佳实践
  • 京东秋招技术通用岗笔试全攻略:题型解析与备考策略
  • 从仿真到硬件:拆解Unitree机器人技术栈与开发实践
  • QAT伪量化
  • Windows下部署OpenClaw:从WSL2到本地大模型的AI代理实战指南
  • 2025阿里云研发岗春招笔试全解析:考察逻辑与备战策略
  • 【原创】基于AI大模型+SpringBoot+Vue的健身房私教预约及会员办理系统(设计与实现)
  • MKVToolNix:无损封装音视频与字幕的终极工具指南
  • 【单片机毕业设计】基于 STM32 或 51 单片机的激光测距参数设置与移动端监控系统设计 基于 STM32 或 51 单片机的 TOF 传感器距离采集预警设备设计与实现(023305)
  • 国防科大操作系统公开课:从进程内存到文件I/O的体系化学习指南