高并发系统设计实战:从单体到微服务的亿级流量架构演变

引言

大多数系统在起步阶段,都是一个单体应用 + 一台 MySQL,跑得好好的。然后某一天,用户在增长,QPS 从 100 涨到了 1000,系统开始不堪重负。

系统的架构不是设计出来的,是演变出来的。 每个架构决策背后都有一个性能瓶颈和用户投诉。本文模拟一个真实的电商系统,从 0 到 1 演变成一个支撑亿级流量的架构。


第一阶段:单体架构的黄金年代

初始架构

Nginx -> Tomcat(单体应用) -> MySQL

这个架构很简单:

  • 一台 Nginx 做反向代理
  • 一个 Spring Boot 单体应用
  • 一台 MySQL 数据库

第一个性能瓶颈:数据库连接

当用户量开始增长,第一个倒下的一般是数据库。不是 MySQL 不够快,而是数据库连接数不够用。

引入连接池

# HikariCP 配置
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

连接池大小有个经典公式:

connections = ((core_count * 2) + effective_spindle_count)

但实际应用中,数据库连接数一般不超过 CPU 核心数的 2 倍。连接池太大的话,上下文切换的代价会超过并行带来的收益。


第二阶段:引入缓存层

Redis 的出现时机

当你的数据库 QPS 超过 1000 时,就该上 Redis 了。一个简单的 Read-Write Through 模式:

import redis
import json
from typing import Optional, Any
from functools import wraps
import hashlib

class CacheService:
    """
    多级缓存服务:本地缓存 -> Redis -> 数据库
    """

    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
        self.local_cache = {}  # 进程内 LRU 缓存

    def cached(self, ttl: int = 300):
        """缓存装饰器"""
        def decorator(func):
            @wraps(func)
            async def wrapper(self, key: str, *args, **kwargs):
                # L1: 本地缓存
                cache_key = f"{func.__name__}:{key}"
                result = self.local_cache.get(cache_key)
                if result is not None:
                    return result

                # L2: Redis
                redis_key = f"cache:{func.__name__}:{key}"
                result = await self.redis.get(redis_key)
                if result is not None:
                    self.local_cache[cache_key] = json.loads(result)
                    return self.local_cache[cache_key]

                # L3: 数据库
                result = await func(self, key, *args, **kwargs)
                if result is not None:
                    await self.redis.setex(
                        redis_key,
                        ttl,
                        json.dumps(result),
                    )
                    self.local_cache[cache_key] = result

                return result
            return wrapper
        return decorator

缓存穿透/击穿/雪崩 解决方案

