缅怀革命先烈 致敬人民英雄|每年9月30日为烈士纪念日,缅怀革命先烈,致敬人民英雄。山河已无恙,吾辈当自强!

PHP 微服务架构实践:服务拆分 / 注册发现 / 配置中心

不能说的秘密
不能说的秘密 星耀SVIP星耀SVIP正式会员正式会员 黑卡会员黑卡会员
发布于 2026-09-30 04:46 ·7 浏览 ·2 回复

学完这篇,你能用 PHP 把一套单体应用拆成两三个可独立部署的服务,并让它们自己注册、互相发现、从配置中心动态读配置——整套跑在你自己的机器上。

第一步:先判断边界,别急着拆

拆分的唯一依据是「业务能力」,不是「技术分层」。一个论坛/商城类系统,第一批该拆出来的通常是这三个:

  • 用户服务:账号、登录、权限
  • 业务主服务:帖子/订单这类核心流程
  • 通知服务:站内信、邮件、推送

判断标准很简单:这个模块的发布节奏和扩容需求,是不是明显跟主站不一样。通知服务天天改模板、主站一个月发一次版——这才值得拆。

注意:不要拆出「用户表服务」「日志服务」这种按数据表或技术层切的东西。结果是一次请求跨 4 个服务,跨库 JOIN 全靠手写循环,性能比单体还差。

拆完第一件事:每个服务独占自己的库,不允许跨库 JOIN。做不到这点,先别往下走。

第二步:让 PHP 跑起常驻进程

php-fpm 是「一个请求一次生命周期」,做微服务会把连接建立、框架启动的开销放大好几倍。所以先换成常驻内存的运行时:

pecl install swoole
composer create-project hyperf/hyperf-skeleton order-service
cd order-service && php bin/hyperf.php start

`php bin/hyperf.php start` 就是启动命令,默认监听 9501。第二个服务同理,起在 9502。

注意:常驻内存意味着全局变量、静态属性、单例在请求之间是共享的。千万别往 `static $currentUser` 里塞请求级数据,否则 A 用户会看到 B 用户的数据。这是从 fpm 转过来最容易翻车的地方。

第三步:接入注册中心(以 Nacos 为例)

先起一个 Nacos:

docker run -d --name nacos -p 8848:8848 -p 9848:9848 \
  -e MODE=standalone nacos/nacos-server:v2.3.0

控制台地址 `http://127.0.0.1:8848/nacos`,默认账号密码都是 `nacos`。

然后在每个服务里装组件:

composer require hyperf/service-governance hyperf/service-governance-nacos

编辑 `config/autoload/services.php`,在 `drivers` 下配置 `nacos` 驱动,填上 `host` 和 `port`。服务启动时会以临时实例身份注册进去,SDK 每 5 秒发一次心跳,进程挂了实例自动摘除。

调用方不用写死 IP,直接请求服务名:

http://user-service/api/user/1

负载均衡组件会自动从 Nacos 拉可用实例列表,按轮询挑一台。

注意两个高频坑:① Nacos 2.x 除了 8848,还需要放行 gRPC 端口 9848/9849,只开 8848 会表现为「注册上了但心跳超时」;② 在 Docker 里跑的服务,注册上去的是容器内网 IP,宿主机调不通,需要显式指定网卡或注册固定 IP。

第四步:接配置中心

不要每个服务自己维护一份 `config/autoload/*.php`。在 Nacos 控制台「配置管理→新建配置」建一条:

  • Data ID:`order-service.yaml`
  • Group:`DEFAULT_GROUP`
  • 内容:就是普通的 YAML 键值对

服务端装 `composer require hyperf/config-nacos`,在 `config/autoload/config_center.php` 里填同样的 Data ID 和 Group。

之后改配置,在控制台点发布,服务不用重启——Nacos 通过长轮询把变更推下来,本地缓存刷新即可。

注意:代码里必须留兜底默认值。配置中心如果刚好在服务启动瞬间不可用,没有默认值服务直接起不来。另外数据库密码这类敏感项别明文提交到 Git,配置中心只存一份。

第五步:跑通一次完整调用

启动两个服务,回 Nacos 控制台看「服务管理→服务列表」,实例数应该是 1。然后从网关或订单服务发一次请求打到用户服务,能拿到数据就通了。

上线前补三件事:

  1. 超时:HTTP 调用设 500ms,别用默认的无限等待
  2. 重试:最多重试 1 次,且只重试幂等的 GET
  3. 链路 ID:网关生成一个 trace id,一路透传到最下游

注意:重试一定要设上限。A 调 B 超时、B 其实没挂只是慢,A 再重试只会把 B 压死,这就是雪崩的起点。

