Skip to main content

07. 规模化架构:LB、CDN、K8s、Kafka、Nacos

读完这篇你能:

  • 看懂从 1 万日活到百万日活的架构演进
  • 知道负载均衡、CDN、读写分离、分片分别解决什么问题
  • 看懂 K8s 的核心概念(Pod、Deployment、Service)
  • 知道 ZooKeeper / Nacos / Kafka / gRPC 是干嘛的、什么时候用

对应架构层:接入层 + 协调层 + 部署层(进阶)

前置知识:01-06 篇

预计用时:3 小时(主要理解,操作可选)


0. 为什么算法工程师要学这个

你的应用在 Cloud Run 跑得好好的,日活 1000。突然老板说:

"我们下周要在超级碗投广告,预计带来 50 万用户。"

你看了一眼 Cloud Run 配置:max-instances=10。每实例 80 并发,总共 800 并发——50 万用户同时来,会挂

怎么扩?直接调 max-instances=100?成本炸了。怎么办?

更要命的问题:

  • 数据库一台机器扛不住 50 万用户读,怎么分担?
  • 单个 Cloud Run 区域在亚洲,美国用户延迟 300ms+,怎么加速?
  • LLM 调用慢,队列堆积怎么处理?
  • 几百台机器,怎么管理?怎么发现彼此?

这一篇回答这些问题。注意:这是"理解型"教程,不是"动手型"——这些技术都需要团队 / 运维支持,算法工程师主要是看懂 + 能讨论,不是亲手搭。


1. 架构演进:从 1 到百万日活

1.1 阶段 1:单机(< 1000 日活)

用户 → Cloud Run(1 个实例)→ Postgres

Redis / S3
  • 一个 Docker 容器,Cloud Run 自动扩缩
  • 一个 Postgres 实例(2 CPU, 4GB)
  • 一个 Redis 实例
  • 月成本:$50-200

瓶颈:单个 Cloud Run 实例的并发上限。

1.2 阶段 2:小规模扩展(1k - 1 万日活)

用户 → Cloud Run(多实例,自动扩)→ 主 Postgres(读写都它)
↑ ↓
CDN Redis(缓存)
  • Cloud Run 多实例 + 自动扩缩
  • Postgres 升级到 4 CPU 16GB
  • 加 CDN 加速静态资源
  • 月成本:$200-2000

瓶颈:

  • Postgres 写入压力
  • LLM 调用排队(单个用户等很久)

1.3 阶段 3:读写分离 + 缓存(1万 - 10 万日活)

用户 → LB → 多个 App 实例 → Postgres 主(写)
↘ Postgres 从(读)× 2
↘ Redis 集群
↘ Kafka(异步任务)

消费者(后台 worker)
  • 加 LB(负载均衡,Cloud Run 自带,自建需 Nginx / ALB)
  • Postgres 读写分离:1 主 2 从
  • Redis 集群(分片)
  • Kafka / RabbitMQ 做异步任务(LLM 调用、批量处理)
  • 多个 worker 消费异步任务
  • 月成本:$2000-20000

瓶颈:

  • 单库写入(写不能像读那样加从库)
  • 协调问题(服务发现、配置同步)

1.4 阶段 4:微服务 + K8s(10 万 - 100 万日活)

用户 → CDN → Global LB → 多区域 K8s 集群
├── 用户服务(微服务)
├── LLM 服务(微服务)
├── 计费服务
└── 通知服务

每个微服务:
→ 自己的 DB(分库)
→ 通过 gRPC / 消息队列通信
→ 通过 Nacos / etcd 注册发现
  • K8s 管几百个容器
  • 服务网格(Istio)管服务间通信
  • 数据库分片(按 user_id hash 切到不同库)
  • 多区域部署(美东、亚太、欧洲)
  • 月成本:$20000-200000+

瓶颈:

  • 复杂度爆炸
  • 团队 / 组织问题(康威定律)

1.5 经验法则:不要过早优化

日活 1k → Cloud Run + 单 Postgres 够了
日活 1万 → 加 Redis 缓存、CDN
日活 10万 → 加读写分离、异步任务
日活 100万 → 才考虑 K8s、分片、多区域

