系统设计与通信实战指南:从CDN到服务网格
前言:通信是分布式系统的血液
分布式系统的核心难题之一就是通信。服务之间怎么调用、数据怎么同步、故障怎么隔离——每个问题都决定了系统的稳定性。
一、CDN 内容分发网络
1.1 CDN 的核心价值
- 加速:用户从最近节点获取内容
- 减负:源站压力降低
- 高可用:多节点冗余
- 安全:隐藏源站 IP,抗 DDoS
1.2 CDN 工作原理
1.3 CDN 配置
# 静态资源缓存
location ~* \.(jpg|png|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 不缓存
location / {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
1.4 CDN 优化策略
| 策略 | 说明 |
|---|---|
| 文件名加 hash | app.abc123.js 实现永久缓存 |
| HTML 不缓存 | 保证用户拿到最新入口 |
| CDN 预热 | 发布前主动推送 |
| 缓存刷新 | 紧急情况下手动刷新 |
1.5 CDN 的坑
坑一:缓存更新不及时
用户看到旧版本。解决:文件名加 hash,HTML 不缓存。
坑二:跨域问题
# CORS 配置
add_header Access-Control-Allow-Origin "https://example.com";
add_header Access-Control-Allow-Methods "GET, OPTIONS";
坑三:HTTPS 证书
CDN 节点也要配证书。用 Let’s Encrypt + CDN 服务商的证书托管。
二、gRPC
2.1 gRPC vs REST
| 维度 | gRPC | REST |
|---|---|---|
| 协议 | HTTP/2 | HTTP/1.1 |
| 序列化 | Protobuf | JSON |
| 性能 | 快 5-10 倍 | 慢 |
| 流式 | 支持 | 不支持 |
| 浏览器 | 需要 gRPC-Web | 原生支持 |
2.2 Protobuf 定义
// user.proto
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
rpc CreateUsers (stream CreateUserRequest) returns (CreateUsersResponse);
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
message GetUserRequest {
int32 id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
int64 created_at = 4;
}
2.3 Python gRPC 实现
# 服务端
import grpc
from concurrent import futures
class UserService(user_pb2_grpc.UserServiceServicer):
def GetUser(self, request, context):
user = db.get_user(request.id)
return user_pb2.User(
id=user.id,
name=user.name,
email=user.email
)
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
# 客户端
channel = grpc.insecure_channel('localhost:50051')
stub = user_pb2_grpc.UserServiceStub(channel)
response = stub.GetUser(user_pb2.GetUserRequest(id=1))
print(response.name)
2.4 gRPC 拦截器
# 服务端拦截器(鉴权、日志)
class AuthInterceptor(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
metadata = handler_call_details.invocation_metadata
token = next((v for k, v in metadata if k == 'authorization'), None)
if not self._verify_token(token):
return self._unary_unary_rpc_terminator
return continuation(handler_call_details)
server = grpc.server(
futures.ThreadPoolExecutor(max_workers=10),
interceptors=[AuthInterceptor()]
)
2.5 gRPC 流式
# 服务端流式(适合大数据量)
class UserService:
def ListUsers(self, request, context):
for user in db.get_all_users():
yield user_pb2.User(id=user.id, name=user.name)
# 双向流式(适合聊天)
class ChatService:
def Chat(self, request_iterator, context):
for message in request_iterator:
response = process_message(message)
yield response
2.6 gRPC 的坑
坑一:浏览器不支持
需要 gRPC-Web 或 gRPC-Gateway 转 REST。
坑二:Protobuf 兼容性
修改 .proto 要注意向后兼容。只加字段不删,废弃字段用 reserved。
坑三:调试困难
二进制协议不好抓包。用 grpcurl 或 BloomRPC。
三、GraphQL
3.1 GraphQL vs REST
| 维度 | GraphQL | REST |
|---|---|---|
| 数据获取 | 客户端指定 | 服务端定义 |
| 请求次数 | 一次获取多资源 | 多次 |
| 版本 | 无版本 | v1, v2 |
| 学习曲线 | 陡 | 平 |
| 缓存 | 复杂 | HTTP 缓存 |
3.2 GraphQL Schema
type User {
id: ID!
name: String!
email: String
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users(limit: Int = 10): [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User!
}
type Subscription {
postCreated: Post!
}
3.3 GraphQL Resolver
import strawberry
@strawberry.type
class User:
id: strawberry.ID
name: str
email: str
@strawberry.type
class Query:
@strawberry.field
def user(self, id: strawberry.ID) -> User:
return db.get_user(id)
@strawberry.field
def users(self, limit: int = 10) -> list[User]:
return db.get_users(limit)
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user(self, name: str, email: str) -> User:
return db.create_user(name, email)
schema = strawberry.Schema(query=Query, mutation=Mutation)
3.4 N+1 问题
# 错误:N+1 查询
def resolve_posts(user):
return [db.get_post(pid) for pid in user.post_ids] # N 次查询
# 正确:用 DataLoader 批量查询
from strawberry.dataloader import DataLoader
async def load_posts(post_ids):
posts = db.get_posts_batch(post_ids)
return [posts.get(pid) for pid in post_ids]
post_loader = DataLoader(load_posts)
def resolve_posts(user):
return [post_loader.load(pid) for pid in user.post_ids]
3.5 GraphQL 的坑
坑一:深度查询
客户端可以无限嵌套查询。加深度限制:
from graphql import validate
from graphql.depth_limit import depth_limit_validator
schema = build_schema(...)
validate(query, schema, [depth_limit_validator(max_depth=10)])
坑二:复杂查询
复杂查询可能打垮数据库。加复杂度分析。
四、事件溯源(Event Sourcing)
4.1 传统 vs 事件溯源
| 维度 | 传统 CRUD | 事件溯源 |
|---|---|---|
| 存储 | 当前状态 | 事件序列 |
| 历史 | 丢失 | 完整保留 |
| 回放 | 不可能 | 可重放 |
| 审计 | 难 | 天然支持 |
4.2 事件溯源实现
from dataclasses import dataclass
from datetime import datetime
from typing import List
@dataclass
class Event:
aggregate_id: str
event_type: str
data: dict
timestamp: datetime
class EventStore:
def __init__(self):
self.events = {} # 实际用数据库
def append(self, aggregate_id: str, event: Event):
if aggregate_id not in self.events:
self.events[aggregate_id] = []
self.events[aggregate_id].append(event)
def get_events(self, aggregate_id: str) -> List[Event]:
return self.events.get(aggregate_id, [])
# 订单聚合
class OrderAggregate:
def __init__(self):
self.id = None
self.status = None
self.items = []
def apply_event(self, event: Event):
if event.event_type == 'OrderCreated':
self.id = event.data['order_id']
self.items = event.data['items']
self.status = 'created'
elif event.event_type == 'OrderPaid':
self.status = 'paid'
elif event.event_type == 'OrderShipped':
self.status = 'shipped'
@classmethod
def from_events(cls, events: List[Event]):
order = cls()
for event in events:
order.apply_event(event)
return order
# 使用
store = EventStore()
# 创建订单(产生事件)
store.append('order-123', Event(
'order-123', 'OrderCreated',
{'order_id': 'order-123', 'items': ['book', 'pen']},
datetime.now()
))
# 支付
store.append('order-123', Event(
'order-123', 'OrderPaid', {}, datetime.now()
))
# 重建订单状态
events = store.get_events('order-123')
order = OrderAggregate.from_events(events)
print(order.status) # 'paid'
4.3 CQRS(命令查询职责分离)
# 写模型:处理命令
class OrderCommandHandler:
def handle_create_order(self, command):
# 验证、产生事件
event = Event('OrderCreated', {...})
event_store.append(command.order_id, event)
# 发布事件到消息队列
message_bus.publish(event)
# 读模型:优化查询
class OrderReadModel:
"""从事件构建的物化视图"""
def __init__(self):
self.orders = {} # 实际用数据库/ES
def handle_event(self, event):
if event.event_type == 'OrderCreated':
self.orders[event.aggregate_id] = event.data
elif event.event_type == 'OrderPaid':
self.orders[event.aggregate_id]['status'] = 'paid'
def get_order(self, order_id):
return self.orders.get(order_id)
4.4 事件溯源的坑
坑一:事件不可变
事件一旦产生就不能修改。只能产生补偿事件。
坑二:版本升级
事件 schema 变了怎么办?用 upcaster 模式。
坑三:复杂度高
不是所有场景都适合。审计要求高、需要回放的才用。
五、微服务通信
5.1 通信模式
| 模式 | 说明 | 适用 |
|---|---|---|
| 同步(REST/gRPC) | 实时响应 | 简单查询 |
| 异步(消息队列) | 解耦、削峰 | 事件通知 |
| 事件驱动 | 完全解耦 | 复杂业务 |
5.2 同步 vs 异步
5.3 服务间通信最佳实践
# 1. 超时
import httpx
async def call_service():
async with httpx.AsyncClient(timeout=30.0) as client:
try:
response = await client.post('http://service-b/api', json=data)
return response.json()
except httpx.TimeoutException:
# 超时降级
return fallback()
# 2. 重试(指数退避)
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
async def call_with_retry():
return await call_service()
# 3. 熔断
from circuitbreaker import circuit
@circuit(failure_threshold=5, recovery_timeout=60)
async def call_with_circuit():
return await call_service()
# 4. 幂等
async def create_order(order_id, data):
# 幂等检查
if order_exists(order_id):
return get_order(order_id)
return await create_new_order(order_id, data)
六、服务网格(Service Mesh)
6.1 什么是服务网格
6.2 Istio 核心功能
# 1. 流量管理
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: myapp
spec:
http:
- match:
- headers:
canary:
exact: "true"
route:
- destination:
host: myapp
subset: v2 # 金丝雀版本
- route:
- destination:
host: myapp
subset: v1 # 默认版本
weight: 90
- destination:
host: myapp
subset: v2
weight: 10 # 10% 流量到 v2
# 2. 熔断
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: myapp
spec:
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s
# 3. mTLS
apiVersion: security.istio.io/v1alpha3
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: STRICT
6.3 服务网格选型
| 工具 | 特点 |
|---|---|
| Istio | 功能最全、最复杂 |
| Linkerd | 轻量、易用 |
| Consul Connect | HashiCorp 生态 |
| Cilium | eBPF、性能好 |
七、混沌工程
7.1 什么是混沌工程
主动制造故障,验证系统的弹性。
“在故障发生前,先让故障发生。” —— Netflix Chaos Monkey
7.2 混沌实验设计
# 混沌实验模板
experiment = {
"name": "验证订单服务宕机时系统是否降级",
"hypothesis": "订单服务宕机时,购物车服务应降级为'稍后重试'",
"steady_state": {
"metric": "error_rate",
"threshold": "< 1%"
},
"failure": {
"type": "service_down",
"target": "order-service",
"duration": "5m"
},
"rollback": {
"condition": "error_rate > 5%",
"action": "restore order-service"
}
}
7.3 Chaos Mesh(K8s 混沌工程)
# Pod 故障注入
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: pod-kill-example
spec:
action: pod-kill
mode: one
selector:
namespaces:
- default
labelSelectors:
app: myapp
scheduler:
cron: "@every 10m"
# 网络延迟
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: network-delay
spec:
action: delay
mode: all
selector:
labelSelectors:
app: myapp
delay:
latency: "100ms"
correlation: "100"
jitter: "10ms"
duration: "30s"
7.4 混沌工程原则
- 从稳态假设开始:先定义正常状态
- 变化真实世界的事件:模拟真实故障
- 在生产环境实验:最能发现问题
- 自动化持续运行:集成到 CI/CD
- 最小化爆炸半径:从小范围开始
八、踩坑总结
坑一:CDN 缓存了错误内容
解决: 文件名加 hash,HTML 不缓存。
坑二:gRPC 调试难
解决: 用 grpcurl,或加 gRPC-Gateway。
坑三:GraphQL N+1
解决: DataLoader 批量加载。
坑四:事件溯源复杂度爆炸
解决: 只在审计要求高的核心域用。
坑五:服务网格性能开销
Sidecar 增加延迟。性能极致敏感场景谨慎。
坑六:混沌实验引发真实故障
解决: 工作时间运行、有回滚机制、从最小范围开始。
九、写在最后
系统设计与通信是分布式系统的核心。
几条核心原则:
- CDN 是前端加速标配
- gRPC 适合服务间高性能通信
- GraphQL 适合复杂前端查询
- 事件溯源适合审计要求高的场景
- 服务网格把通信逻辑从应用代码剥离
- 混沌工程是验证弹性的最好方式
- 通信必须有超时、重试、熔断
本文整合了 10 篇系统设计与通信相关文章,涵盖 CDN、gRPC、GraphQL、事件溯源/CQRS、微服务通信、服务网格、混沌工程等核心技术。
版权声明: 本文首发于 指尖魔法屋-系统设计与通信实战指南:从CDN到服务网格(https://blog.thinkmoon.cn/post/system-design-communication-comprehensive-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。