系统设计与通信实战指南:从CDN到服务网格

前言:通信是分布式系统的血液

分布式系统的核心难题之一就是通信。服务之间怎么调用、数据怎么同步、故障怎么隔离——每个问题都决定了系统的稳定性。

一、CDN 内容分发网络

1.1 CDN 的核心价值

  • 加速:用户从最近节点获取内容
  • 减负:源站压力降低
  • 高可用:多节点冗余
  • 安全:隐藏源站 IP,抗 DDoS

1.2 CDN 工作原理

graph LR A[用户] --> B[DNS] B --> C[CDN 边缘节点] C -->|缓存命中| A C -->|未命中| D[源站] D --> C

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 优化策略

策略说明
文件名加 hashapp.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

维度gRPCREST
协议HTTP/2HTTP/1.1
序列化ProtobufJSON
性能快 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

维度GraphQLREST
数据获取客户端指定服务端定义
请求次数一次获取多资源多次
版本无版本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 异步

graph TB subgraph 同步 A[服务 A] -->|HTTP| B[服务 B] B -->|响应| A A --> C[返回用户] end subgraph 异步 D[服务 A] -->|发消息| E[消息队列] D --> F[立即返回] E --> G[服务 B] E --> H[服务 C] end

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 什么是服务网格

graph TB subgraph 传统 A1[服务 A] --> B1[服务 B] A1 --> C1[服务 C] Note1[应用代码处理<br/>熔断/重试/追踪] end subgraph 服务网格 A2[服务 A] --> SA[Sidecar] SA --> SB[Sidecar] SB --> B2[服务 B] Note2[Sidecar 处理<br/>熔断/重试/追踪/mTLS] end

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 ConnectHashiCorp 生态
CiliumeBPF、性能好

七、混沌工程

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 混沌工程原则

  1. 从稳态假设开始:先定义正常状态
  2. 变化真实世界的事件:模拟真实故障
  3. 在生产环境实验:最能发现问题
  4. 自动化持续运行:集成到 CI/CD
  5. 最小化爆炸半径:从小范围开始

八、踩坑总结

坑一:CDN 缓存了错误内容

解决: 文件名加 hash,HTML 不缓存。

坑二:gRPC 调试难

解决: 用 grpcurl,或加 gRPC-Gateway。

坑三:GraphQL N+1

解决: DataLoader 批量加载。

坑四:事件溯源复杂度爆炸

解决: 只在审计要求高的核心域用。

坑五:服务网格性能开销

Sidecar 增加延迟。性能极致敏感场景谨慎。

坑六:混沌实验引发真实故障

解决: 工作时间运行、有回滚机制、从最小范围开始。

九、写在最后

系统设计与通信是分布式系统的核心。

几条核心原则:

  1. CDN 是前端加速标配
  2. gRPC 适合服务间高性能通信
  3. GraphQL 适合复杂前端查询
  4. 事件溯源适合审计要求高的场景
  5. 服务网格把通信逻辑从应用代码剥离
  6. 混沌工程是验证弹性的最好方式
  7. 通信必须有超时、重试、熔断

本文整合了 10 篇系统设计与通信相关文章,涵盖 CDN、gRPC、GraphQL、事件溯源/CQRS、微服务通信、服务网格、混沌工程等核心技术。

版权声明: 本文首发于 指尖魔法屋-系统设计与通信实战指南:从CDN到服务网格https://blog.thinkmoon.cn/post/system-design-communication-comprehensive-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!