class CacheShield:
    """缓存防护层"""

    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client

    async def get_with_bloom_filter(self, key: str) -> Optional[str]:
        """
        布隆过滤器防止缓存穿透
        - 穿透:请求不存在的数据,每次都打到 DB
        - 解决:用布隆过滤器过滤掉一定不存在的 key
        """
        if not self.bloom_filter.might_contain(key):
            return None  # 一定不存在,不查了

        return await self.redis.get(key)

    async def get_with_mutex(self, key: str) -> Optional[str]:
        """
        互斥锁防止缓存击穿
        - 击穿:热点 key 过期瞬间,大量请求打到 DB
        - 解决:同一时刻只允许一个请求重建缓存
        """
        value = await self.redis.get(key)
        if value is not None:
            return value

        # 分布式锁:只有一个线程能去查 DB
        lock_key = f"lock:rebuild:{key}"
        if await self.redis.set(lock_key, "1", nx=True, ex=10):
            try:
                value = await self._load_from_db(key)
                if value:
                    await self.redis.setex(key, 60, value)
                return value
            finally:
                await self.redis.delete(lock_key)
        else:
            # 获取锁失败,等 50ms 后重试
            await asyncio.sleep(0.05)
            return await self.get_with_mutex(key)

    async def set_with_random_ttl(self, key: str, value: str, base_ttl: int):
        """
        随机过期时间防止缓存雪崩
        - 雪崩:大量 key 同时过期,瞬间流量全打到 DB
        - 解决:过期时间加随机偏移
        """
        import random
        ttl = base_ttl + random.randint(0, base_ttl // 2)
        await self.redis.setex(key, ttl, value)

第三阶段:数据库优化

读写分离

主库(写) -> 从库1(读)、从库2(读)
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from contextlib import contextmanager
import random

class DatabaseRouter:
    """数据库读写分离路由"""

    def __init__(self):
        self.write_engine = create_engine(
            "mysql+pymysql://user:pass@master:3306/db",
            pool_size=10,
            max_overflow=20,
        )
        self.read_engines = [
            create_engine(f"mysql+pymysql://user:pass@slave{i}:3306/db")
            for i in range(2)
        ]
        self.WriteSession = sessionmaker(bind=self.write_engine)

    @contextmanager
    def read_session(self) -> Session:
        """获取读库连接(随机负载均衡)"""
        engine = random.choice(self.read_engines)
        Session = sessionmaker(bind=engine)
        session = Session()
        try:
            yield session
        finally:
            session.close()

    @contextmanager
    def write_session(self) -> Session:
        """获取写库连接"""
        session = self.WriteSession()
        try:
            yield session
            session.commit()
        except Exception:
            session.rollback()
            raise
        finally:
            session.close()

分库分表策略

当单表数据量超过 2000 万行时,MySQL 的写入性能开始显著下降。

Sharding 算法

class ShardingStrategy:
    """一致性哈希分片策略"""

    def __init__(self, shard_count: int = 16):
        self.shard_count = shard_count
        self.virtual_nodes = 128
        self.ring = self._build_ring()

    def get_shard(self, user_id: str) -> int:
        """
        根据用户 ID 确定分片
        使用 user_id % shard_count 保证同一用户的请求落在同一分片
        """
        hash_val = hashlib.md5(user_id.encode()).hexdigest()
        return int(hash_val[:8], 16) % self.shard_count

    def get_table(self, order_id: str) -> int:
        """
        订单表按月分表
        单月数据量可控,方便归档清理
        """
        # order_id 格式: YYYYMMDD_xxxxx
        year_month = order_id[:6]
        return int(year_month)

# 使用示例
class OrderDAO:
    def __init__(self, sharding: ShardingStrategy):
        self.sharding = sharding
        self.shard_engines = {
            i: create_engine(f"mysql://.../db_shard_{i}")
            for i in range(sharding.shard_count)
        }

    def create_order(self, user_id: str, order: dict):
        shard = self.sharding.get_shard(user_id)
        table = f"orders_{self.sharding.get_table(order['order_id'])}"

        engine = self.shard_engines[shard]
        with engine.connect() as conn:
            conn.execute(
                f"INSERT INTO {table} (user_id, product_id, amount) VALUES (:uid, :pid, :amt)",
                uid=user_id,
                pid=order["product_id"],
                amt=order["amount"],
            )

第四阶段:引入消息队列

异步解耦

用户下单 -> 订单服务 -> Kafka -> 库存服务
                     -> Kafka -> 通知服务
                     -> Kafka -> 推荐服务
from confluent_kafka import Producer, Consumer
import json
from dataclasses import dataclass
from typing import Callable

@dataclass
class KafkaConfig:
    bootstrap_servers: str = "localhost:9092"
    group_id: str = "order-service"
    auto_offset_reset: str = "earliest"

class EventBus:
    """基于 Kafka 的事件总线"""

    def __init__(self, config: KafkaConfig):
        self.config = config
        self._producer = None
        self._consumers: dict[str, Consumer] = {}

    @property
    def producer(self):
        if self._producer is None:
            self._producer = Producer({
                'bootstrap.servers': self.config.bootstrap_servers,
                'acks': 'all',  # 等待所有副本确认
                'retries': 3,
                'enable.idempotence': True,  # 幂等性保证
            })
        return self._producer

    def publish(self, topic: str, event: dict, key: str = None):
        """发布事件(异步)"""
        self.producer.produce(
            topic,
            key=key,
            value=json.dumps(event),
            callback=self._delivery_callback,
        )
        self.producer.poll(0)  # 触发回调

    def subscribe(self, topic: str, handler: Callable):
        """订阅事件"""

        consumer = Consumer({
            'bootstrap.servers': self.config.bootstrap_servers,
            'group.id': self.config.group_id,
            'auto.offset.reset': self.config.auto_offset_reset,
            'enable.auto.commit': False,  # 手动提交,保证 at-least-once
        })
        consumer.subscribe([topic])

        # 启动消费循环
        import threading
        thread = threading.Thread(
            target=self._consume_loop,
            args=(consumer, handler),
            daemon=True,
        )
        thread.start()

    def _consume_loop(self, consumer: Consumer, handler: Callable):
        while True:
            msg = consumer.poll(1.0)
            if msg is None:
                continue
            if msg.error():
                print(f"Consumer error: {msg.error()}")
                continue

            try:
                event = json.loads(msg.value())
                handler(event)
                consumer.commit(msg)
            except Exception as e:
                print(f"Handle event error: {e}")
                # 不提交 offset,下次重试

    def _delivery_callback(self, err, msg):
        if err:
            print(f"Message delivery failed: {err}")
        else:
            print(f"Message delivered to {msg.topic()} [{msg.partition()}]")

第五阶段:服务拆分与微服务化

拆服务的方法论

什么时候该拆?一个信号:当你修改一个功能需要改动多个模块时。

# 拆分后的订单服务
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx

app = FastAPI(title="订单服务")

class OrderRequest(BaseModel):
    user_id: str
    product_id: str
    quantity: int

@app.post("/api/orders")
async def create_order(request: OrderRequest):
    # 1. 查询库存(调用库存服务)
    async with httpx.AsyncClient() as client:
        stock_resp = await client.get(
            f"http://inventory-service/api/stock/{request.product_id}"
        )
        if stock_resp.status_code != 200:
            raise HTTPException(status_code=502, detail="库存服务异常")

        stock = stock_resp.json()
        if stock["available"] < request.quantity:
            raise HTTPException(status_code=400, detail="库存不足")

        # 2. 扣减库存
        await client.post(
            f"http://inventory-service/api/stock/deduct",
            json={"product_id": request.product_id, "quantity": request.quantity},
        )

    # 3. 创建订单
    order_id = await create_order_in_db(request)

    # 4. 发送创建事件(异步)
    event_bus.publish("order.created", {
        "order_id": order_id,
        "user_id": request.user_id,
        "product_id": request.product_id,
        "quantity": request.quantity,
    })

    return {"order_id": order_id, "status": "created"}

第六阶段:分布式事务

分布式事务 Saga 模式

from enum import Enum
from typing import List, Callable, Dict
import asyncio

class SagaStatus(Enum):
    PENDING = "pending"
    COMPLETED = "completed"
    COMPENSATING = "compensating"
    FAILED = "failed"

class SagaStep:
    """Saga 事务步骤"""

    def __init__(
        self,
        name: str,
        execute: Callable,
        compensate: Callable,
    ):
        self.name = name
        self.execute = execute
        self.compensate = compensate

class SagaOrchestrator:
    """
    Saga 编排器 - Choreography 模式

    每个步骤执行后,下一个步骤由事件驱动
    如果某步失败,自动补偿之前的所有步骤
    """

    def __init__(self):
        self.steps: List[SagaStep] = []
        self.status = SagaStatus.PENDING
        self.executed_steps: List[SagaStep] = []

    def add_step(self, step: SagaStep):
        self.steps.append(step)

    async def execute(self, context: Dict) -> Dict:
        """执行 Saga 事务"""
        try:
            for step in self.steps:
                print(f"执行步骤: {step.name}")
                result = await step.execute(context)
                self.executed_steps.append(step)
                context.update(result or {})

            self.status = SagaStatus.COMPLETED
            return {"status": "success", "context": context}

        except Exception as e:
            print(f"步骤 {step.name} 执行失败: {e}")
            self.status = SagaStatus.COMPENSATING
            await self._compensate(context)
            self.status = SagaStatus.FAILED
            return {"status": "failed", "error": str(e)}

    async def _compensate(self, context: Dict):
        """回滚已执行的步骤(逆序)"""
        print("开始补偿事务...")
        for step in reversed(self.executed_steps):
            try:
                print(f"补偿步骤: {step.name}")
                await step.compensate(context)
            except Exception as e:
                print(f"补偿步骤 {step.name} 失败: {e}")
                # 补偿失败需要人工介入
                # 发送告警通知运维

# 使用示例:订单创建 Saga
async def create_order_saga(user_id: str, product_id: str, quantity: int):
    saga = SagaOrchestrator()

    # 步骤1:创建订单
    async def execute_create_order(ctx):
        order_id = await order_service.create(ctx["user_id"], ctx["product_id"])
        ctx["order_id"] = order_id
        return ctx

    async def compensate_create_order(ctx):
        await order_service.cancel(ctx["order_id"])

    saga.add_step(SagaStep(
        name="创建订单",
        execute=execute_create_order,
        compensate=compensate_create_order,
    ))

    # 步骤2:扣减库存
    async def execute_deduct_stock(ctx):
        await inventory_service.deduct(ctx["product_id"], ctx["quantity"])
        return ctx

    async def compensate_deduct_stock(ctx):
        await inventory_service.restore(ctx["product_id"], ctx["quantity"])

    saga.add_step(SagaStep(
        name="扣减库存",
        execute=execute_deduct_stock,
        compensate=compensate_deduct_stock,
    ))

    # 步骤3:扣款
    async def execute_charge(ctx):
        await payment_service.charge(ctx["user_id"], ctx["amount"])
        return ctx

    async def compensate_charge(ctx):
        await payment_service.refund(ctx["user_id"], ctx["amount"])

    saga.add_step(SagaStep(
        name="用户扣款",
        execute=execute_charge,
        compensate=compensate_charge,
    ))

    return await saga.execute({
        "user_id": user_id,
        "product_id": product_id,
        "quantity": quantity,
    })

第七阶段:限流与熔断

令牌桶限流

import time
import asyncio
from dataclasses import dataclass

@dataclass
class TokenBucket:
    """
    令牌桶限流器
    允许一定的突发流量(burst),但限制了平均速率
    """
    rate: float           # 每秒生成的令牌数
    capacity: int         # 桶容量(允许的突发流量)
    tokens: float = 0.0
    last_refill: float = 0.0

    def __post_init__(self):
        self.tokens = self.capacity
        self.last_refill = time.monotonic()

    async def acquire(self, tokens: int = 1) -> bool:
        """获取令牌"""
        self._refill()

        if self.tokens >= tokens:
            self.tokens -= tokens
            return True

        return False

    def _refill(self):
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
        self.last_refill = now


class SentinelGateway:
    """API 网关限流中间件"""

    def __init__(self):
        self.buckets: dict[str, TokenBucket] = {}

    def add_route(self, path: str, rate: int, burst: int):
        """注册路由限流规则"""
        self.buckets[path] = TokenBucket(rate=rate, capacity=burst)

    async def handle(self, path: str, handler):
        """限流中间件"""
        bucket = self.buckets.get(path)
        if bucket is None:
            return await handler()

        if not await bucket.acquire():
            raise HTTPException(
                status_code=429,
                detail="Too Many Requests - 请稍后重试",
                headers={"Retry-After": "5"},
            )

        return await handler()

# 使用示例
gateway = SentinelGateway()
gateway.add_route("/api/orders", rate=100, burst=200)   # 每秒100个请求,允许200突发
gateway.add_route("/api/search", rate=500, burst=800)    # 搜索接口更高频

熔断器模式

from enum import Enum
from datetime import datetime, timedelta
import asyncio

class CircuitState(Enum):
    CLOSED = "closed"            # 正常状态
    OPEN = "open"               # 熔断状态,拒绝请求
    HALF_OPEN = "half_open"     # 半开状态,探测下游是否恢复

class CircuitBreaker:
    """
    熔断器实现

    状态流转:
    CLOSED -> (失败次数达标) -> OPEN -> (冷却时间过了) -> HALF_OPEN
    HALF_OPEN -> (探测成功) -> CLOSED
    HALF_OPEN -> (探测失败) -> OPEN
    """

    def __init__(
        self,
        failure_threshold: int = 5,       # 失败次数阈值
        timeout_seconds: int = 30,        # 冷却时间(秒)
        half_open_max_requests: int = 3,   # 半开状态允许的探测请求数
    ):
        self.failure_threshold = failure_threshold
        self.timeout = timedelta(seconds=timeout_seconds)
        self.half_open_max_requests = half_open_max_requests

        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.last_failure_time: datetime = None
        self.half_open_requests = 0

    async def call(self, func, *args, **kwargs):
        """通过熔断器调用下游服务"""
        if self.state == CircuitState.OPEN:
            if self._should_try_half_open():
                self.state = CircuitState.HALF_OPEN
                self.half_open_requests = 0
            else:
                raise CircuitBreakerOpenError("熔断器已打开,请稍后重试")

        if self.state == CircuitState.HALF_OPEN:
            if self.half_open_requests >= self.half_open_max_requests:
                raise CircuitBreakerOpenError("半开状态探测请求数已达上限")

            self.half_open_requests += 1

        try:
            result = await func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise

    def _on_success(self):
        """调用成功"""
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.CLOSED
            self.failure_count = 0

    def _on_failure(self):
        """调用失败"""
        self.failure_count += 1
        self.last_failure_time = datetime.now()

        if (self.state == CircuitState.CLOSED
            and self.failure_count >= self.failure_threshold):
            self.state = CircuitState.OPEN

    def _should_try_half_open(self) -> bool:
        if self.last_failure_time is None:
            return True
        return datetime.now() - self.last_failure_time >= self.timeout

class CircuitBreakerOpenError(Exception):
    pass


# 使用示例
breaker = CircuitBreaker(failure_threshold=3, timeout_seconds=30)

@app.post("/api/orders")
async def create_order(request: OrderRequest):
    try:
        return await breaker.call(
            inventory_service.check_and_deduct,
            request.product_id,
            request.quantity,
        )
    except CircuitBreakerOpenError:
        # 熔断器打开,返回降级响应
        return {
            "status": "degraded",
            "message": "系统繁忙,订单已接收,请稍后查看状态",
            "order_id": await async_create_pending_order(request),
        }

第八阶段:异步非阻塞架构

Python asyncio + FastAPI 的极致优化

import asyncio
from concurrent.futures import ThreadPoolExecutor

class AsyncOptimizer:
    """
    异步优化器 - 避免同步阻塞

    关键原则:
    1. IO 密集用 async/await
    2. CPU 密集用 run_in_executor
    3. 数据库查询用异步驱动(asyncpg / aiomysql)
    """

    def __init__(self):
        self.cpu_executor = ThreadPoolExecutor(max_workers=4)

    async def fetch_user_orders(self, user_id: str) -> dict:
        """并行获取用户数据(多个下游服务同时调)"""
        # 同时调用三个无依赖的服务
        results = await asyncio.gather(
            order_service.get_orders(user_id),
            user_service.get_profile(user_id),
            recommendation_service.get_recommended(user_id),
            return_exceptions=True,  # 部分失败不影响其他
        )

        orders, profile, recommendations = results

        return {
            "user": profile if not isinstance(profile, Exception) else None,
            "orders": orders if not isinstance(orders, Exception) else [],
            "recommendations": recommendations if not isinstance(recommendations, Exception) else [],
        }

    async def compute_heavy_task(self, data: list) -> dict:
        """CPU 密集型任务转移到线程池"""
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            self.cpu_executor,
            self._cpu_bound_algorithm,
            data,
        )

    def _cpu_bound_algorithm(self, data: list) -> dict:
        """CPU 密集型计算"""
        # 复杂的数据聚合、计算
        result = {}
        for item in data:
            # ... 复杂运算
            pass
        return result

