预计阅读 20 分钟
分布式网络架构 — 从单体到微服务的网络演进
当系统规模从”一台服务器搞定一切”演变为”数百个服务协同工作”时,网络不再只是传输数据的管道——它成了整个架构的骨骼和血脉。本章将从生产环境的真实挑战出发,带你掌握微服务通信、服务发现、API 网关以及网络故障排查的核心技能。
📋 开篇自测:你已经知道多少?
- 微服务之间的通信方式有哪些?同步通信和异步通信各适用于什么场景?
- 服务发现的两种模式(客户端发现 vs 服务端发现)有什么区别?
- API 网关在微服务架构中扮演什么角色?它与反向代理有什么区别?
一、从单体到微服务:网络视角的演进
1.1 拆分带来的网络挑战
单体架构下,模块之间是本地函数调用——零网络开销。拆分为微服务后,原来的函数调用全部变成了网络调用:
单体: 用户模块 --函数调用(纳秒)--> 订单模块 (同一进程)
微服务:
+----------+
| API 网关 |
+----+-----+
|
+---------------+---------------+
| | |
+----+----+ +-----+-----+ +-----+-----+
| 用户服务 | | 订单服务 | | 支付服务 |
| :8001 | | :8002 | | :8003 |
+----+----+ +-----+-----+ +-----+-----+
函数调用变网络调用后的新问题:
- 延迟: 纳秒 --> 毫秒 (差距百万倍)
- 不可靠: 丢包、超时、连接中断
- 服务发现: 怎么知道目标服务的地址?
- 负载均衡: 多个实例, 请求发给谁?
二、微服务通信模式
2.1 同步通信 vs 异步通信
同步通信 (请求-响应):
服务A --[请求]--> 服务B --[响应]--> 服务A
协议: HTTP/REST, gRPC
场景: 需要立即得到结果 (查询用户, 验证权限)
异步通信 (消息驱动):
服务A --[消息]--> 消息队列 --[消费]--> 服务B
协议: AMQP (RabbitMQ), Kafka
场景: 不需要即时结果 (发邮件, 生成报表)
常见组合: 外部流量 --[REST]--> API网关 --[gRPC]--> 内部微服务
2.2 消息队列的两种模型
点对点 (Queue):
生产者 --[消息]--> [ 队列 ] --[消息]--> 消费者
一条消息只被一个消费者处理 (任务分发)
发布-订阅 (Topic):
生产者 --[消息]--> [ Topic ]
|
+-------+-------+
消费者组A 消费者组B
(订单服务) (通知服务)
一条消息可被多个消费者组独立消费 (事件广播)
2.3 实际场景:电商下单流程
客户端 --POST /orders(REST)--> API网关 --gRPC--> 订单服务
|
+--[gRPC同步]--> 用户服务: 验证身份 |
+--[gRPC同步]--> 库存服务: 锁定库存 |
+--[gRPC同步]--> 支付服务: 发起支付 |
+--[异步消息]--> 消息队列 |
| |
+------+------+ |
| | v
物流服务 通知服务 返回订单结果
创建配送单 发送短信
设计原则: 需要即时结果 --> 同步 | 不影响主流程 --> 异步
🤔 想一想 如果支付服务成功了但发送消息到队列失败了,会出现什么问题?分布式事务要如何保证数据一致性?
三、服务发现
3.1 为什么需要服务发现
容器化环境中,服务实例随时动态增减,IP 地址不断变化。硬编码地址的方式彻底失效了:
硬编码: order_url: http://192.168.1.10:8002 (容器重启IP就变)
服务发现:
启动时 --> 自动向注册中心注册地址
调用时 --> 从注册中心查询可用实例
宕机时 --> 自动从注册中心移除
3.2 两种发现模式
客户端发现 (Client-Side):
服务A --1.查询--> 注册中心(Consul/Nacos/etcd) --2.返回[B1,B2,B3]-->
服务A --3.选择B2(客户端负载均衡)--> 服务B2
优点: 客户端可实现智能负载均衡
缺点: 每种语言都要实现发现逻辑
常见注册中心: Consul, etcd, Nacos(国内广泛使用), ZooKeeper
服务端发现 (Server-Side):
服务A --1.请求--> 负载均衡器/DNS --2.转发--> 服务B2
|
查询注册中心
代表: Kubernetes Service + kube-proxy
优点: 客户端无感, 语言无关
缺点: 多一跳网络开销
3.3 Kubernetes 中的服务发现
Kubernetes 集群内:
Pod(用户服务)
| 访问 http://order-service:8080/api/orders
v
CoreDNS --> 解析为 ClusterIP 10.96.0.100
v
kube-proxy (iptables/IPVS) --> 负载均衡到后端Pod
v
+--------+ +--------+ +--------+
| 订单Pod | | 订单Pod | | 订单Pod |
+--------+ +--------+ +--------+
只需用服务名即可访问, 无需关心具体IP和端口
四、API 网关
4.1 为什么需要 API 网关
没有网关 (客户端直连每个服务):
APP --> user.api.com:8001, order.api.com:8002, pay.api.com:8003
问题: 地址管理混乱, 认证/限流/日志每个服务重复实现
有网关 (统一入口):
APP --> api.example.com --> API网关 --> 路由到具体服务
网关统一处理: 认证、限流、路由、协议转换、日志
4.2 核心功能一览
+------------------------------------------------------------+
| API 网关 |
+------------------------------------------------------------+
| 1. 请求路由: /api/users/** --> 用户服务 |
| 2. 认证鉴权: 验证JWT --> 提取用户信息 --> 注入请求头 |
| 3. 限流熔断: 令牌桶限流 + 下游错误率>50%自动熔断 |
| 4. 协议转换: 外部REST --> 内部gRPC |
| 5. 请求聚合: 一个请求 --> 并发调多个服务 --> 合并响应 |
| 6. 可观测性: 请求日志、链路追踪ID注入、指标采集 |
+------------------------------------------------------------+
4.3 请求聚合(BFF 模式)
不聚合 (客户端串行3次请求):
APP --> 用户服务(50ms) --> 订单服务(80ms) --> 推荐服务(120ms)
总耗时: 250ms, 请求次数: 3
聚合 (网关并发调用):
APP --1次请求--> 网关 --并发--> 用户(50ms) + 订单(80ms) + 推荐(120ms)
<-- 合并响应 --
总耗时: 120ms, 请求次数: 1
4.4 限流策略
令牌桶: 桶容量100, 每秒补10个. 有令牌放行, 无令牌拒绝(429)
允许突发流量(桶满可瞬间处理100请求)
滑动窗口: 统计最近60秒请求数, 超过阈值拒绝
比固定窗口更平滑
多维度限流:
- 全局: 总QPS <= 10000
- 用户: 每用户每分钟 <= 100
- IP: 每IP每秒 <= 50
- 接口: /api/search 每秒 <= 200
五、网络容错与弹性设计
5.1 超时、重试、熔断、降级
这四个机制是分布式系统可靠性的四大支柱:
1. 超时 -- 快速失败, 不无限等待
连接超时: 1-3s | 读取超时: 按接口特性 | 总超时: 含重试
传递原则: 客户端(3s) > 网关(2.5s) > 服务A(2s) > 服务B(1.5s)
2. 重试 -- 应对瞬时故障
只对可重试错误重试(超时/503), 不对业务错误重试(400/401)
退避: 指数退避+抖动 (1s -> 2.3s -> 4.7s)
重试预算: 重试请求不超过总请求的10%, 防止重试风暴
3. 熔断 -- 主动断开故障链路
+------+ 失败率>50% +------+ 冷却30s +--------+
| 关闭 | ----------> | 打开 | -------> | 半开 |
| 正常 | | 快速 | | 探测 |
| 转发 | <---------- | 失败 | <------- | 少量 |
+------+ 探测成功 +------+ 探测失败 | 请求 |
+--------+
4. 降级 -- 返回"够用"的结果
推荐服务不可用 --> 返回热销Top10(缓存)
Level1: 上次缓存 | Level2: 静态默认 | Level3: 隐藏模块
六、网络故障排查实战
6.1 分层排查法
问题: "服务A调用服务B超时"
第1步(网络层): ping <IP> --> 不通? 防火墙/路由
第2步(传输层): nc -zv <IP> <端口> --> 不通? 服务未启动
第3步(DNS): dig <服务名> +short --> 失败? DNS配置
第4步(应用层): curl -v <url>/health --> 5xx? 查服务日志
第5步(链路): 查看TraceID瀑布图 --> 定位最慢Span
6.2 常用排查命令
+------------------+------------------------------------------+
| 排查目标 | 命令 |
+------------------+------------------------------------------+
| 连通性 | ping <host> |
| 路由路径 | traceroute <host> |
| 端口监听 | ss -tlnp | grep <port> |
| TCP连接状态 | ss -tnp | grep <host> |
| DNS解析 | dig <domain> +short |
| HTTP调试 | curl -v <url> |
| 抓包分析 | tcpdump -i any host <ip> and port <port> |
| 连接数统计 | ss -s |
+------------------+------------------------------------------+
6.3 实战案例:排查间歇性超时
现象: 订单服务调用用户服务, 约5%请求超时(恰好3s)
排查:
1. 用户服务监控: CPU 30%, 内存 60% --> 资源正常
但 P99延迟 2800ms --> 接近3s超时阈值!
2. 慢请求日志: 超时请求都涉及"查用户详情+权限"
3. 链路追踪:
订单服务 --> 用户服务(2900ms)
+--> MySQL(50ms)
+--> 权限服务(2800ms) <-- 瓶颈!
4. 权限服务: 新版本有一个未加索引的查询
数据量大时 10ms --> 3000ms
解决: 紧急回滚 | 短期加索引 | 长期加缓存(TTL=5min)
教训: 上线前做性能回归, 关键链路要有缓存兜底
6.4 分布式追踪
客户端 --X-Trace-ID:abc123--> API网关[5ms]
|
订单服务[150ms]
|-- 用户服务[30ms]
|-- 库存服务[45ms]
|-- 支付服务[200ms]
|-- 银行网关[180ms]
瀑布图:
|-- 网关 (5ms) --------------------------------|
|-- 订单 (150ms) ---------------------------|
|-- 用户 (30ms) --|
|-- 库存 (45ms) ----|
|-- 支付 (200ms) ----------------------|
|-- 银行 (180ms) --------------|
工具: Jaeger, Zipkin, SkyWalking, OpenTelemetry
七、章节小结
- 微服务通信分为同步(REST、gRPC)和异步(消息队列)两大类,选择取决于是否需要即时结果。
- 服务发现解决了”如何找到目标服务”的问题,Kubernetes 内置了基于 DNS 的服务发现。
- API 网关是微服务的统一入口,承担路由、认证、限流、协议转换等横切关注点。
- 容错四件套(超时、重试、熔断、降级)是分布式系统可靠性的基石。
- 故障排查遵循分层法,配合分布式追踪定位瓶颈。
思考题
-
在微服务架构中,如果服务 A 同步调用服务 B,服务 B 又同步调用服务 C,形成了长调用链。当服务 C 出现高延迟时,你会采取哪些措施防止级联故障?
-
一个电商系统在大促期间出现大量订单支付超时。你作为值班工程师,会按什么步骤排查?
📝 结尾自测:检验你的收获
- 同步通信和异步通信分别适合什么场景?各举两个例子。
- 客户端发现和服务端发现的核心区别是什么?各适合什么规模的系统?
- 熔断器的三个状态是什么?它们之间如何转换?
- API 网关的请求聚合有什么优势?什么场景下不适合使用?
- 排查微服务间超时问题时,你会按什么顺序逐层排查?
下一章预告:恭喜你走到了这里!下一章是本课程的结束篇,我们将回顾整个课程的知识脉络,用一张知识图谱把所有章节串联起来,并为你规划从网络原理到架构思维的进阶路线。
购买课程解锁全部内容
网络通信第一课:10 章掌握计算机网络
¥29.90