mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
914 字
2 分钟
为什么基础服务不应该高可用
2023-03-03

在互联网架构中,“高可用”是一个被过度使用的词汇。每个服务都在追求 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

特性EurekaZooKeeper
架构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 不可用时,事务超时自动回滚

七、总结#

基础服务不应该追求高可用的原因

原因说明
正确性优先数据不一致比不可用更危险
简化设计高可用带来复杂度
降低成本避免过度工程
上层容错让上层服务处理降级

正确的架构

  1. 基础服务追求正确性和简单性
  2. 接受基础服务的不可用
  3. 在上层服务中实现容错和降级
  4. 使用幂等性设计处理重试

核心哲学:分布式系统的本质是处理故障,而不是避免故障。

参考引用#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

为什么基础服务不应该高可用
https://blog.souloss.com/posts/why-the-design/why-basic-services-dont-need-high-availability/
作者
Souloss
发布于
2023-03-03
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时