引言
微服务不是银弹——PHP 单体应用改微服务,最常踩的坑是把「服务划分」做成「拆表拆代码」。本文不讲空话,讲落地:什么时候该上微服务、服务怎么划、服务之间怎么通信、分布式事务怎么处理、以及 PHP 生态里微服务的部署与观测。和 /php-microservices-message-queue/ 的消息队列深度互补。
前置:/php-microservices-message-queue/(MQ 与事件驱动)、/php-api-design-rest/(API 设计)、/php-swoole-async/(常驻内存服务)。
目录
- 1. 什么时候才该上微服务
- 2. 服务划分:限界上下文原则
- 3. 服务间通信:同步与异步
- 4. API Gateway:聚合与流量入口
- 5. 服务发现与配置中心
- 6. 分布式事务:Saga 与最终一致
- 7. 服务部署:Docker 与 Kubernetes
- 8. 可观测性:日志、追踪与指标
- 9. 实战:订单系统微服务改造
- 10. 速查表与一句话记忆
- 延伸阅读
1. 什么时候才该上微服务
1.1 微服务的代价
多服务 = 网络延迟 + 部署复杂度 + 一致性难题 + 观测成本
单体 = 简单可靠,但团队大了会「牵一发动全身」
1.2 该上的信号
| 信号 | 说明 |
|---|---|
| 团队 10+ 人 | 独立部署、独立发布 |
| 模块频繁独立演进 | 支付/库存/风控各自迭代 |
| 不同扩展需求 | 某模块要单独扩容/异步 |
| 技术异构 | 某模块需要非 PHP 技术栈 |
记忆:微服务的代价是分布式复杂性;团队小/模块耦合/演进不独立,先保持单体(可用模块化单体)。上微服务要有明确信号,别为拆而拆。
2. 服务划分:限界上下文原则
2.1 DDD 限界上下文
按业务领域划界,而非按技术/表结构:
订单上下文:订单、支付、发货状态
库存上下文:库存、采购、预警
用户上下文:账号、权限、资料
(不按 user 表拆,按业务能力拆)
2.2 划分自查
✓ 该上下文数据独立变更?
✓ 该上下文有独立业务规则?
✓ 对外暴露清晰接口?
✗ 为「复用表」而拆 → 会拆出分布式泥潭
记忆:按 DDD 限界上下文划分服务——以业务能力为界(订单/库存/用户),不是按表或代码结构拆;每个上下文数据独立、接口清晰。
3. 服务间通信:同步与异步
3.1 同步通信(REST/gRPC)
适合实时需要结果的场景:
// REST 调用(Laravel HTTP Client)
$resp = Http::get('http://inventory-service/api/stock/'.$sku);
$stock = $resp->json()['stock'];
// 或 gRPC(protobuf + Spiral RoadRunner 等)
3.2 异步通信(MQ)
适合解耦、可延迟的场景:
// 订单创建后发事件,库存/通知异步处理
event(new OrderCreated($order));
// listener 里
dispatch(new DeductStock($order))->onQueue('inventory');
3.3 选型表
| 场景 | 方式 |
|---|---|
| 需要即时结果 | 同步 REST/gRPC |
| 可延迟、要解耦 | 异步 MQ |
| 最终一致 | 事件 + MQ |
| 高吞吐流水线 | MQ 流式处理 |
记忆:同步(REST/gRPC)拿即时结果、异步(MQ)解耦降耦合;订单这类跨服务流程,尽量事件驱动 + 最终一致。
4. API Gateway:聚合与流量入口
4.1 网关职责
统一入口 → 鉴权/限流/日志 → 路由到各服务 → 聚合返回
4.2 聚合层:BFF 模式
前端需要「订单+商品+用户」组合数据时,网关聚合:
// 网关服务(BFF)
public function orderDetail($orderId) {
$order = Http::get("orders/{$orderId}")->json();
$order['items'] = Http::get("inventory/items", ['ids' => $order['item_ids']])->json();
return $order;
}
4.3 常见网关选型
| 方案 | 语言 | 场景 |
|---|---|---|
| Kong | Lua/Go | 通用 API 网关 |
| Traefik | Go | K8s 原生入口 |
| Laravel BFF | PHP | 自家聚合服务 |
记忆:网关 = 统一入口(鉴权/限流/路由);BFF 聚合层把多服务数据组合成前端需要的形状,减少前端请求次数。
5. 服务发现与配置中心
5.1 服务发现
K8s 下服务名即 DNS,天然服务发现;非 K8s 用注册中心:
K8s Service:inventory-service → 自动负载均衡
Consul / etcd:服务注册 + 健康检查
5.2 配置中心
统一管理各服务配置、支持动态刷新:
// 配置放环境变量/K8s ConfigMap/Consul
$dbHost = config('services.inventory.host'); // 从配置中心拉取
记忆:K8s 里服务发现用 Service DNS 即可;非 K8s 用 Consul/etcd 注册中心;配置用配置中心统一管理、动态刷新。
6. 分布式事务:Saga 与最终一致
6.1 为什么不能「本地事务」
跨服务没有统一事务管理器——订单和库存分属不同库,一个本地事务管不了。
6.2 Saga 模式
订单服务:创建订单(事务1)
→ 发事件 → 库存服务:扣库存(事务2)
→ 若失败 → 补偿:库存回滚 + 订单标记失败
// 扣库存失败时发补偿事件
try {
$inventory->deduct($order);
} catch (DeductFailedException $e) {
event(new OrderCancelled($order)); // 补偿
}
6.3 实现选择
| 方式 | 复杂度 | 一致性 |
|---|---|---|
| 本地事务 + 单库 | 低 | 强 |
| 事件 + 最终一致 | 中 | 最终一致 |
| Saga 编排/协同 | 高 | 最终一致 |
记忆:跨服务事务用 Saga——每个服务本地事务 + 失败补偿事件,达到最终一致;多数业务「事件驱动 + 最终一致」够用,别强行两阶段提交。
7. 服务部署:Docker 与 Kubernetes
7.1 容器化 PHP 服务
FROM php:8.3-fpm
COPY . /app
# 多阶段:依赖、Composer install --no-dev
RUN composer install --no-dev --optimize-autoloader
7.2 K8s 部署清单
apiVersion: apps/v1
kind: Deployment
metadata: { name: inventory-service }
spec:
replicas: 3
selector: { matchLabels: { app: inventory } }
template:
metadata: { labels: { app: inventory } }
spec:
containers:
- name: inventory
image: registry/inventory:latest
env:
- name: DB_HOST
value: postgres:5432
7.3 扩容与发布
kubectl scale deployment/inventory --replicas=5 # 扩缩容
kubectl rollout status deployment/inventory # 滚动发布
记忆:服务用多阶段 Docker 镜像打包(–no-dev 精简依赖);K8s 声明 Deployment(副本数/镜像/环境变量)、Service 做发现、滚动发布 rollout。
8. 可观测性:日志、追踪与指标
8.1 三支柱
| 支柱 | 工具 | 作用 |
|---|---|---|
| 日志 | ELK / Loki | 排障细节 |
| 追踪 | Jaeger / Zipkin | 跨服务调用链 |
| 指标 | Prometheus + Grafana | 性能与健康 |
8.2 分布式追踪(PHP)
// 请求头带 trace_id,贯穿所有服务调用
$resp = Http::withHeaders(['X-Trace-Id' => $traceId])
->get('http://inventory-service/api/stock/'.$sku);
// Jaeger/Zipkin 按 trace_id 串起整条调用链
8.3 指标
// 关键指标:QPS、P95 延迟、错误率、命中率
Prometheus::inc('http_requests_total', ['route' => 'order.create']);
记忆:观测三支柱——日志排障、追踪串调用链、指标看健康;PHP 服务间用 trace_id 头贯穿,Prometheus 暴露 /metrics。
9. 实战:订单系统微服务改造
9.1 改造前(单体)
单一 Laravel 应用:订单/库存/用户一张库
痛点:促销大促时库存模块要独立扩容,做不到
9.2 改造后
用户服务(auth/user API)
订单服务(order create/query,Saga 编排)
库存服务(扣减/查询,Redis 预扣 + 异步落库)
网关 BFF(订单详情聚合)
通信:下单走事件(OrderCreated → 扣库存/发通知)
事务:Saga 补偿(扣库存失败 → 订单取消)
9.3 落地顺序
先划界 → 先抽库存(最大热点)→ 抽订单 → 网关聚合
每步独立部署、回归验证,别一次全拆
记忆:订单系统改造 = 按界抽服务(先抽热点库存)、事件驱动下单、Saga 补偿、BFF 聚合;逐服务抽离独立部署验证,别一次拆完。
10. 速查表与一句话记忆
| 维度 | 关键做法 |
|---|---|
| 该不该上 | 团队/演进/扩展信号 |
| 划分 | 限界上下文 |
| 同步 | REST/gRPC |
| 异步 | 事件 + MQ |
| 入口 | API Gateway / BFF |
| 事务 | Saga 最终一致 |
| 部署 | Docker + K8s |
| 观测 | 日志/追踪/指标 |
一句话记忆:PHP 微服务落地 = 有明确信号才拆(团队 10+/独立演进/独立扩容),按 DDD 限界上下文划分(订单/库存/用户业务界);通信同步 REST/gRPC 拿即时结果、异步事件+MQ 解耦;入口用 API Gateway+BFF 聚合、K8s Service 做发现;跨服务事务用 Saga 补偿到最终一致(别强上两阶段);部署用多阶段 Docker + K8s Deployment 滚动发布;观测三支柱——日志(ELK/Loki)、追踪(Jaeger,trace_id 贯穿)、指标(Prometheus);按「先划界→先抽热点服务→逐步独立部署验证」落地,别一次全拆。
延伸阅读
- /php-microservices-message-queue/ — RabbitMQ/Kafka 与事件驱动
- /php-api-design-rest/ — 服务间 API 设计
- /php-swoole-async/ — 常驻内存服务与协程
- /php-deployment-nginx/ — 服务部署与运维
- /php-caching-redis/ — Redis 缓存与锁
- [[distributed-systems]] — 分布式系统原理
- [[architecture]] — 系统架构设计
- [[kubernetes]] — K8s 部署与编排
- Kong 文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。