架构演变总结

阶段 核心问题 解决方案 QPS 上限
单体 数据库连接不足 连接池(HikariCP) ~500
缓存 数据库压力太大 Redis多级缓存 ~5,000
数据库 单表数据太大 读写分离 + 分库分表 ~20,000
消息队列 同步链路太长 Kafka异步解耦 ~50,000
微服务 代码耦合严重 服务拆分 + RPC ~200,000
分布式事务 跨服务数据一致 Saga补偿模式 -
限流熔断 雪崩效应 Sentinel + Hystrix -
异步架构 CPU/IO阻塞 asyncio + 协程 ~500,000

终极原则

  1. 不要过早优化:QPS 不到 1000 时,你的瓶颈大概率在你自己的代码,不在架构
  2. 先监控后优化:没有监控数据的优化就是盲人摸象
  3. 缓存是第一优先级:80% 的性能问题加个 Redis 就解决了
  4. 数据一致性 > 性能:分布式系统下,不能拿业务正确性去换 10ms 的延迟提升
  5. 架构是长出来的:不要在一开始就设计一个"完美的架构",跟着业务的节奏一步一步来

声明:本文中的性能数据是基于常见硬件配置的估计值,实际表现会因具体业务逻辑、数据规模和硬件环境而异。架构设计需要结合实际情况进行决策。