hyperf跨机房服务发现必须清空nodes数组并启用consul/nacos服务发现,配置region感知的regionawareloadbalancer、按region隔离httpclient连接池,并确保熔断器存储按region分库。

Hyperf 跨机房服务发现必须关掉静态 nodes
跨机房部署时,rpc_client.php 里 nodes 数组非空,Hyperf 就会跳过 Consul 或 Nacos,直接硬连列表里的地址——而这些地址往往写的是 127.0.0.1 或容器名但没配 DNS,结果就是调用永远走本地或解析失败。
正确做法是:把 nodes 设为空数组 [],强制走服务发现。再确认 config/autoload/consul.php 中 uri 指向的是能被所有机房节点访问的 Consul Server 地址(比如公网 VIP 或内网 LB),且 datacenter 字段按机房区分,例如北京设 bj、上海设 sh。
- Consul 的
service.tags必须带上机房标识,如["region:bj", "env:prod"] - Hyperf 客户端初始化时要传
tags过滤,否则默认拉全量服务列表 - 别依赖
ping判断连通性,用nc -zv consul-server 8500验证 TCP 层可达
就近路由逻辑得写在 LoadBalancer 里,不是靠注解
Hyperf 默认的 RandomLoadBalancer 或 RoundRobinLoadBalancer 不识别机房标签,它只会随机挑一个健康节点——哪怕对方在新加坡,你的请求也照发不误。
必须自定义一个 RegionAwareLoadBalancer,继承 AbstractLoadBalancer,重写 select() 方法:
- 先从
$nodes中筛选带当前 region 标签的节点(如region:bj) - 若存在,从中随机选一个;若为空,再 fallback 到其他 region 节点(避免完全不可用)
- region 值不能硬编码,应从环境变量读取,如
getenv('HYPERF_REGION') ?: 'bj'
然后在 rpc_client.php 中指定:'load_balancer' => \App\LoadBalancer\RegionAwareLoadBalancer::class。
HTTP 调用也要做 region 感知,别只盯 RPC
很多团队只改了 RPC,忘了 HttpClient 调用第三方 API 或内部 HTTP 服务时,照样跨机房乱发请求。这时候靠 base_uri 静态配置根本不够用。
推荐两种落地方式:
- 用
HttpClient的base_uri+ 环境变量拼接,如https://api-{region}.example.com,启动前注入region=bj - 更稳妥的是封装一个
RegionHttpClient,构造时根据region动态 setbase_uri,并在请求头加X-Region: bj供下游识别 - 如果下游是统一网关,确保网关层能根据该 header 做路由,否则前端传了也没用
连接池必须按 region 隔离,否则复用失效
Hyperf 默认的 HttpClient 连接池是全局共享的。MaxIdleConnsPerHost 如果设为 20,那 api-bj.example.com 和 api-sh.example.com 会共用这 20 个空闲连接——跨机房建连频繁失败时,连接池不断重建,反而拖慢同机房请求。
解决方法是在 config/autoload/client.php 中为不同 region 单独配连接池:
'http' => [
'pool' => [
'bj' => ['max_connections' => 30, 'min_connections' => 5],
'sh' => ['max_connections' => 30, 'min_connections' => 5],
],
'handler' => \Hyperf\HttpClient\CoroutineHandler::class,
// 其他参数...
],
调用时显式指定 pool 名:$client->setPoolName(getenv('HYPERF_REGION'))。注意:这个 pool name 必须和配置里的 key 严格一致,大小写敏感。
最容易被忽略的一点:如果你用了 hyperf/circuit-breaker,它的存储(如 Redis)也得按 region 分库或加前缀,否则熔断状态在不同机房间互相污染。











