系统设计中的强一致性 vs 最终一致性
Source: Dev.to
为什么这在分布式系统中很重要(以及你为何需要了解它)
现代软件很少局限于单台机器。它分布在多个服务器和地区,以保持对所有用户的快速和可靠。
这种分布很强大,但它带来了 一致性 的挑战:当你在不同服务器上拥有数据的多个副本时,你必须决定 这些副本如何保持同步。
你处理一致性的方式决定了系统在负载或网络故障下的行为。作为开发者或架构师,你必须 有意地选择一致性模型——否则系统会为你做出选择,往往在最不方便的时候。
一致性在分布式系统中的体现
一致性 与时机有关。
“在我保存一条数据后,其他人多久能看到更新?”
把它想象成一个群聊:
| 模型 | 用户看到的内容 |
|---|---|
| 强一致性 | 每个人在完全相同的时间看到完全相同顺序的每条消息。没有人能回复他们还没看到的消息。 |
| 最终一致性 | 有些人看到消息的时间可能比其他人晚几秒。最终所有人都会看到相同的历史,但在短暂的时间里,“真相”会有所不同。 |
在它们之间的选择并非纯技术问题;它是 具有架构后果的业务决策。
注意: 这 不是 单节点数据库中的 ACID 一致性。
ACID 描述的是状态转换;分布式一致性 描述的是这些状态变化 何时 可见。
强一致性
强一致性保证 一旦写入数据,随后所有的读取都会返回该新值,无论用户连接到哪台服务器。
工作原理
- leader / primary node 对写入进行排序。
- Synchronous replication 到副本。
- Quorums(多数同意)在写入成功前必须达成。
写入只有在系统能够保证任何后续读取都会看到它时才被视为成功。
常见后果
- 写入必须等待 确认。
- 读取可能阻塞,直到系统确认最新状态。
- 网络分区会降低可用性(系统倾向于正确性而非在线可用性)。
正确性至关重要的场景
| 场景 | 为何需要强一致性 |
|---|---|
| 你转账 | 银行余额绝不能出现错误,即使是一瞬间。 |
| 你购买最后一件商品 | 电商库存、限时抢购、票务预订——两个人不能买到同一个最后的座位。 |
| 你失去或获得访问权限 | 权限变更必须即时传播(例如撤销管理员权限)。 |
| 你触发使用限制 | API 速率限制或订阅上限必须精确执行。 |
| 你切换关键开关 | 功能标记、紧急关闭开关、安全开关——部分发布可能会快速导致生产故障。 |
强一致性的好处
- 可预测性: 行为类似单节点数据库。
- 安全性: 没有用户会看到过时或错误的值。
- 正确性优先于可用性: 当需要权衡时,系统选择“正确性”。
Eventual Consistency
Eventual consistency allows different servers to hold different versions of the data for a short window. The system promises that “eventually” all copies will converge, but it does not wait for convergence before completing the request.
How It Works
- Asynchronous replication – 写入记录在一台服务器上,请求会立即返回“Success”。
- Background synchronization 将数据复制到其他副本。
- Conflict‑resolution strategies 处理分歧的更新。
The system prioritizes availability, low latency, and partition tolerance. Stale reads are possible, but the system stays responsive.
When Slight Staleness Is Acceptable
| 场景 | 为什么最终一致性是可以接受的 |
|---|---|
| You refresh your feed | 你刷新动态 |
| You open an analytics dashboard | 你打开分析仪表盘 |
| You get recommendations | 你获取推荐 |
| You search for something | 你搜索某些内容 |
| You receive notifications | 你收到通知 |
Benefits (Eventual Consistency)
- Performance: 极其快速,因为没有协调延迟。
- Resilience: 即使许多节点出现分区,系统仍能接受写入并提供读取。
- Scalability: 在大规模(例如全球服务)下表现良好。
Trade‑offs
- Confusion: 用户在刷新后可能看到过时的数据。
- Complexity: 开发者必须在不同副本上出现并发更新时处理冲突。
实际案例:DynamoDB
Amazon DynamoDB 默认是 最终一致性。写入后,读取可能会返回陈旧的数据,但你可以在每个请求上请求 强一致性。
// Write (eventual consistency)
await dynamodb.put({
TableName: "Users",
Item: {
userId: "42",
email: "new@email.com"
}
});
// The write succeeds, but replicas may not all be updated yet.
// Strongly consistent read (optional)
const result = await dynamodb.get({
TableName: "Users",
Key: { userId: "42" },
ConsistentRead: true // Guarantees the latest committed value
});
- 默认(最终一致性): 延迟更低,可用性更高。
- StrongRead 标志: 以稍微增加的延迟和可用性换取数据的新鲜度保证。
选择合适的模型
- 确定业务需求 – 瞬时不一致是无害的还是灾难性的?
- 将场景映射到一致性需求 – 使用上面的表格作为快速参考。
- 利用每次操作的选择 – 许多服务(例如 DynamoDB、Cosmos DB)允许你为每个请求选择一致性。
- 为冲突解决进行设计 – 如果选择最终一致性,需规划如何合并分歧的更新。
通过了解 何时 以及 何地 应用每种模型,你可以避免过度设计(始终强一致)或保护不足(始终最终一致)的系统。
祝架构愉快!
await dynamodb.get({
TableName: "Users",
Key: { userId: "42" },
ConsistentRead: true
});
你可以保证收到最新提交的值,但代价是更高的延迟和吞吐量使用。
在设计下一个功能时,问问自己:“如果用户看到的是五秒前的数据,最糟糕的情况是什么?”
- 如果答案是 “没什么大不了的,” 请选择 最终一致性,享受额外的速度。
- 如果答案是 “我们会失去金钱或信任,” 则坚持使用 强一致性。
强一致性 将正确性置于可用性之上。
最终一致性 将可用性和可扩展性置于即时性之上。
有意识地权衡这些取舍,你构建的系统不仅在技术上可靠,而且与用户的真实需求保持一致。