前面十一篇我们一件一件地认识了武器:网络、数据库、缓存、队列、容器、微服务。最后这一篇回答两个问题:怎么把这些武器组装成一个打不垮的系统,以及怎么从「会用」走到「精通」。架构能力说到底不是记住多少组件,而是在约束和权衡中做决策的能力。
一、高可用:假设一切都会挂
高可用(High Availability)的第一性原理只有一句话:任何单点都会故障,所以任何关键角色都要有备份。把这句话在系统的每一层问一遍,架构图就长出来了:
- 应用层:无状态服务多副本部署,前面挂负载均衡。一个实例挂了,流量自动切走。这也是为什么我们反复强调服务要无状态——状态放 Redis 和数据库里,实例才是随时可以替换的「牛」而不是「宠物」。
- 数据层:数据库主从复制,主库挂了failover(故障切换)到从库。这里有个关键取舍:自动切换快但可能丢最后几条未同步的写入;人工确认安全但停机时间长。不同业务答案不同。
- 机房与地域层:整个机房断电、光缆被挖断怎么办?多活在多个机房/城市同时对外服务,一个机房整体消失,流量在 DNS 或全局负载均衡层切走。多活最大的难点从来不是应用,而是数据跨地域同步——延迟和冲突(两边同时改同一条数据)让真正的双写多活极其昂贵,所以业界常见的是「异地灾备」:远端机房平时不服务,只同步数据,灾难时顶上。
可用性常用「几个 9」衡量:99.9% 意味着一年允许停机约 8.8 小时,99.99% 约 53 分钟。每多一个 9,成本可能翻几倍。架构师的第一个问题是:这个业务真的需要几个 9? 内部后台系统追求 5 个 9 是浪费,支付系统只有 2 个 9 是事故。
冗余解决「挂了一个还有一个」,但还有一类故障是「都没挂,只是扛不住」——流量超过容量。所以高可用的另一半是容量规划:通过压测(用 wrk、k6 这类工具逐步加压)摸清单实例的真实极限,画出「QPS-延迟曲线」,找到延迟开始飙升的那个拐点,然后按「峰值流量 ÷ 拐点容量 × 冗余系数」决定部署多少实例。凭感觉加机器和凭感觉减机器一样危险。
二、可观测性三支柱:Metrics、日志、Tracing

