PHP 微服务架构实战:从单体应用到分布式系统的演进之路
别为了"微服务"而微服务。这篇文章讲的是一个真实、克制、可落地的演进过程。
一、背景:单体应用撑不住了
故事从三年前开始。
- 技术栈:PHP 8.1 + Laravel + MySQL + Redis
- 部署方式:单台服务器,Nginx + PHP-FPM
- 业务:电商系统(订单、商品、用户、支付、后台)
早期很爽:
- 一个仓库
- 一次部署
- 一个数据库
- 开发效率高
问题随着业务增长逐渐暴露:
| 问题 | 表现 |
|---|---|
| 代码耦合 | 改一个订单逻辑,用户模块也跟着发版 |
| 部署风险 | 一次发布影响全站 |
| 性能瓶颈 | 订单接口压力一大,整个站点变慢 |
| 技术栈僵化 | 想换框架 / 语言,牵一发动全身 |
| 团队协作 | 十几个人挤在一个仓库,冲突不断 |
单体不是原罪,但单体 + 规模增长,一定会出问题。
二、演进原则:先拆"边界",再拆"服务"
很多人一上来就问:
"我要用 gRPC?还是 HTTP?用 Kubernetes 吗?"
错。
微服务第一件事不是选技术,而是划边界。
我们的拆分原则(DDD 味道)
- 按业务领域拆分,而不是按"技术层"
- 每个服务只对自己的数据负责
- 服务之间通过 API / 事件 通信
- 能不拆就不拆,拆必有理
第一版拆分结果
| 服务 | 职责 |
|---|---|
| User Service | 用户、账号、权限 |
| Product Service | 商品、库存 |
| Order Service | 订单、支付 |
| Notification Service | 短信、邮件、站内信 |
| API Gateway | 鉴权、聚合、限流 |
三、第一步:数据库先行拆分(最关键)
数据库不拆,微服务就是伪服务。
单体阶段
db/
├── users
├── products
├── orders
└── payments
拆分后
- user_db
- product_db
- order_db
- notification_db
每个服务 独享数据库,禁止跨库 JOIN。
❌ 错误做法:订单服务直接连用户库查用户
✅ 正确做法:订单服务调用用户服务 API
四、通信方式:什么时候用 HTTP,什么时候用 RPC?
对外:HTTP + JSON(友好)
-
给前端
-
给第三方
-
给管理后台
GET /api/v1/orders/123
对内:gRPC(高效)
我们选择 gRPC + Protobuf:
-
强类型
-
性能高
-
自动生成客户端代码
-
PHP 8 + Swoole / RoadRunner 支持良好
service UserService {
rpc GetUser (GetUserRequest) returns (User);
}
为什么不是纯 HTTP?
- 内部调用 QPS 高
- 需要类型安全
- 减少序列化开销
五、API Gateway:微服务的"门面"
前端 不直连 微服务。
[ Web / App ]
|
API Gateway
|
---------------------
| Order | User | Product | Notification
---------------------
Gateway 的职责
- 统一鉴权(JWT / OAuth)
- 请求聚合(BFF)
- 限流 / 熔断
- 灰度发布
- 日志 & 监控
我们用的是 Nginx + Lua(OpenResty),后来逐步引入 Envoy。
六、服务注册与发现:别写死 IP
单体时代:
define('USER_SERVICE_HOST', '10.0.0.12');
微服务时代:这是灾难。
解决方案
- Consul
- etcd
- Nacos
服务启动时注册:
user-service: 10.0.0.12:8001
user-service: 10.0.0.13:8001
客户端通过服务名调用:
user-service/proto.UserService
七、分布式事务:最难的那个坑
订单创建流程:
- 创建订单
- 扣减库存
- 扣用户余额
- 发通知
任何一个失败,都要回滚。
我们最终选择的方案:Saga 模式
-
不追求强一致性
-
追求最终一致性
-
每个服务提供 补偿接口
创建订单
→ 扣库存
→ 扣余额
→ 失败
→ 回滚库存
→ 取消订单
微服务里,"事务"是业务设计问题,不是数据库问题。
八、日志、监控与追踪:没有可观测性,就是瞎子
日志
- 统一格式(JSON)
- 集中收集(ELK / Loki)
指标
- QPS
- 响应时间
- 错误率
- 服务依赖
Prometheus + Grafana
链路追踪
- TraceID 贯穿所有服务
- 用 Jaeger / Zipkin
一个请求在微服务里"飞一圈",能清晰看到:
- 哪个服务慢
- 哪个 SQL 拖后腿
- 哪次 RPC 失败
九、部署:从 FTP 到 Kubernetes
单体时代
- FTP 上传
git pullphp artisan optimize
微服务时代
-
Docker 镜像
-
CI/CD(GitLab CI / GitHub Actions)
-
Kubernetes
-
Helm
replicas: 3
resources:
limits:
memory: 512Mi
cpu: "0.5"
十、PHP 在微服务里的定位
很多人问:
"PHP 适合做微服务吗?"
适合,但要认清角色。
PHP 擅长的
- 业务编排
- CRUD API
- 后台管理
- BFF 层
PHP 不擅长的
- 长连接
- 高并发流式处理
- 底层中间件
因此我们的架构是:
[ PHP 微服务 ] → 业务
[ Go / Java ] → 网关 / 中间件 / 高并发组件
技术栈为业务服务,而不是反过来。
十一、踩过的坑(血泪总结)
- 服务拆太细 → 运维成本爆炸
- 过早上 Kubernetes → 学习曲线太陡
- 共享数据库 → 拆了等于没拆
- 忽略监控 → 出问题只能靠猜
- 分布式事务想用 2PC → 性能直接跪
- 前端直接调 N 个服务 → 延迟高、耦合重
十二、最终架构全景图(文字版)
┌────────────┐
│ Web │
└─────┬──────┘
│
┌──────▼──────┐
│ API Gateway │
│ (Envoy) │
└──┬───┬───┬──┘
│ │ │
┌──────────┘ │ └──────────┐
│ │ │
┌───────▼───────┐┌─────▼─────┐┌───────▼───────┐
│ Order Service ││ User Svc ││ Product Svc │
│ PHP + Swoole ││ PHP + FPM ││ PHP + FPM │
└───────┬───────┘└─────┬─────┘└───────┬───────┘
│ │ │
└───────────────┼───────────────┘
│
┌───────▼───────┐
│ Message │
│ Queue │
│ (RabbitMQ) │
└───────────────┘
十三、什么时候不该用微服务?
如果你符合以下情况,请慎用微服务:
- 团队 < 10 人
- 业务不稳定,变化极快
- 没有专职运维 / DevOps
- 没有监控体系
- 单体应用还能跑得很稳
微服务是解决复杂度问题的工具,不是"高级感"的象征。
十四、总结:演进,而不是革命
我们的路径是:
单体应用
→ 模块化解耦
→ 数据库拆分
→ 核心服务独立
→ API Gateway
→ 容器化
→ Kubernetes
每一步都踩在痛点上,而不是踩在技术热点上。
写在最后
PHP 从来不是微服务时代的"弃子",而是一个务实的选择。
真正决定系统成败的,不是你用了多少"高大上"的技术,而是:
- 你是否理解业务边界
- 你是否尊重运维成本
- 你是否愿意循序渐进