90% 的 LLM 应用到不了 100 万日活。如果你是创业公司,先把 1000 日活跑通,再谈扩展


2. 接入层进阶

2.1 负载均衡(Load Balancing)

作用:把进入的流量分发到多台后端机器。

算法:

算法怎么工作用途
轮询(Round Robin)A→B→C→A→B→C默认,机器配置相同
最少连接(Least Connections)找当前连接最少的长连接、LLM 应用
IP Hash同 IP 固定去某机器需要 session sticky
加权(Weighted)按 CPU/内存权重机器配置不同

LLM 应用用最少连接——因为每个请求时长差异大(LLM 调用 1-30 秒),轮询会让某些机器被慢请求卡满。

主流 LB:

工具特点
Nginx老牌,配置式,反向代理 + LB
HAProxy专业 LB,性能强
Cloud LB(AWS ALB、GCP LB)托管,全球分布
Cloud Run(自带)你不用管,Cloud Run 自动 LB
Envoy / Istio服务网格的 LB,微服务间

Nginx 配置例子:

upstream llm_backend {
least_conn; # 最少连接算法
server app1:8000 weight=3;
server app2:8000 weight=2;
server app3:8000 weight=1;
keepalive 32; # 保持长连接
}

server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;

location /api/ {
proxy_pass http://llm_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_buffering off; # ✅ SSE 流式必须关
}
}

2.2 CDN(内容分发网络)

作用:把静态资源(JS、CSS、图片、视频)缓存在全球各地的节点,用户就近访问。

没有 CDN:

澳大利亚用户 → 美国服务器(200ms 延迟)→ 加载 5 秒

有 CDN:

澳大利亚用户 → 悉尼 CDN 节点(5ms 延迟)→ 加载 1 秒

主流 CDN:

服务特点
Cloudflare免费,边缘计算,防护强
AWS CloudFront深度集成 S3
Akamai老牌,企业级
阿里云 CDN / 腾讯云 CDN国内

LLM 应用怎么用 CDN:

  • 前端静态资源(HTML/CSS/JS)→ 必须走 CDN
  • API 响应 → 不缓存(动态),但可以用 CDN 做边缘路由
  • 文件下载(S3 上的 PDF)→ 通过 CDN 加速