系统上线只是开始,看不见的系统等于没有系统。可观测性有三根支柱,各自回答不同的问题:
- Metrics(指标):回答「系统整体健康吗」。QPS、错误率、P99 延迟、CPU、连接数,以时间序列存储(Prometheus 是事实标准),配 Grafana 出图。指标的核心价值是告警——在用户发现之前发现。告警的黄金法则是「告警必须 actionable」:一条告警如果不能让人立刻做点什么,它就是噪音,噪音多了,真的告警也会被无视。
- Logging(日志):回答「具体发生了什么」。结构化日志(JSON 格式、带上
traceId)比自由文本好用一百倍,因为它可以被 ELK/Loki 检索和聚合。日志要克制:关键路径、异常、状态变更必打,高频循环里别打。 - Tracing(链路追踪):回答「这个请求慢在哪一环」。上一篇讲过,三者在
traceId上汇合:告警发现错误率上升 → 用 traceId 找到慢的调用链 → 翻对应时间点的日志定位根因。
这套「指标告警 → 链路定位 → 日志取证」的流程,就是线上排障的标准动作。
三、案例拆解一:短链系统
需求:把长网址转成 https://s.cn/abc123 这样的短链,访问短链 302 跳转到原地址。这是系统设计面试的经典题,因为它小中见大。
发号是关键。短码怎么生成?常见方案:
- 自增 ID + Base62:数据库自增 ID 转 62 进制(0-9a-zA-Z),7 位能表示 3.5 万亿个,够用。缺点是可被遍历猜出总量。
- 哈希 + 冲突处理:对长 URL 做 MurmurHash 取前几位,撞了就在原 URL 后加盐重试。
- 发号器预生成:独立服务离线批量生成短码放进池子,创建时直接取一个,彻底无冲突,还能过滤敏感词。
读路径是性能重点。短链是典型的一写多读:跳转请求量是创建量的成百上千倍。跳转逻辑要打到缓存——短码到长 URL 的映射放进 Redis,命中率极高;302(临时重定向)让浏览器不缓存跳转,保证每次点击都能被统计(统计点击量是短链服务的核心商业价值)。
存储上,映射表按短码分库分表即可支撑海量数据,冷数据(一年没人点的链接)可以归档。
这个案例的心法:先找读写比和热点,把缓存放在最贵的路径上;唯一 ID 的生成方案往往就是系统的地基。
四、案例拆解二:秒杀系统
需求:1 万件商品,100 万人同时开抢。秒杀是「瞬时洪峰」的极限场景,设计原则可以总结为层层过滤,把流量尽量挡在数据库之外:
- 前端层:按钮倒计时置灰、答题/验证码拉长请求间隔。不是防作弊为主,而是把 0 点的尖峰摊成缓坡。
- 网关层:限流(令牌桶),按用户 ID 去重——同一个人 1 秒内 100 个请求只放行 1 个。
- 缓存层扣库存:库存预热到 Redis,用
DECR原子扣减。Redis 单机扛十万级 QPS 没问题,而直接 update 数据库行锁早就崩溃了。 - 消息队列削峰:Redis 扣减成功后,只往 MQ 丢一条「下单消息」就立刻返回「抢购中」;订单服务按自己的节奏消费消息、落库。洪峰被队列蓄成了细流。
- 异步确认:用户轮询或等推送拿最终结果。
-- Redis Lua 脚本:原子地判断并扣减库存
if tonumber(redis.call('GET', KEYS[1]) or '0') > 0 then
return redis.call('DECR', KEYS[1])
end
return -1
还要处理边角:超卖(Lua 脚本保证原子性已解决)、少卖(用户下单不支付,需要超时取消订单回补库存)、防刷(风控、限购)。数据库只承担「订单最终落库」这个它扛得住的平稳流量。
另一个常被追问的问题是:Redis 里的库存和数据库里的库存怎么保持一致? 答案是它们不需要实时一致——Redis 是「闸门」,决定谁能进来;数据库是「账本」,记录最终结果。订单落库时才真正扣减数据库库存,Redis 只是提前挡住了超额流量。活动结束后再以对账任务核对两边差异即可。这正是上一篇「最终一致」思想在实战中的样子:强一致只保留在单点原子的那一小段,其余靠流程和对账兜底。
最后别忘了预案:秒杀活动前压测整条链路、备好降级开关(队列积压过多时直接提示「已抢完」而不是让用户无限等待)、安排值班盯大盘。大型活动的稳定性,一半靠设计,一半靠演练。
这个案例的心法:不要硬扛峰值,而是设计一条漏斗,让每一层只做它能做的事。
五、从熟练工到专家:三件长期主义的事
技术是学不完的,但有三件事的复利最大:
读源码。选你天天在用的一个项目(用过 Redis 就读 Redis,用 Netty 就读 Netty),先读它的文档和架构文章,再带着一个具体问题去读代码,比如「Redis 的过期 key 是怎么被删除的」。读源码的目标不是记住实现,而是看一流工程师如何做权衡。
参与开源。从给文档挑错、回答 issue 开始,到提一个 bugfix PR,再到负责一个小 feature。开源给你的不是简历上的一行字,而是和全世界高水平工程师协作的实操机会,以及「代码会被无数双眼睛审视」的标准。
写复盘。每次线上事故、每次架构选型、每次性能优化,事后花半小时写下来:现象、定位过程、根因、改进。一年后回看,这就是你独有的、任何教程都给不了的知识库。经验不等于年限,经验等于你认真复盘过的次数。
最后还有一条关于选型的心法,叫**「无聊技术」原则**:每个团队的「创新配额」是有限的,把它花在能给业务带来真正差异的地方,其余地方选成熟、社区大、招人容易的技术。用上最新最炫的组件不会让你成为专家,在约束下持续做出正确的取舍才会。
小结
高可用靠冗余与故障切换在每一层消灭单点,多活解决地域级灾难但贵在数据同步;可观测性三支柱各司其职,用「告警 → 链路 → 日志」串联排障流程;短链系统教会我们看读写比、把缓存放在热路径,秒杀系统教会我们用漏斗层层消峰、让数据库只做最终落库;而读源码、参与开源、写复盘是通往专家的慢变量。这个系列到此结束,但你的后端专家之路才刚刚开始——挑一个方向,扎进去。
系列导航:上一篇《分布式与微服务:CAP、拆分与治理》(/content/backend-expert-11-microservices)