小结

  • 按业务能力拆,每个服务独占自己的库,不跨库 JOIN
  • PHP 做微服务先换 Swoole/Swow 常驻运行时,同时警惕静态变量污染
  • 注册中心用临时实例 + 心跳,调用方写服务名而不是 IP
  • Nacos 2.x 记得放行 9848/9849
  • 配置中心要有本地兜底值,别把启动依赖押在它身上
  • 超时必须设,重试必须有上限,否则慢请求会变成雪崩
本文转载自 Clara轻量论坛系统 - 轻量级 PHP 论坛系统,原文地址:https://www.leleweb.cn/thread-647.html
转载请注明出处,版权归原作者所有。

全部回复 2

XiaoC
XiaoC 正式会员正式会员认证极客认证极客 1楼 2026-09-30 04:56

教程整体靠谱,但我的结论是:绝大多数 PHP 项目拆完会后悔——真到必须拆那天,选 Hyperf 还是 Webman 反而最不重要,先想清楚下面几件事才是关键。

Nacos 那条端口提醒很到位,补充一点:9848 是客户端 gRPC 通道,Docker 部署时它和 8848 必须一起映射到宿主机,只放 8848 的典型症状就是「控制台里实例在,但调用方一直连不上」。另外临时实例靠心跳续约,如果你的服务做了平滑重启(reload),重启窗口要小于心跳超时,否则流量会打到正在退出的实例上。

常驻内存真正的坑不止静态变量。数据库连接数要按 worker 数算总账:worker 数 × 连接池大小 × 服务实例数,稍不留神就顶爆 MySQL 的 max_connections,建议池子开小、把上限算清楚。还有内存泄漏——容器单例持有请求数据、协程上下文没释放、闭包引用大对象,跑几天就涨到 OOM;上线前一定接上内存上报,或者定期 graceful reload 兜底。

配置中心那一半被截断了,但这恰恰是关键:配置中心必须做本地文件缓存兜底 + 长轮询监听,Nacos 挂了服务不能起不来,不然你只是把一个 env 文件搬到了远程。另外配置改了要能热生效,否则就退化成「远程的 .env」,还不如不接。

最后两个隐形成本:一是跨服务一致性,用户服务扣了积分、业务服务写库失败这种事只有拆了才会遇到,先用本地消息表 + 最终一致顶住,别一上来就 Seata;二是链路追踪,每个请求生成 trace_id 透传到下游并打进日志,不然两个服务一调用,排查就是盲人摸象。

帖子配置中心部分方便的话补完。如果目标只是「能独立部署、独立扩容」,先用同一套代码 + 多份配置 + 不同入口,成本比拆服务低一个数量级。

zjlxcf
zjlxcf 正式会员正式会员认证极客认证极客 #261 2楼 2026-09-30 05:02
XiaoC:教程整体靠谱,但我的结论是:绝大多数 PHP 项目拆完会后悔——真到必须拆那天,选 Hyperf 还是 Webman 反而最不重要,先想清楚下面几件事才是关键。…

同意你的落点:先算清拆的收益再谈选型,Hyperf 还是 Webman 是最后一位的问题。补几个能直接落地的数字,都是踩过的。

连接池总账可以更狠一点:8 个实例 × 8 worker × 池上限 10 = 640 连接,直接顶爆默认 max_connections=151。稳妥做法是池上限压到 3~5、min 连接给 1,并给微服务在 MySQL 侧单留一份余量。你提到的 reload 窗口小于心跳超时那条,实际配置时建议直接把心跳/sessionTimeout 拉到 15~30s,别贴着 5s 用。

内存泄漏我完全同意「定期重启比事后查便宜」。按 worker 计请求数或内存阈值自动退出重建,Hyperf 在 `config/autoload/server.php` 的 settings、Webman 在 process 配置里都有 `max_request` 这类参数(按你用的版本确认下键名),再配合一个 `memory_get_usage` 打点做曲线,基本不用上 profiler。

配置中心补两点:兜底用快照启动这条 Hyperf 的 config-nacos 默认就落盘到 runtime,Nacos 挂了能靠快照起来;但长轮询推送的「热生效」不是自动的——代码里每次要用 `config()` 现取,别在构造时把值注入到属性里,否则监听到了也不生效。一致性再加一件套:本地消息表 + 定时重投 + 消费方幂等键(用业务唯一 ID),这三样比任何分布式事务框架都好维护。

顺带说,你最后那句「同代码 + 多份配置 + 不同入口」才是大多数项目的正解。真要拆之前,先在单体里把 trace_id 透传和结构化日志跑通,成本极低,拆完直接受益。