914 字
2 分钟
为什么基础服务不应该高可用
在互联网架构中,“高可用”是一个被过度使用的词汇。每个服务都在追求 99.99% 的可用性,但你有没有想过:有些服务根本不应该追求高可用?
一、高可用的代价
1.1 高可用的成本
flowchart LR
subgraph 成本要素
H[硬件成本] --> C[多机房部署]
S[软件成本] --> C
O[运维成本] --> C
C --> T[总成本]
end
A[99.9%] -->|2x| B[99.99%]
B -->|4x| C2[99.999%]
| 可用性 | 每年宕机时间 | 成本倍数 |
|---|---|---|
| 99% | 3.65 天 | 1x |
| 99.9% | 8.76 小时 | 2-3x |
| 99.99% | 52.6 分钟 | 5-10x |
| 99.999% | 5.26 分钟 | 20-50x |
1.2 什么是基础服务?
基础服务是那些:
- 提供能力
- 其他服务强依赖
- 通常是状态ful 的
flowchart TB
subgraph 基础服务
E[配置服务]
R[注册中心]
M[元数据库]
end
subgraph 上层服务
A[用户服务]
B[订单服务]
C[支付服务]
end
A --> E
B --> E
C --> E
A --> R
B --> R
C --> R
A --> M
B --> M
C --> M
二、基础服务高可用的困境
2.1 分布式系统的困境
flowchart LR
subgraph CAP 定理
C[一致性] --> A[可用性]
A --> P[分区容错]
end
Note over C,A,P: 分区容错必须满足<br/>C 和 A 只能选一个
注册中心(ZooKeeper/Nacos):
- 选主期间不可用
- 脑裂时需要牺牲可用性
配置中心:
- 配置变更是敏感操作
- 高可用反而增加复杂度
2.2 基础服务故障的影响
当基础服务不可用时:
flowchart TB
R[注册中心故障] --> A[服务 A]
R --> B[服务 B]
R --> C[服务 C]
A --> D[无法发现服务 B]
B --> E[无法发现服务 C]
C --> F[无法发现服务 A]
D --> G[系统级故障]
E --> G
F --> G
结论:基础服务故障会导致级联失败,追求基础服务的高可用可能适得其反。
三、为什么基础服务不应该高可用?
3.1 设计哲学
核心观点:基础服务应该追求正确性而非高可用。
flowchart LR
subgraph 基础服务
H[高可用] --> C[正确性]
C --> S[简单性]
end
subgraph 上层服务
H2[高可用] --> U[用户体验]
end
| 服务类型 | 目标 | 原因 |
|---|---|---|
| 基础服务 | 正确性 | 数据不一致比不可用更危险 |
| 用户服务 | 高可用 | 用户体验优先 |
| 数据服务 | 正确性 | 数据损失不可接受 |
3.2 配置服务的例子
配置服务的高可用是陷阱:
场景:配置服务需要将"关闭支付"配置推送给所有服务
如果配置服务高可用:- 99.99% 的情况下配置正确推送- 但 0.01% 的情况可能推送不一致- 导致部分服务关闭支付,部分没有
正确做法:- 配置服务允许不可用(降级)- 配置版本化,每个服务独立决定3.3 注册中心的例子
Eureka vs ZooKeeper:
| 特性 | Eureka | ZooKeeper |
|---|---|---|
| 架构 | AP(可用性 + 分区容错) | CP(一致性 + 分区容错) |
| 选主 | 不需要 | 需要 |
| 网络分区 | 可用 | 不可用 |
| 场景 | 服务发现 | 分布式锁 |
Eureka 的设计哲学:服务发现可以暂时不一致,但必须可用。
四、正确的设计模式
4.1 基础服务降级策略
flowchart LR
subgraph 正常
A[应用] -->|读| C[配置中心]
A -->|写| C
end
subgraph 降级
B[应用] -->|读缓存| L[本地配置]
B -->|写队列| Q[配置队列]
end
配置服务降级:
- 读取:使用本地缓存
- 写入:写入本地队列,后续同步
- 优先保证功能可用
4.2 上层服务的容错
# 上层服务应该能容忍基础服务不可用class ServiceDiscovery: def __init__(self): self.cache = LocalCache() # 本地缓存 self.queue = RetryQueue() # 重试队列
def register(self, service): """注册服务,允许失败重试""" try: zk.register(service) except Exception: # 写入队列,稍后重试 self.queue.push(service)
def discover(self, service_name): """服务发现,使用缓存降级""" try: return zk.get(service_name) except Exception: # 降级到本地缓存 return self.cache.get(service_name)4.3 幂等性设计
flowchart LR
subgraph 幂等操作
R[请求] --> ID[幂等性检查]
ID -->|已处理| E[返回结果]
ID -->|新请求| P[处理]
P --> ID
end
五、什么时候追求高可用?
5.1 判断标准
| 问题 | 答案 | 优先级 |
|---|---|---|
| 服务不可用时,用户能做什么? | 等待 | 追求高可用 |
| 数据不一致时,能恢复吗? | 不能 | 追求一致性 |
| 故障的影响范围? | 全局 | 追求高可用 |
| 成本接受度? | 有限 | 选择性高可用 |
5.2 架构分层
flowchart TB
subgraph 接入层(高可用优先)
L[负载均衡]
G[网关]
end
subgraph 应用层(高可用优先)
U[用户服务]
O[订单服务]
end
subgraph 数据层(一致性优先)
DB[(主数据库)]
C[缓存]
end
subgraph 基础层(正确性优先)
Z[注册中心]
C2[配置中心]
end
六、案例分析
6.1 Netflix 的实践
Netflix 的架构哲学:
"We don't want a highly available configuration service.We want a configuration service that, when it goes down,doesn't bring down everything else."具体做法:
- 配置中心故障时,使用本地缓存
- 服务发现使用 Ribbon 客户端缓存
- 允许配置暂时不一致
6.2 蚂蚁金服的实践
分布式事务实验:
场景:分布式事务中,TC(事务协调器)故障
错误做法:TC 高可用 → 复杂的主备切换正确做法:TC 不可用时,事务超时自动回滚七、总结
基础服务不应该追求高可用的原因:
| 原因 | 说明 |
|---|---|
| 正确性优先 | 数据不一致比不可用更危险 |
| 简化设计 | 高可用带来复杂度 |
| 降低成本 | 避免过度工程 |
| 上层容错 | 让上层服务处理降级 |
正确的架构:
- 基础服务追求正确性和简单性
- 接受基础服务的不可用
- 在上层服务中实现容错和降级
- 使用幂等性设计处理重试
核心哲学:分布式系统的本质是处理故障,而不是避免故障。
参考引用
- Building Scalable Systems — 分布式系统设计
- Fallacies of Distributed Computing — 分布式系统的误区
- Google SRE Book — Site Reliability Engineering 最佳实践
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时
相关文章 智能推荐
1
为什么需要服务网格
技术科普 深入理解服务网格的设计动机,掌握微服务治理的演进历程与核心技术。
2
为什么数据库不应该使用外键
技术科普 深入解析为什么现代互联网应用中不建议使用外键,以及如何替代外键实现数据一致性。
3
为什么浮点数计算不精确
技术科普 深入解析为什么 0.1 + 0.2 = 0.300000004,IEEE 754 浮点数表示法的原理和局限性。
4
为什么 PostgreSQL 使用 MVCC
技术科普 深入解析多版本并发控制的设计原理,理解 PostgreSQL 如何实现高并发事务处理。
5
为什么 WebSocket 需要握手
技术科普 深入解析 WebSocket 握手协议的设计原理,理解从 HTTP 到 WebSocket 的协议升级机制。






