键值存储系统设计(第二部分)
本文是“设计键值存储”系列文章的第二篇。如果您尚未阅读 第一篇文章,建议先参阅该篇以了解背景。
在上一篇文章中,我们主要探讨了键值存储的基本概念,尤其是单机方案。当面临扩展性问题时,我们需要依据特定规则将数据分发至多台计算机,并通过协调器(Coordinator)将客户端请求定向至持有目标资源的节点。
设计分布式系统时,需要考虑诸多因素。将数据拆分到多台计算机时,流量平衡至关重要,因此确保键(Key)的随机分布是最佳实践。
本文将继续深入讨论分布式键值存储系统,涵盖系统可用性、一致性等核心主题。
系统可用性
评估分布式系统时,一个关键指标是系统可用性。例如,假设集群中的某台计算机因硬件故障或程序错误而崩溃,这将如何影响键值存储系统?
显然,若用户请求的资源恰好位于该故障机器上,系统将无法返回正确响应。在构建小型项目时,您可能不会优先考虑此问题;但如果您需要为数百万用户提供服务且拥有大量服务器,硬件故障将成为常态。您无法每次都手动重启服务器,这就是为什么高可用性在当今每个分布式系统中都必不可少。那么,该如何解决这一问题?
当然,您可以通过编写更强大的测试用例来提升代码健壮性。但程序始终可能存在缺陷,且硬件问题更难完全规避。最常见的解决方案是引入 副本(Replica)。通过设置拥有重复资源的计算机,我们可以大幅减少系统停机时间。例如,若单台计算机每月崩溃概率为 10%,引入一台备份计算机后,两台计算机同时宕机的概率将降至 1%(假设故障相互独立)。
副本与分片
乍一看,副本与分片(Sharding)非常相似。那么,这两者之间是什么关系?在设计分布式键值存储时,如何在副本和分片之间进行选择?
首先,我们需要明确这两种技术的目的:
- 分片:主要用于将数据拆分到多台计算机,因为单台计算机无法存储海量数据。
- 副本:是一种保护系统免于停机的方法,旨在提高可用性。
考虑到这一点,如果单台计算机无法存储所有数据,仅使用副本将无济于事,必须结合分片策略。
一致性
通过引入副本,我们可以增强系统的健壮性。然而,这也带来了一个关于一致性的问题。假设对于机器 A1,我们设有副本 A2。如何确保 A1 和 A2 的数据相同?
例如,当插入新条目时,我们需要更新两台计算机。但有可能其中之一的写操作失败。随着时间的推移,A1 和 A2 之间可能会积累大量不一致的数据,这是一个严重问题。
这里有几种常见的解决方案:
- 协调器保留副本:将本地副本保留在协调器中。每当更新资源时,协调器都会保留更新版本的副本。因此,如果节点更新失败,协调器可以重新执行该操作。
- 提交日志(Commit Log):如果您使用过 Git,应该非常熟悉提交日志的概念。基本上,每个节点计算机都会保留每个操作的提交日志,类似于所有更新的历史记录。当我们要更新机器 A 中的条目时,首先将此请求存储在提交日志中。然后,一个单独的程序将按顺序(在队列中)处理所有提交日志。每当操作失败时,我们可以查找提交日志轻松恢复。
- 读取冲突解决:假设请求的资源位于 A1、A2 和 A3 中,协调器可以向所有三台机器进行询问。如果返回的数据不同,系统可以即时解决冲突(例如采用多数派原则)。
值得注意的是,所有这些方法都不是互斥的。您完全可以根据应用程序的具体需求组合使用多种策略。
读取吞吐量
我还想在这篇文章中简要提及读取吞吐量。通常,键值存储系统应该能够支持大量的读取请求。那么,您将使用哪些方法来提高读取吞吐量?
为了提高读取吞吐量,通常的方法是利用内存。如果数据存储在每个节点计算机的磁盘中,我们可以将热点数据移动到内存中。一个更通用的想法是使用缓存。鉴于《设计缓存系统》一文已对此主题进行了深入分析,因此我在这里不再赘述。
总结
不要将这里的分析视为标准答案。相反,这些常见的解决方案旨在为您提供启发,帮助您提出不同的想法。
没有适用于每个系统的通用解决方案,您应该始终根据特定场景调整方法。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/jian-zhi-cun-chu-xi-tong-she-ji--di-er-bu-fen.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。