Cloudflare 配置:

  1. 域名 DNS 切到 Cloudflare
  2. 开启 "Proxies"(橙色云朵)
  3. 配 Page Rules:/static/* 全缓存,/api/* 不缓存

2.3 DNS 和全球路由

DNS:把 example.com 翻译成 IP。

智能 DNS(Route53、Cloudflare)能根据用户地理位置返回不同 IP:

美国用户查 example.com → 1.2.3.4(美东机房)
亚洲用户查 example.com → 5.6.7.8(亚太机房)

实现"地理就近接入"。

Anycast:同一个 IP 在全球多个机房宣告,BGP 自动路由到最近的。Cloudflare、Cloud LB 都用这个技术。


3. 数据层进阶

3.1 读写分离

原理:

  • 所有写操作 → 主库(Master / Primary)
  • 所有读操作 → 从库(Replica / Standby)
  • 主库的修改异步同步到从库
App → [写] → Master Postgres ─── 同步 ───→ Replica 1
└── 同步 ──→ Replica 2
App → [读] → LB → Replica 1 / Replica 2

代码:

from sqlalchemy import create_engine

write_engine = create_engine("postgresql://user:pass@master:5432/db")
read_engine = create_engine("postgresql://user:pass@replica-lb:5432/db")

# 写
with Session(write_engine) as s:
s.add(user)
s.commit()

# 读
with Session(read_engine) as s:
users = s.exec(select(User)).all()

注意:

  • 复制延迟:主库写后,从库可能晚几十毫秒到几秒才同步
  • 写完立刻读,可能读到旧数据——用"读主库"或"读写绑定同一会话"

3.2 分片(Sharding)

当读写分离不够:单库扛不住写入(每秒 10 万写),或单表行数到亿级。

分片:把数据水平切到多个库,每个库只存一部分。

用户 ID 1-100万     → DB Shard 1
用户 ID 100万-200万 → DB Shard 2
用户 ID 200万-300万 → DB Shard 3

分片键(Shard Key)选择很关键:

策略例子优缺点
按用户 ID hashshard = hash(user_id) % N均匀,但跨片查询难
按时间2026 年数据在 A,2025 年在 B时序数据好用,但热点(最新片)
按地域美国用户在美东库,亚洲用户在亚太库就近访问,但跨区查询慢
按业务订单库、用户库分业务边界清晰

警告:分片是不可逆的复杂化。一旦分了,JOIN、事务都变难。不到万不得已不要分片

3.3 冷热数据隔离

热数据(近 30 天,频繁访问)→ Redis + Postgres SSD
温数据(近 1 年,偶尔访问)→ Postgres HDD
冷数据(1 年前,几乎不访问)→ S3 Glacier

Postgres 表分区:

CREATE TABLE queries (
id BIGSERIAL,
user_id INT,
created_at TIMESTAMP,
...
) PARTITION BY RANGE (created_at);

CREATE TABLE queries_2026_06 PARTITION OF queries
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');

CREATE TABLE queries_2026_05 PARTITION OF queries
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');

查询时 Postgres 自动只扫相关分区。

3.4 缓存集群

单 Redis 扛不住时:

Redis Cluster(官方分片):

  • 数据自动分片到多个节点
  • 主从复制,自动 failover
  • 客户端用 redis-py 集群模式

Redis 主从 + Sentinel:

  • 一主多从,读分摊
  • Sentinel 监控主,挂了自动选新主
from redis.cluster import RedisCluster
client = RedisCluster(host="redis-cluster", port=6379)

4. 协调层:服务发现和配置中心

4.1 为什么需要协调层

单机时代:函数调用,代码里写死 localhost:8000

多机时代:

  • 服务 A 要调服务 B,但 B 有 10 个实例,调哪个?
  • B 的实例 IP 会变(扩容、重启、迁移)
  • 配置改了(限流从 10/min 调成 100/min),不想重启

协调层 解决这些:

问题解决方案技术
服务发现服务注册 + 健康检查 + 客户端发现Nacos、Consul、etcd、ZooKeeper
动态配置配置中心,改配置不重启Nacos、Apollo
异步通信消息队列,解耦Kafka、RabbitMQ、RocketMQ
分布式锁多实例操作同一资源防冲突Redis RedLock、ZooKeeper

4.2 ZooKeeper / etcd / Consul / Nacos 对比

技术来源特点主用场景
ZooKeeperYahoo, 2008老牌,复杂,JavaKafka 早期依赖,Hadoop 生态
etcdCoreOS, 2013简单,Go,强一致(Raft)K8s 底层用
ConsulHashiCorp, 2014服务发现 + 健康 + KV微服务架构
Nacos阿里, 2018注册中心 + 配置中心一体国内微服务标配

简单理解:

  • K8s 自带 etcd,你不直接用,但要知道 K8s 就靠它做服务发现
  • 自建微服务(Java/Dubbo 体系)用 NacosConsul
  • ZooKeeper 在新项目里用得越来越少,但老系统(Kafka、Hadoop)还在用

4.3 Nacos 工作原理(代表)

注册中心:

服务启动 → 向 Nacos 注册(IP + 端口 + 健康检查 URL)
服务调用方 → 从 Nacos 拉服务列表(本地缓存)
健康检查失败 → Nacos 摘除该实例 → 通知调用方更新

配置中心:

配置存 Nacos → 服务启动时拉取 + 长轮询监听变更
配置改了 → Nacos 推送到所有订阅的服务 → 服务热更新(不重启)

Python 接入 Nacos(虽然 Python 用 Nacos 不多,Java/Dubbo 主流):

import nacos

client = nacos.NacosClient("nacos-server:8848", namespace="prod")

# 拉配置
config = client.get_config("my-app", "DEFAULT_GROUP")

# 监听配置变更
def on_change(args):
print("Config updated:", args["content"])
client.add_config_watcher("my-app", "DEFAULT_GROUP", on_change)

LLM 应用真的需要 Nacos 吗?

大多数情况:不需要。Cloud Run / K8s 自带服务发现,配置用环境变量 + Secret Manager。Nacos 是 Java 微服务生态的产物,Python 圈用得少。

你只需要知道:如果有人跟你说"我们用 Nacos",你知道它在协调层,做注册 + 配置。

4.4 Kafka:高吞吐消息队列

作用:

  • 生产者把消息写到 topic
  • 消费者订阅 topic 异步处理
  • 解耦 + 削峰

典型用法:

用户行为日志 → Kafka → 实时分析(Flink)
↘ → 数据仓库(Snowflake)
↘ → 监控告警

LLM 调用请求 → Kafka → Worker 池消费 → 调 LLM → 结果回写

Kafka 核心概念:

概念类比
Topic队列名(分类)
Partition一个 topic 切成多个,并行
Producer写消息的
Consumer读消息的
Consumer Group一组消费者平分 partition
Offset消费位置(类似文件指针)

vs RabbitMQ:

维度KafkaRabbitMQ
吞吐极高(百万/秒)中(万/秒)
延迟毫秒毫秒
持久化持久 + 可回放持久但消费后删
模型日志流队列
用途大数据、事件流任务队列、RPC

LLM 应用:大多数用 Redis Streams 或 RabbitMQ 就够了,Kafka 是大数据/日志场景才需要。

4.5 gRPC:高性能 RPC

REST + JSON 简单但慢(JSON 序列化开销大)。gRPC 用 Protobuf(二进制),速度快 10 倍。

// hello.proto
syntax = "proto3";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}
# 服务端
class Greeter(greeter_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return greeter_pb2.HelloReply(message=f"Hello {request.name}")

server = grpc.aio.server()
greeter_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port("[::]:50051")
await server.start()
# 客户端
async with grpc.aio.insecure_channel("localhost:50051") as channel:
stub = greeter_pb2_grpc.GreeterStub(channel)
resp = await stub.SayHello(greeter_pb2.HelloRequest(name="sanbu"))

什么时候用 gRPC:

  • 内部服务间调用(对性能敏感)
  • 跨语言(Java + Python + Go 都能调)
  • 流式通信(gRPC 双向流)

对外 API 还是 REST——因为浏览器、curl 对 gRPC 支持差。


5. K8s 基础(看懂 yaml)

5.1 为什么需要 K8s

Cloud Run / Fargate 简单,但:

  • 锁定云厂商
  • 灵活度有限(网络、存储、调度策略)
  • 大规模(几百个服务)成本高

Kubernetes 是"容器的操作系统"——管几百个容器的部署、扩缩容、网络、存储、故障恢复。

5.2 K8s 核心概念

概念是什么类比
Pod最小调度单位(1+ 容器)一个进程组
Deployment管 Pod 的副本数 + 滚动更新systemd
Service一组 Pod 的稳定网络入口内部 DNS + LB
Ingress外部流量入口Nginx / Cloud LB
ConfigMap配置/etc/config
Secret密钥加密的 ConfigMap
Volume持久化磁盘挂载
Namespace资源隔离文件夹

5.3 一个完整的 Deployment YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-llm-app
labels:
app: my-llm-app
spec:
replicas: 3 # 跑 3 个副本
selector:
matchLabels:
app: my-llm-app
template:
metadata:
labels:
app: my-llm-app
spec:
containers:
- name: app
image: my-registry/my-app:v1.2.3
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
resources:
requests: # 保底资源
cpu: "500m"
memory: "512Mi"
limits: # 上限
cpu: "1000m"
memory: "1Gi"
readinessProbe: # 就绪探针(能接流量了吗)
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe: # 存活探针(要不要重启)
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: my-llm-app
spec:
selector:
app: my-llm-app
ports:
- port: 80
targetPort: 8000
type: ClusterIP # 集群内访问

部署:

kubectl apply -f deployment.yaml
kubectl get pods
kubectl logs my-llm-app-xxx
kubectl exec -it my-llm-app-xxx -- bash
kubectl scale deployment my-llm-app --replicas=5

5.4 自动扩缩(HPA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-llm-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-llm-app
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

CPU 超 70% → 自动扩;空闲 → 自动缩。

LLM 应用特殊:CPU 不是好指标(LLM 调用等 IO,CPU 不高但应用很忙)。用自定义指标(请求队列长度、并发数)。

5.5 Helm:K8s 包管理

手写 YAML 太繁琐。Helm 是 K8s 的"pip / npm":

helm install my-release bitnami/postgresql
helm install my-release bitnami/redis

社区有几千个现成的 chart。

5.6 微服务 vs 单体

单体(Monolithic):

  • 一个代码库,一个进程
  • 简单,迭代快
  • 适合 < 50 万日活

微服务(Microservices):

  • 拆成多个独立服务,独立部署
  • 团队并行迭代
  • 但复杂度爆炸(网络、事务、调试)

康威定律(Conway's Law):软件架构会映射组织架构。10 个人的团队硬上微服务 = 灾难。

经验:

  • 0-50 万日活:单体(或者 Cloud Run 几个独立服务)
  • 50 万-500 万日活:有限度的微服务(按业务域拆 3-5 个)
  • 500 万+:微服务

6. 成本估算

百万日活 LLM 应用大致成本(月):

组件配置月成本
应用集群20 台 4C8G$2,000
Postgres(主+2从)16C 64GB$1,500
Redis 集群6 节点$500
对象存储 S35TB$150
CDN10TB 流量$500
LLM API(OpenAI)100M tokens/月$3,000-15,000
可观测(Grafana Cloud)Pro$300
K8s 集群(自管)3 master$1,000
合计$9,000-21,000/月

LLM API 是最大头。控制成本的关键在 09. LLM 工程 讲的成本治理。


7. 常见踩坑

坑 1:过早微服务化

10 人团队硬上微服务 → 服务间通信、调试、事务都变难 → 效率反而下降。

先单体,真有痛点再拆

坑 2:无脑加 Redis 缓存

数据本来强一致要求高,加缓存后出现脏读 → 用户看到旧余额 → 投诉。

不是所有数据都适合缓存

坑 3:K8s 上 Single-Point Postgres

K8s 里跑 Postgres,挂了没自动备份 → 数据全丢。

数据库用托管服务(Cloud SQL、RDS),不要自管。

坑 4:读写分离后立即读

写完主库,立刻读从库,但同步还没到 → 读到旧数据。

解决:

  • 写后强制读主
  • 用"读己写"一致性(SQLAlchemy 的 session 复用)

坑 5:gRPC 对外暴露

浏览器调不了 gRPC(需要 grpc-web 转换)。

对外 API 用 REST/gateway,内部用 gRPC。

坑 6:Kafka 当数据库用

消费后消息还在 Kafka,觉得"反正有日志"。某天磁盘满了,Kafka 挂了,数据丢。

Kafka 是消息队列不是数据库。消费完该入库的还是要入库。


8. 检查清单

  • 我能解释负载均衡的轮询、最少连接、IP hash 算法
  • 我知道 CDN 解决什么问题,LLM 应用怎么用
  • 我能解释读写分离、分片、冷热隔离
  • 我知道为什么不要过早分片
  • 我能解释 ZooKeeper / etcd / Nacos / Consul 是干嘛的
  • 我知道 Kafka 和 RabbitMQ 的区别
  • 我知道 gRPC 和 REST 的区别,各自什么时候用
  • 我能看懂 K8s 的 Deployment + Service YAML
  • 我知道 HPA 自动扩缩容的原理
  • 我能画一张 100 万日活的 LLM 应用架构图
  • 我知道单体和微服务的边界(康威定律)

9. 下一步

架构理解到位了!下一站可靠性:

让系统在 LLM 乱说、模型故障、突发流量下,还能稳定运行。

进阶学习资源