模块二-架构基础功 | 第13讲:系统设计实战 - 如何设计一个日处理 10 万 PR 的代码审核平台
开场:把面试题变成交付物,而不是背八股
系统设计的价值不在于“画一张看起来很厉害的架构图”,而在于你能把 需求 → 约束 → 估算 → 权衡 → 演进路线 串成一条可执行的决策链。本讲以 CodeSentinel 为主线,用接近 系统设计面试 的叙述方式,演练如何在 日处理 10 万 PR 的量级下,仍然保持成本可控、延迟可接受、LLM 调用可治理。你会看到:功能需求与非功能需求如何分开写;如何做 背板估算 让团队对机器规模心里有数;为什么 队列 + 水平扩展 Worker 往往比同步堆 API 线程更稳;以及 限流、缓存、批处理 在 AI 审核场景里如何决定盈亏。
本讲假设你已经理解模块二前几讲的分层与一致性取舍:权威状态仍在关系型数据库,派生索引与报表允许滞后。这里我们把镜头拉远,讨论 多服务、多队列、多副本 下的吞吐与弹性。完成本讲后,你应能对外解释:我们的峰值 QPS 大概是多少、Worker 需要多少核、向量索引写入带宽来自哪里、LLM 预算如何不被突发流量击穿。下一讲我们将回到代码结构本身,讨论重构与坏味道;本讲先确保“系统能撑住”。你也可以把本讲当作 架构师面试自述:用 CodeSentinel 的真实约束讲清 trade-off,而不是背诵通用模板。
把“10 万 PR/天”当作训练基准,是因为它强迫你做 全链路乘法:不是只看 API 网关,而是把 diff 大小、检查项数量、嵌入维度、重试倍数 全部算进去。很多团队在引入大模型后出现的成本失控,本质上是把 同步链路 当成默认,而没有用队列做 负载整形(load leveling)。CodeSentinel 的架构师要把这一点写成显式原则:API 负责受理与查询,重计算进 Worker,观测贯穿两者。下面先用全局架构图把组件关系钉住,再进入估算与代码。
面试场景里,面试官常会追问“瓶颈在哪里”。对本讲架构,最常见的真实瓶颈依次是:LLM 供应商限额、向量索引写入带宽、Git/对象存储读取、PostgreSQL 写入热点(例如全局计数器或单表索引不当)。你的回答不要泛泛说“可以加机器”,而要指出 哪一类资源不可线性扩展,以及你们如何用 分片、批处理、缓存、降级 缓解。CodeSentinel 若把“每次审核都全量 embedding 全仓库”,那成本曲线会离谱;更合理的通常是 增量变更 + 受影响文件子集 + 近邻检索,把计算聚焦在风险最高的差异上。
全局视角:CodeSentinel 大规模拓扑与数据流
第一张图给出 负载均衡 → 网关 → 服务 → 队列 → Worker → 存储 的教学版拓扑。实际生产会再拆分租户、多区域与专线,但主干逻辑一致。
flowchart TB
LB["负载均衡 / Ingress"]
GW["API Gateway<br/>(鉴权/配额/路由)"]
API["FastAPI 控制面"]
Q["Redis Streams<br/>或 Kafka"]
W1["Review Worker"]
W2["Review Worker"]
WN["Review Worker ..."]
PG[("PostgreSQL<br/>元数据权威")]
RD[("Redis<br/>缓存/队列/限流")]
VDB[("Vector DB<br/>代码嵌入索引")]
OBJ[("对象存储<br/>补丁快照")]
LB --> GW --> API
API --> PG
API --> RD
API --> Q
Q --> W1 & W2 & WN
W1 & W2 & WN --> PG
W1 & W2 & WN --> VDB
W1 & W2 & WN --> OBJ
W1 & W2 & WN --> RD
第二张图描述 一次 PR 审核的数据流:从入队到落库、再到异步索引与缓存回填。
flowchart LR
A["客户端提交 PR 审核"] --> B["API 写入 review 记录"]
B --> C["投递队列消息"]
C --> D["Worker 拉取"]
D --> E["拉取 diff / 构建上下文"]
E --> F["规则检查 + LLM 调用"]
F --> G["持久化 findings"]
G --> H["发布完成事件"]
H --> I["索引 upsert"]
I --> J["缓存失效/回填"]
第三张图强调 水平扩展与背压:Worker 数量随队列深度弹性伸缩,网关层用 令牌桶 限制 LLM 调用提交速率,避免把第三方 API 与内部预算同时打爆。这里刻意把“扩容”画在队列深度驱动上,是要强调:CPU 低但 lag 高 时,盲目加 API 副本往往无效;正确动作是加 Worker 或修消费逻辑。
flowchart TB
subgraph Control["控制面弹性"]
HPA["队列深度指标"] --> SCALE["扩容 Worker"]
end
subgraph Protect["保护面"]
RL["全局限流 / 租户配额"]
CB["熔断与半开探测"]
DLQ["死信队列 + 人工介入"]
end
Q2["任务队列"] --> HPA
API2["提交接口"] --> RL
W["Worker 执行"] --> CB
W --> DLQ
核心原理:需求、估算、存储与扩展策略
1. 需求分层:先写清“必须”和“最好”
功能需求(示例):创建审核、查询状态、拉取 findings、按仓库策略触发检查、对接 CI、生成报告导出。
非功能需求(示例):峰值 10 万 PR/天;P95 入队延迟 < 200ms;审核完成时间 P95 < N 分钟(由检查项与模型决定);可用性 99.9%;多租户隔离;审计可追溯;成本上限可配置。
2. 背板估算:把乘法写在白板上
估算的第一步不是拿计算器,而是 统一单位:PR/天、PR/秒、checks/PR、tokens/check、美元/百万 tokens。团队常在“千 tokens”和“百万 tokens”之间混用,导致预算差三个数量级。第二步是把 峰值 定义成可检验的:例如“工作日 10:00-12:00 的 P99 入站速率”,而不是拍脑袋“10 倍”。第三步是为每一项假设标注 置信度:来自日志的是高置信,来自访谈的是中置信,来自猜测的必须标红并在 PoC 里验证。
假设:
- 10 万 PR/天 ≈ 1.16 PR/s 平均;峰值按 10× 估算 ≈ 12 PR/s 入站(仍可能更高,需按业务活动校准)。
- 每 PR 平均 500 行变更(教学假设),每行 tokenizer 后按 1.2 token 粗算 ≈ 600 tokens;再加系统提示与上下文,可能到 8k~32k tokens/PR(取决于你是否全量塞入模型窗口)。
- 每 PR 3 类检查(静态规则、相似度检索、LLM 深度评审各算一类),其中 LLM 检查可能是 1~3 次调用。
则 LLM 调用量粗算:10 万 × 2(中位)≈ 20 万次/天;峰值再乘倍数。若每次平均 6k tokens 输入 + 1k 输出,则 token/天 与 费用 可直接映射到预算。架构师必须把这个表给财务与产品看,否则“智能审核”会变成不可控支出。
3. 为什么队列是核心:同步链路的隐藏二次成本
同步执行审核会把 长尾延迟 直接暴露给 HTTP 客户端:Git 拉取慢、对象存储慢、模型慢,任何抖动都会放大为超时重试,进而放大为 重复任务。队列把计算从请求线程剥离,使 API 只做 受理 + 持久化 + 排队,用户体验改为 轮询/推送进度,系统获得 削峰填谷 能力。
4. 数据库与存储选型:PostgreSQL 仍然是元数据真相源
- PostgreSQL:
reviews、findings、jobs、tenants、policies等强一致实体。 - Redis:队列(Streams)、去重键、分布式锁、热点缓存、限流计数器。
- Vector DB:代码块嵌入与相似缺陷检索;与上讲一致,属于 最终一致 派生视图。
- 对象存储:大 diff、附件、报告快照,配合预签名 URL。
5. Worker 扩展:无状态、可抢占、可重入
Worker 进程应尽量 无本地状态:从队列读出 job_id,加载任务描述,执行,写回结果,ack 消息。执行过程必须 幂等(见上一讲)。Kubernetes 上按 队列 lag 与 CPU 利用率 组合扩容,避免单指标误判。
6. LLM 限流与节流:保护预算与保护供应商
全局限流 + 租户配额 + 关键字成本标签(不同模型不同桶)。对可延迟任务使用 延迟队列,在预算耗尽时降级到规则-only 模式并标记报告“未启用深度模型”。
7. 缓存策略:结果缓存与嵌入缓存
- 审核结果缓存:键可设为
(repo_id, commit_sha, policy_pack_version, checks_version),任一变化即失效。 - 嵌入缓存:键为
(file_hash, chunk_span, embedding_model_version),命中率高时能显著降本。
8. 观测与 SLA:没有指标就没有“可扩展”
至少监控:入队速率、队列深度、消费延迟、Worker 错误率、LLM 429/5xx、token 用量、索引滞后、缓存命中率。把 SLO burn rate 接入告警,比单纯“CPU 高”更有效。
9. 多租户公平性:避免“大租户吃掉队列”
公平调度策略包括:每租户并发上限、加权轮询、单独队列与 Worker pool(贵客隔离)。CodeSentinel 若服务开源社区与企业客户,隔离是商业化底线。
10. 演进路线:从单区域到多区域
第一阶段单区域 + 多副本;第二阶段只读副本与就近缓存;第三阶段事件跨区复制与冲突解决(成本高)。架构评审要把阶段边界写清,避免一开始就做全球强一致幻想。
11. 安全与合规:大规模下的放大效应
10 万 PR/天意味着密钥轮换、审计日志、供应链检查都必须是 自动化 的。API Gateway 统一 OAuth、mTLS、内部服务 JWT;敏感代码片段进入模型前要执行 脱敏策略(与数据治理模块联动)。
12. 与 Clean Architecture 的对齐:扩展的是部署,不是领域规则
领域规则仍应在内核;扩展 Worker 只是增加 基础设施适配器 的实例数。不要在 Worker 里复制一份“临时业务规则”,否则多实例部署会变成 规则漂移灾难。
13. 背板估算进阶:把“平均”拆成分布,而不是一句口号
真实 PR 的 diff 体积往往呈 长尾:大多数很小,但少数巨型 PR 会拖垮 Worker。架构上要对 pr_lines 或 patch_bytes 做 分层路由:小 PR 走快路径(更便宜的模型与更少的检索),大 PR 走慢路径(分块、MapReduce 式汇总、甚至人工确认)。否则你的估算是按平均值成立的,但线上是按 P99 崩的。CodeSentinel 可以在入队消息里携带 size_tier 字段,由网关根据统计阈值计算。
14. API 形态:同步、异步与 Webhook 的组合拳
企业客户常要求 CI 阻塞式 结果(同步),而平台内部仍应 异步执行:做法是网关接受同步请求后立即创建任务,客户端以 长轮询 / Webhook / SSE 获取最终结果。把“外部协议”和“内部执行模型”解耦,是规模化的关键。否则你会为了兼容 Jenkins 插件而把重计算塞回 HTTP 线程池。
15. 数据一致性与搜索体验:大规模下的产品策略
当索引滞后从秒级变分钟级,产品必须提供 可解释的进度:例如“深度检索同步中(预计 2 分钟)”。这与第12讲的读写分层一致,但在 10 万 PR/天时会变成 客服工单结构问题,需要提前设计 FAQ 与状态机文案。
16. 费用归因:让每一次 LLM 调用都能追溯到租户与仓库
把 tenant_id、repo_id、review_id、check_name、model、token_in/out 写入 不可变计费流水(可存 PostgreSQL 分区表或低成本列存)。没有归因,你无法做公平限流,也无法向大客户解释账单。架构师要在第一版就预留字段,而不是等财务介入再改表。
17. 弹性与冷启动:队列瞬间堆积时 Worker 扩容的上限
云厂商扩容不是瞬时完成的,且镜像拉取、JIT、连接池预热都会吃时间。策略包括:最小副本数 > 0、提前按日历扩容(大型活动前)、队列随机抖动入队(避免同一秒爆炸)。CodeSentinel 若对接开源活动(Hackathon),没有日历扩容几乎必炸。
18. 测试数据与合成流量:用“可控洪水”验证限流
在生产影子环境用真实大小的 patch 回放,验证令牌桶参数、Worker 并发、数据库连接池是否匹配。别只在开发机用 10 行 diff 测功能,那测不出规模。
19. 与 LangChain 的关系:框架是工具链,不是性能承诺
LangChain 适合编排与工具调用,但大规模下你要关注 每步阻塞点:串行工具调用会把吞吐压扁。应把可并行部分(多个文件 embedding)并行化,同时用信号量限制并行度,避免把向量库与 LLM 同时打到极限。
20. 设计评审清单(可直接贴到 Confluence)
需求是否区分峰值与平均?是否定义完成口径(落库 vs 可搜)?是否定义降级模式?是否定义租户隔离?是否定义计费归因?是否定义死信处理?是否定义密钥轮换?只要有一项空白,就不该进入“拍脑袋买机器”的阶段。
代码实战:基于 Redis Streams 的审核 Worker 与令牌桶限流
下面给出 可在本机运行 的精简示例:TokenBucketLimiter 用于保护 LLM 调用;ReviewWorker 从 Redis Stream 消费任务并模拟审核;enqueue_review_job 模拟 API 入队。你需要本地 Redis,并安装 redis 包。
文件:requirements.txt
redis>=5.0.0
文件:rate_limiter.py
from __future__ import annotations
import time
from dataclasses import dataclass
import redis
@dataclass(frozen=True)
class RateLimitResult:
allowed: bool
retry_after_ms: int
class TokenBucketLimiter:
"""
分布式令牌桶(Redis + Lua):
- key: rl:llm:global
- 每秒 refill rate,burst 容量 cap
生产环境应分租户分模型分桶。
"""
def __init__(self, client: redis.Redis, key: str, rate_per_sec: float, burst: int) -> None:
self._r = client
self._key = key
self._rate = float(rate_per_sec)
self._burst = int(burst)
_lua = """
local key = KEYS[1]
local now = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local burst = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local data = redis.call("HMGET", key, "tokens", "ts")
local tokens = tonumber(data[1])
local ts = tonumber(data[2])
if tokens == nil then
tokens = burst
ts = now
end
local delta = math.max(0.0, now - ts)
tokens = math.min(burst, tokens + delta * rate)
if tokens < requested then
local missing = requested - tokens
local retry_after_ms = math.ceil((missing / rate) * 1000)
redis.call("HSET", key, "tokens", tokens, "ts", now)
return {0, retry_after_ms}
end
tokens = tokens - requested
redis.call("HSET", key, "tokens", tokens, "ts", now)
return {1, 0}
"""
def consume(self, tokens: int = 1) -> RateLimitResult:
now = time.time()
allowed, retry_after_ms = self._r.eval(self._lua, 1, self._key, now, self._rate, self._burst, tokens)
return RateLimitResult(bool(allowed), int(retry_after_ms))
def mock_llm_call() -> str:
return "finding: possible N+1 query in repository layer"
文件:worker.py(消费循环 + 简易限流等待)
from __future__ import annotations
import json
import time
import uuid
from dataclasses import dataclass
from typing import Any, Dict, Optional
import redis
from rate_limiter import RateLimitResult, TokenBucketLimiter, mock_llm_call
STREAM_KEY = "codesentinel:review:jobs"
GROUP = "review-workers"
CONSUMER = f"worker-{uuid.uuid4().hex[:8]}"
@dataclass(frozen=True)
class ReviewJob:
review_id: str
tenant_id: str
pr_lines: int
checks: int
class ReviewWorker:
def __init__(self, client: redis.Redis) -> None:
self._r = client
self._limiter = TokenBucketLimiter(
client,
key="rl:llm:global",
rate_per_sec=5.0, # 教学:每秒 5 次 LLM(全局)
burst=10,
)
def ensure_group(self) -> None:
try:
self._r.xgroup_create(STREAM_KEY, GROUP, id="0-0", mkstream=True)
except redis.ResponseError as exc:
if "BUSYGROUP" not in str(exc):
raise
def enqueue(self, job: ReviewJob) -> str:
payload: Dict[str, Any] = {
"review_id": job.review_id,
"tenant_id": job.tenant_id,
"pr_lines": job.pr_lines,
"checks": job.checks,
}
msg_id = self._r.xadd(STREAM_KEY, {"payload": json.dumps(payload, ensure_ascii=False)})
return msg_id.decode("utf-8") if isinstance(msg_id, (bytes, bytearray)) else str(msg_id)
def _handle_job(self, job: ReviewJob) -> None:
# 每个 PR 假设触发 checks 次 LLM(教学)
for i in range(job.checks):
while True:
res = self._limiter.consume(1)
if res.allowed:
break
time.sleep(max(res.retry_after_ms, 1) / 1000.0)
_ = mock_llm_call()
# 这里应写入 PostgreSQL;示例用 Redis 哈希演示落库效果
self._r.hset(f"review:{job.review_id}", mapping={"status": "completed", "tenant": job.tenant_id})
def run_forever(self, block_ms: int = 5000) -> None:
self.ensure_group()
while True:
resp = self._r.xreadgroup(
GROUP,
CONSUMER,
streams={STREAM_KEY: ">"},
count=5,
block=block_ms,
)
if not resp:
continue
for _stream, messages in resp:
for msg_id, fields in messages:
raw = fields.get(b"payload") or fields.get("payload")
if isinstance(raw, bytes):
raw_s = raw.decode("utf-8")
else:
raw_s = str(raw)
data = json.loads(raw_s)
job = ReviewJob(
review_id=data["review_id"],
tenant_id=data["tenant_id"],
pr_lines=int(data["pr_lines"]),
checks=int(data["checks"]),
)
try:
self._handle_job(job)
self._r.xack(STREAM_KEY, GROUP, msg_id)
except Exception:
# 生产:进入 DLQ / 重试计数 / 告警
raise
def demo() -> None:
r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=False)
w = ReviewWorker(r)
w.ensure_group()
for i in range(20):
jid = w.enqueue(
ReviewJob(
review_id=f"rev-{i}",
tenant_id="tenant-a" if i % 2 == 0 else "tenant-b",
pr_lines=500,
checks=3,
)
)
print("enqueued", jid)
# 注意:demo 只入队;消费请运行 `python worker.py consume` 或调用 run_forever
if __name__ == "__main__":
import sys
r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=False)
worker = ReviewWorker(r)
if len(sys.argv) > 1 and sys.argv[1] == "consume":
worker.run_forever()
else:
demo()
如何把示例映射到 CodeSentinel 真系统
- API 层:
POST /reviews写入 PostgreSQL 后XADD,返回202与review_id。 - Worker 层:拉取后执行 规则引擎端口 与 LLM 端口;限流键细化为
rl:llm:{tenant}:{model}。 - 缓存层:对
(repo, sha, policy_version)做结果短路;miss 才进入完整链路。 - 观测层:在
consume前后打点,记录等待令牌耗时与执行耗时。
代码阅读提示:别忽略 ACK 与重试的边界
XREADGROUP 未 ACK 的消息会进入 Pending Entries List,需要 XPENDING/XCLAIM 治理。教学示例为短代码直接 raise,生产必须引入 最大投递次数 与 死信流。否则队列会在“半成功”状态堆积,表现为 lag 高但 CPU 低 的经典症状。
与上一讲幂等的关系
同一条 review_id 可能被重复入队(客户端重试、网关重发)。Worker 应在落库 findings 时使用 唯一约束 或 幂等键,把重复执行变成 可证明的无害。
本地运行步骤(建议照做一遍)
- 启动 Redis:
docker run --rm -p 6379:6379 redis:7。 - 将
rate_limiter.py与worker.py放在同一目录,创建虚拟环境并pip install redis。 - 先运行
python worker.py入队 20 条任务;再开第二个终端运行python worker.py consume,观察令牌桶如何把 LLM 调用限制在每秒 5 次附近(可用日志或打印验证)。 - 把
checks调到 10 并快速入队 200 条,观察队列深度上升;思考若增加 Worker 副本,是否会线性提升吞吐直到触及 LLM 限额。
从 Redis Streams 迁移到 Kafka 的触发条件
当需要 更强保留策略、更复杂重放、跨团队多消费者生态 时,Kafka 更常见。代价是运维复杂度上升。教学示例用 Streams 是为降低门槛;架构评审要写的是 触发条件 而不是“Kafka 更酷”。
网关限流 vs Worker 限流:两层缺一不可
网关限流保护 入口 与被刷接口风险;Worker 限流保护 下游 与预算。只做一个会在另一侧失守。CodeSentinel 建议网关按租户做 并发连接 + QPS,Worker 侧按模型做 token/分钟 估算限流(可把 token 近似为调用次数乘以经验系数)。
生产环境实战:从演示到可运维平台
-
容量规划表:把 PR/天、峰值倍数、每 PR checks、token 单价写入表格,每月复盘偏差。
-
配额策略:企业租户购买“每日 token 包”,耗尽后自动降级并通知负责人。
-
隔离策略:大客户独立队列与 Worker Deployment,避免噪声邻居。
-
数据局部性:Worker 与 Git 镜像、对象存储同区域,降低拉取延迟。
-
回归测试:对关键规则集做 金丝雀 PR 集,每日跑一遍验证引擎版本升级无回归。
-
故障演练:随机杀 Worker Pod,验证 PEL 回收与任务最终完成率。
-
成本告警:token 用量 15 分钟环比异常 + 队列深度异常 → 可能是重试风暴或遭刷接口。
-
API 防刷:网关层人机验证、签名、IP 信誉;内部服务 mTLS。
-
模型路由:小模型做初筛,大模型做深度;把路由策略配置化,避免改代码热修复。
-
合规:代码出境、模型托管区域、客户合同约束要在架构图上有明确边界。
-
备份与恢复:PostgreSQL PITR、对象存储版本控制、向量索引重建流程要写进 Runbook;否则“元数据回来了,但向量全丢”会让搜索侧长期不可用。
-
配置治理:
policy_pack_version与规则集发布要走 CI,不允许 Worker 本地硬编码;否则多副本会出现 不同 Worker 不同规则 的隐性分区。 -
发布策略:Worker 与 API 版本握手(兼容旧消息格式),用 双写/双读 过渡期处理 schema 演进;禁止“停全世界升级”。
-
客户可见 SLA:把“完成”定义成数据库状态还是报告生成完成,要写进合同;否则销售与工程会对同一指标各自解释。
-
绿色节能与成本:非高峰自动缩容到最小副本,配合 Cron 把批处理任务放到低价时段(若合规允许)。
-
连接池与文件描述符:1 万个 Worker 副本并不等于更快;过多并发会把 PostgreSQL 连接打满。要用 池化、批处理写入、异步提交,并在架构层明确“最大安全并发”。
-
灰度与特性开关:新检查项默认影子模式(只记录不阻断),通过采样比例逐步放大,避免新规则导致队列全局堆积。
-
客户集成节奏:Webhook 投递失败重试要指数退避并设置上限,避免对客户系统造成 DDoS 观感;同时提供签名与时间戳防重放。
-
供应链安全:依赖包扫描、SBOM、镜像漏洞扫描在大规模平台里是基础项;否则一次供应链事件会放大成大规模数据外泄风险。
-
组织配套:SRE、平台、算法、数据、合规的 RACI 表要与架构图同步更新;系统能扩展但组织推诿会让平台停在纸面。
本讲小结:大规模审核平台思维导图
mindmap
root((10万PR天))
需求
功能
非功能SLO
估算
PR速率
token乘法
峰值倍数
架构
网关保护
队列削峰
Worker水平扩展
数据
PostgreSQL权威
Redis队列缓存限流
VectorDB派生索引
治理
租户隔离
成本配额
观测告警
延伸阅读:把本讲输出成团队对齐文档
建议你课后整理一份 一页纸架构(One-pager):左侧写用户故事与 SLO,中间画数据流,右侧写风险与降级。再附一张 估算表:峰值 PR/s、每 PR token、每日费用区间、Worker 副本区间。文档的价值在于让产品、工程、财务对同一套数字讨论;否则“10 万 PR”只会变成口号。另建议把 队列深度 与 LLM 429 比例 绑定为发布门禁:新版本若让重试放大,应在预发环境就被拦住,而不是靠线上用户投诉发现。
如果你需要向管理层解释“为什么必须上队列”,可以用一句非常朴素的话:同步系统把不确定性留给用户超时,异步系统把不确定性转化为可观测的队列深度。CodeSentinel 的用户可以接受“审核需要几分钟”,但很难接受“网关随机 504”。因此本讲的工程结论往往比具体中间件更重要:先把执行模型做对,再争论 Redis 还是 Kafka。最后提醒一句:任何估算都要标注假设来源(客户访谈、历史日志、PoC 压测),否则数字会在复盘时被一票否决。把假设写出来,不是示弱,而是让团队能一起把误差缩小;这也是专业工程沟通的一部分。
思考题
-
若 LLM 供应商突发限流(429),你会优先牺牲吞吐还是牺牲延迟?如何让用户可感知?
-
结果缓存键包含哪些版本字段才能避免“错缓存导致误判”?
-
单队列 vs 多队列(按租户/优先级)各有什么运维代价?
-
如果把审核结果缓存从 Redis 换成 CDN 边缘缓存,会带来哪些一致性与安全问题?
下一讲预告
下一讲我们进入 代码重构的艺术:面对 AI 生成代码常见的“上帝类、长方法、霰弹式修改”等坏味道,如何用 安全重构手法 与 CodeSentinel 重构建议引擎 把质量拉回可控区,并用测试网兜住风险。那里我们会更多讨论“如何让机器提出改动、让人类负责合并”,与本讲的“如何让系统扛住流量”形成互补:性能与成本解决能不能跑,重构与评审解决跑得好不好。