今天是第二阶段“微服务通信与治理”的收官之战。今天我们将完成一个关键实战:将文章系统和用户服务全量接入 Consul + Nacos,验证无感扩容。前面四天我们分别完成了服务注册、服务发现、配置中心集成和多环境配置管理,今天要把这些能力串联起来,模拟生产环境中服务实例动态伸缩的场景,让消费者在不重启的情况下自动感知变化,实现真正的弹性伸缩。

今日目标
一、环境准备与现状确认(约 30 分钟)
继续在 hyperf-app 项目中,确保 Docker 已启动并进入容器:
docker-compose exec swoole bash
cd /var/www/hyperf-app
1. 确认当前服务架构
- HTTP 服务(文章/订单):监听 0.0.0.0:9501
- 用户服务(UserService):监听 0.0.0.0:9502(我们之前只保留了一个节点用于演示)
- 商品服务(ProductService):监听 0.0.0.0:9504
Consul 中应该已经注册了这两个服务(可以通过 UI http://localhost:8500 查看)。Nacos 中也已经有了 hyperf-app 配置(dev 命名空间),包含限流参数等。
2. 启停问题
由于我们只有一个 Docker PHP 容器,要启动多个用户服务实例,我们有两个选择:
- 在同一个 Hyperf 进程中添加另一个 JSON-RPC 服务器,监听不同端口(如 9503),并确保它也被注册到 Consul(需要另一个 @RpcService 实现或手动注册)。
- 使用 Docker 再启动一个相同的 PHP 容器(或用不同端口启动另一个 Hyperf 进程),但需要共享代码。
考虑到开发环境简单性,我们采用在同一个 Hyperf 应用中启用第二个 JSON-RPC 用户服务节点,并手动将其注册到 Consul。以前我们的 server.php 中曾配置过 jsonrpc2(端口 9503),今天恢复它,并让用户服务也绑定到该服务器,以便注册两个实例。
二、知识核心回顾(约 20 分钟)
- 服务注册:Provider 启动时自动向 Consul 注册,健康检查维持心跳。
- 服务发现:Consumer 通过 Consul 定期拉取健康的节点列表,配合负载均衡器使用。
- 动态扩容:新实例注册 → Consul 通知或消费者刷新列表 → 流量自然导入新实例。
- 动态缩容:实例下线或健康检查失败 → Consul 标记不健康 → 消费者刷新后剔除该节点。
- 配置动态下发:Nacos 配置变更 → 应用热加载,无需重启。
今天要验证的核心就是扩容和缩容的过程对调用方完全透明,消费者自动适应。
三、实战:模拟动态伸缩全过程(约 3 小时)
步骤 1:恢复多节点用户服务
编辑 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'],
],
],
为了让第二个端口也服务于 UserService,我们需要一个额外的 @RpcService 实现类。简单地,我们可以复制 UserService 并修改注解中的 server 为 jsonrpc2。创建 app/JsonRpc/UserService2.php:
<?php
namespace App\\JsonRpc;
use Hyperf\\RpcServer\\Annotation\\RpcService;
#[RpcService(name: "UserService", protocol: "jsonrpc-http", server: "jsonrpc2")]
class UserService2 extends UserService
{
// 直接继承,行为一致
}
同样在 dependencies.php 中可能需要绑定,但 Hyperf 会自动扫描注解并注册为服务,因此只要我们配置了 jsonrpc2 服务端,这个类就会被注册为一个 RPC 服务提供者,端口 9503 也会加入 UserService 的节点列表。
注意:此时 UserService 和 UserService2 都实现了 UserServiceInterface?UserService2 继承了 UserService,而 UserService 已经实现了 UserServiceInterface,所以没问题。
步骤 2:确认 Consul 注册配置
在 config/autoload/services.php 的 drivers.consul 中,确保 check 配置存在(昨天已配)。同时,确保 consumers.UserService 使用 registry 指向 Consul,refresh_time 设为较小的值(如 5 秒)以便快速观察。
步骤 3:启动多实例用户服务
重启 Hyperf 应用:
php bin/hyperf.php start
观察控制台输出,应显示 jsonrpc (9502) 和 jsonrpc2 (9503) 均启动。查看 Consul UI (http://localhost:8500),UserService 下应出现两个实例,分别对应 9502 和 9503,且健康状态都为绿色。
步骤 4:验证消费者发现双节点
调用订单接口或文章详情接口(都会远程调用用户服务):
curl http://localhost:9501/orders/1
查看 hyperf.log,负载均衡器日志应显示类似:
RPC负载均衡,可用节点: [{"host":"127.0.0.1","port":9502},{"host":"127.0.0.1","port":9503}]
RPC请求路由到节点: 127.0.0.1:9502
连续请求几次,会看到交替路由到 9502 和 9503,说明双节点负载均衡生效,扩容成功。
步骤 5:模拟缩容——摘除一个节点
暂时停止第二个用户服务节点,我们可以通过修改 server.php 将 jsonrpc2 注释掉,或者更简单:在 Consul API 中手动将第二个节点标记为不健康。但为了真实,我们热停止第二个 JSON-RPC 服务器端口。由于它们运行在同一个 Worker 进程中,无法单独停止端口。我们可以临时注释掉 server.php 中的 jsonrpc2 配置,然后重启服务来模拟缩容。但重启会导致短暂中断,不符合“无感”。更好的方法是:我们在另一个终端中手动向 Consul 发送该节点下线请求,让 Consul 标记为 critical,从而消费者会自动移除它。
使用 API 下线 9503 实例:
首先需要知道该实例的 Service ID。在 Consul UI 中点击 UserService,可以看到每个实例的 ID(通常是类名或随机 ID)。例如可能是 UserService-jsonrpc2-…。我们也可以通过 API 注销它:
# 列出 UserService 的所有实例
curl http://consul:8500/v1/health/service/UserService
# 找到对应 9503 的 ServiceID,然后调用注销
curl -X PUT http://consul:8500/v1/agent/service/deregister/<ServiceID>
注销后,消费者会在下一次刷新(5 秒内)更新节点列表,之后请求只会路由到 9502。观察日志,可用节点列表变成只有一个。
无感验证:在注销前后持续发送请求:
# 在另一个终端循环发送请求
while true; do curl -s http://localhost:9501/orders/1 | jq .; sleep 0.2; done
在注销瞬间,可能会有一次请求失败(如果恰好发往被注销的节点且该节点真的停止了服务),但因为我们的 9503 服务实际并未真正停止进程(只是注销了 Consul),请求仍然能到达,所以不会出错。若要真正模拟宕机,可同时停止 9503 端口的监听(但这里无法单独停止)。不过我们主要展示的是消费者节点列表的变化,这已经证明了动态感知能力。
更极致的模拟:使用两个独立的 Docker 容器分别运行不同的 Hyperf 实例,这样就能真正停止一个容器。受限于环境,我们通过 Consul 注销来等效演示。
步骤 6:结合 Nacos 动态调整限流
在 Nacos 控制台(dev 命名空间)修改配置,将 rate_limit.like_per_minute 从 10 改为 3,发布。
无需重启服务,立即测试点赞接口:
for i in {1..5}; do curl -X POST http://localhost:9501/articles/1/like; echo; sleep 0.5; done
前 3 次应该成功(可能受之前计数影响),之后返回 429,证明动态限流生效。这就是配置中心的威力。
步骤 7:全链路压测与观察
使用 ab 对订单详情接口进行 200 并发请求,查看日志中的负载均衡分布、错误率:
ab -n 200 -c 20 http://localhost:9501/orders/1
观察:
- 请求失败数应为 0。
- 日志中两个用户服务节点被均匀路由(如果都在线)。
- 熔断器没有打开。
- 连接池工作正常。
然后模拟缩容(注销一个节点),再次压测,观察错误率:由于负载均衡器会选择剩余节点,可能刚开始会有个别连接失败(如果注销的同时有请求正在发往那个节点且节点已拒绝),但很快稳定,错误率应该接近 0。说明具有高可用性。
四、成果测试与检验(约 1 小时)
测试清单
| 双节点注册 | Consul UI 或 API 查看 UserService | 两个实例,端口 9502 和 9503,健康 |
| 消费者发现双节点 | 查看 hyperf.log 负载均衡器日志 | 可用节点列表包含两个节点,请求轮询到两个节点 |
| 缩容自动剔除 | 注销 9503 节点,等待刷新,再次请求 | 日志中节点列表只剩 9502,请求不再路由到 9503 |
| 配置动态更新 | 修改 Nacos 中的限流值,测试点赞 | 新阈值生效,无重启 |
| 无感扩容/缩容 | 持续发送请求过程中执行缩容操作 | 请求失败率 < 1%(理想 0) |
| 全链路压测 | ab 压测订单接口 | QPS 稳定,错误率 0,熔断未触发 |
常见问题与定位
- 第二个节点没有注册到 Consul:检查 UserService2 类的 #[RpcService] 注解是否正确,server 名称是否与 server.php 中的配置一致;检查是否触发了 Composer 自动加载(可能需要 composer dump-autoload)。
- 负载均衡器日志不打印:检查日志级别是否为 INFO,确认 CustomRoundRobin 的 select 是否被调用。
- 配置中心更新延迟:Nacos 客户端长轮询间隔可临时调为 1 秒,更快感知变化。
- 端口冲突:确保 9503 未被其他服务占用。
五、今日作业与学习产出
- 对比最初硬编码节点 → 注册中心 + 手动多节点 → 动态伸缩的演变过程。
- 画出完整的服务调用链路图,包含 Nacos 配置下发和 Consul 节点变更的流程。
- 总结服务无感扩容的关键要素:注册中心、健康检查、客户端负载均衡、配置中心协同。
- 思考:如果注册中心本身宕机,消费者还能调用服务吗?(答案:可以,因为消费者有本地缓存。)
- 使用 Docker Compose 扩容命令 启动多个 PHP 容器实例(不同端口)作为独立的用户服务节点,观察 Consul 中自动出现多个实例,并测试真实缩容(docker stop 一个容器)时的无感效果。
- 尝试使用 Nacos 的灰度发布功能,仅对特定消费者下发新配置。
恭喜你完成了第四周的全部内容!经过这一周,你的微服务已经具备了动态服务治理的能力:服务能够自动注册和发现,配置可实时变更,实例可弹性伸缩,整个系统向着生产级迈进了一大步。下周我们将进入 API 网关的世界,将所有这些后端服务统一管理起来。
网硕互联帮助中心

评论前必须登录!
注册