Date: 202102
目录
===== 1. Introduction
===== 2. Background
===== 3. Related work
===== 4. System Architechture
===== 4.1 系统接口
===== 4.2 partition 算法
===== 4.3 Replication
===== 4.4 Data Versioning
===== 4.5 Execution of get() and put() operations
===== 4.6 Handling Failures: Hinted Handoff
===== 4.7 Handling permanent failures: Replica synchronization
===== 4.8 Membership and Failure Detection
===== 4.9 Adding/Removing Storage Node
===== 5. Implementation
===== 6. Experiences & Lessons Learned
===== 6.1 Balancing Performance and Durability
===== 6.2 Ensuring Uniform Load distribution
===== 6.3 Divergent Version: When and How Many
===== 6.4 Client-driven or Server-driven Coordination
===== 6.5 Balancing background vs. foreground tasks
文章主要在讲 如何实现一个A(vailability)&P(artition tolerance) 的系统,同时提供NWR的方式来支持用户在 performance / durability / availability 进行 tradeoff。
在实现上述功能之前,首先对其系统边界进行了明确的描述:RDBMS 提供很多功能,但是大多数系统不需要(比如无须事务的系统)。所以Dynamo提供简单的kv模型,且value足够小,也弱化了ACID的特性支持
那么如何实现一个更关系Availablity的系统:
- 为了保证(基础的) High Availability,所以需要有冗余副本的支持。而同时选择异步复制的方式(最终一致性),以提升可用性。
- 引入多节点写的机制来规避单节点异常带来的 availability 丢失。所以引入了 preference list 以及 coordinator。
- 多节点写入容易引发版本冲突,所以引入 vector clock 以及 syntactic reconciliation&semantic reconciliation 来进行冲突解决。具体的冲突结果过程选择在 read 过程中 以尽可能保证 write可用(提供context信息,read获取冲突,write修正冲突)
- 冲突的产生量 主要与 选择coordinator的方式 有关,同时还与W、并发度相关
- 在节点发生异常的时候,引入 hinted replica 的方式来处理。同时描述了 hinted replica 也发生异常以及 failure detection 机制。引入Merkle tree来高效的进行数据比对,引入 Gossip 进行拓扑变更传输
在提供了Availability系统之上,仍然需要考虑 scalability / uniform load distribution / performance, 以及它们之间的tradeoff
- consistent hash:数据更均衡(便于线性扩展),同时探讨了对 data partitioning 和 data placement 解耦,已解决 节点增删带来的大量数据扫描、Merkle tree 重新计算、归档难的问题
- 提供内存buffer的功能,可以对performance & durability 进行取舍
- admission controller:基于反馈的机制来控制后台任务可获取的执行时间片
【Lamport Clock】[12] Lamport,L.Time,clocks,andtheorderingofeventsina distributed system. ACM Communications, 21(7), pp. 558- 565, 1978.
【gossip?】[8] Gupta, I., Chandra, T. D., and Goldszmidt, G. S. 2001. On scalable and efficient distributed failure detectors. In Proceedings of the Twentieth Annual ACM Symposium on Principles of Distributed Computing (Newport, Rhode Island, United States). PODC '01. ACM Press, New York, NY , 170-179.
【Merkle Tree】[13] Merkle, R. A digital signature based on a conventional encryption function. Proceedings of CRYPTO, pages 369– 378. Springer-Verlag, 1988.
【Bigtable相关? & CAP】[2] Bernstein, P.A., and Goodman, N. An algorithm for concurrency control and recovery in replicated distributed databases. ACM Trans. on Database Systems, 9(4):596-615, December 1984
【Consistency Hash】[10] Karger, D., Lehman, E., Leighton, T., Panigrahy, R., Levine, M., and Lewin, D. 1997. Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web. In Proceedings of the Twenty-Ninth Annual ACM Symposium on theory of Computing (El Paso, Texas, United States, May 04 - 06, 1997). STOC '97. ACM Press, New York, NY, 654-663.
===== 1. Introduction
在Amazon平台下,系统有严格的操作要求:performance / reliability / efficiency / highly scalable 。这其中 Reliability 是最重要的,因为轻微的抖动都会引起用户的信任问题。另外为了支持请求不断增长,highly scalable也十分重要。
应用系统要求存储节点需要一直available,并且处理异常是标准操作。 // 所以,Amazon更加期望拥有一个AP的系统
Dynamo is used to manage the state of services that have very high reliability requirements and need tight control over the tradeoffs between availability, consistency, cost-effectiveness and performance.
Dynamo用来管理对可靠性要求很高的服务状态,并且需要严格控制可用性(availability)、一致性(consistency)、成本效益(cost-effectiveness)和性能(performance)之间的权衡。 // Amazon上有各种系统,需要存储节点足够灵活,来让应用自行权衡上述能力。
Dynamo提供了简单基于 primary-key 的接口。
Dynamo uses a synthesis of well known techniques to achieve scalability and availability: Data is partitioned and replicated using consistent hashing [10], and consistency is facilitated by object versioning [12]. The consistency among replicas during updates is maintained by a quorum-like technique and a decentralized replica synchronization protocol. Dynamo employs a gossip based distributed failure detection and membership protocol.
Dyname使用了很多知名技术来实现扩展性&可用性:使用 consistent hashing[10] 对数据进行分区和复制,而object versioning[12] 则促进了一致性。更新过程中副本之间的一致性状态依靠 quorum-like 技术以及 分散的副本同步 协议来维护。使用基于 gossip 的分布式故障检测&成员协议。
===== 2. Background
RDBMS提供了多余的功能是多数服务不需要的。而这需要更昂贵的硬件及更熟练的人员,同时这些系统更多选择一致性,而不是可用性。
// 那么Dynamo目标即是设计一个更关注可用性,而同时有明确应用场景目标的系统
首先来看Dynamo系统设计的目标(系统假设与要求):
- 查询模型:基于 主键 的数据请求及数据存储;无关系模型;object小于1MB
- ACID 特性:弱一致性 (the "C" in ACID) ; 无隔离性保证(只保证单key更新)
- Efficiency(SLA):保证99.9分位的延迟要求
- 其他:没有安全性要求,具备很强的扩展能力。
设计思考:
This process of conflict resolution introduces two problems: when to resolve them and who resolves them.
Dynamo是保证最终一致性的存储服务,采用异步复制的方式来提升可用性。异步复制的方式也引入了冲突(必须被发现和解决掉)。那么这里有2个问题:什么时候解决冲突 & 谁来解决冲突。
// 传统数据库采用同步复制的模式是为了选择一致性而不是可用性(CAP[2, 11])。
// 这里如果写入的节点网络异常了,而W设置的2,那么最终失败?如果W是1,而该节点接收后异常了,那么写入丢失?
什么时候解决冲突:read过程 or write过程?
Dynamo选择在 read 过程解决冲突。
Dynamo targets the design space of an "always writeable" data store (i.e., a data store that is highly available for writes). This requirement forces us to push the complexity of conflict resolution to the reads in order to ensure that writes are never rejected.
多数传统数据库在 write 过程中解决,而让 read 足够简单。这些系统在 写入不能满足要求(比如不能达到大多数节点)的情况下会被拒绝掉。而Dynamo目标是一个 "always writeable " 的系统,写入被拒绝对Dynamo来说是不可接受的。
谁来解决冲突:database or application?
database解决冲突选择上比较受限,只能使用简单的 "last write wins"[22]。
application可以更好的解决这些问题,因为他们更了解数据模型及业务场景。但是开发者仍然不希望编写冲突解决逻辑,而是使用database的方式来解决。
===== 3. Related work
Peer to Peer System:随着P2P系统的演化,不断的增加每个系统存储路由信息量,以减少单次请求代价(RT)
- 第一代p2p系统大多使用 unstructured P2P networks,每一次查询请求都会有大量的网络请求(flooded through the network)
- 下一代的p2p系统则采用 structured P2P networks,系统引入全局一致性协议(Pastry [16] / Chord [20]),来保证请求在有限跳内即可查询到目标数据。为了减少路由跳转引入的延迟,一些系统[14]让每个节点存储更多的足够的信息来让跳转次数恒定。
Distributed File Systems and Databases:Dynamo的目标场景明确,通过与其他系统对比,更好的确定其实现机制
- 一致性&冲突解决:Dynamo和Bayou[21]、Coda[19]、Ficus[15]比较相似,允许在网络分区的时候进行读写,同时提供不同的冲突解决机制
- 存储方式:分布式块存储系统(比如FAB[18])将大object分割成小块进行存储,Dynamo更适合这种场景,因为其目标是存储小object,并且更便于针对每个应用进行配置
- Antiquity[23]是一个分布式存储系统,使用secure log保证数据完整性、使用复制来进行持久化、使用拜占庭协议保证一致性。Dynamo构建在授信环境下,无需考虑数据完整性以及安全性
- BigTable[2] 用于管理结构化的数据,允许应用使用不同的attr进行访问。而Dynamo旨在处理基于 主键 的数据类型。
Dynamo(由于有明确的系统目标/要求)与上述系统是存在不同的。Dynamo需要 "always writeable",并且是部署在授信环境下的,不需要支持复杂的关系模型,同时对响应时间十分敏感。由于其有延时要求,所以需要避免请求多次路由(最好是0跳)
===== 4. System Architechture
生产环境下的存储系统需要涉及很多功能:持久化存储、扩展性、负载均衡、成员资格与故障探测、故障恢复、复制同步、过载处理、状态转换、并发控制、任务调度、请求组织、请求路由、系统监控、报警、配置管理等。
本文重点关注:分片(partition)、复制(replication)、版本管理(version)、成员资格(membership)、故障处理(failure handling)、扩展性(scale)
===== 4.1 系统接口
系统对外提供2个接口:get() & put()
- get(key) :返回 key 对应的object 或者 object列表+对应的冲突版本+context
- put(key, context, object) :数据写入。context包含元信息(比如version) // 解决冲突
Dynamo对key进行MD5,拿到一个128-bit的编码,并基于此判断目标存储节点。
===== 4.2 partition 算法
Dynamo使用一致性哈希(consistent hashing)来进行数据分片,但是基础的 consistent hashing 算法存在一些问题:
- 每个节点的位置都是随机的,导致数据及负载可能存在不均
- 基础算法未考虑存储节点的异质性
Dynamo uses a variant of consistent hashing (similar to the one used in [10, 20]): instead of mapping a node to a single point in the circle, each node gets assigned to multiple points in the ring.
Dynamo使用了变体的一致性哈希算法,每个存储节点在hash环上有多个节点。
使用这个变体算法有这样的一些优势:
- 当节点不可服务时,压力可以被剩余可用节点均衡的接管。当节点重新可用或者有节点加入时,可以平均的接管其他节点的压力
- 每个存储节点持有的虚拟节点数量可以根据其节点性能而进行配置
对于数据路由的元信息,基础的一致性hash算法只存储 节点的hash值 , 变体的一致性hash算法,需要存储节点的每个虚拟节点的hash值,而更进一步的可以存储slot信息。
随着存储 物理节点hash值 --> 虚拟节点hash值 --> slot信息,每个节点存储的信息量是逐步增大的,但是相对的对路由的把控能力也在逐步加强:
- 存储物理节点hash值只能做到初步的数据打散,而当节点增删时会导致出现系统波动,同时对存储节点性能不同的情况也是没法处理的
- 存储虚拟节点hash值,可以解决上述问题,但是没法应对系统出现热key的场景
- 而slot信息等于是手动执行了虚拟节点的各个位置,可以通过 数据迁移 的方式很好的处理热key
===== 4.3 Replication
为了实现 high availability 和 durability,Dynamo将数据复制到其他节点上。副本数N是可配置的。
coordinator节点:每个key会被分配到一个coordinator节点,该节点除了将key的数据存储到本地外,还会将其复制到其他的数据节点上去(顺时针方向随后的N-1个节点)
preference list:存储特定key的节点列表,成为这个key 的preference list(在4.8中进行具体解释)。
- 对于任意的key,系统中的每个节点其preference list
- 当发生节点异常时,preference list会包含多于N个节点
- perference list中的N个节点表示的是N个物理节点
===== 4.4 Data Versioning
Dynamo提供最终一致性(Eventual consistency),采用异步复制的方式。Dynamo允许同一个Object在同一时间存在不同的版本。
- syntactic reconciliation:一般情况下,新版本包含以前的版本,所以系统可以直接进行 句法和解
- semantic reconciliation:当版本发生分歧时,客户端需要将这些不同版本的数据合并为一个,称为 语义和解
Dynamo 使用 vector clocks[12] 来捕获相同object的不同版本之间的因果关系。
vector clocks是多个(node, counter)对的组合。每个Object的每个版本都会分配一个vector clock。通过比对vector clocks,可以判断2个版本位于不同的分支还是具有因果关系:如果前一个version的counter在所有node上都比后一个version的counter小,那么前者是后者的祖先。
当client要更新object的时候,它必须指定其更新的version(其之前读取到的version)。而在读取的的时候,如果数据无法进行syntactic reconciliation,那么Dynamo会返回所有叶子节点的数据及其version。
- clientA写入key - D1,该请求被Sx处理,那么D1的version为 [(Sx, 1)]
- 随后clientA将key 更新为D2,请求仍然被Sx处理,D2的version 为 [(Sx, 2)]
- clientA继续更新key 为D3,此次被Sy处理,那么D3的version则为 [(Sx, 2),(Sy, 1)]
- clientB 读取D2并更新其为D4,请求被Sz处理,D4的version为 [(Sx, 2),(Sz, 1)]
- 此时如果 clientC 读取D3&D4,由于他们没有因果关系,所以clientC读取的context中,version信息为 [(Sx, 2), (Sy, 1), (Sz, 1)]
- 随后 clientC 基于该context进行更新,将key更新为D5,该请求被Sx处理,那么D5的version 为 [(Sx, 3), (Sy, 1), (Sz, 1)]
这里有一个问题就是vector clock可能会无限增长,正常情况下wite操作都会被topN的preference list来处理,而发生网络分区等情况才有可能导致vector clock增长,这种情况下需要限制vector clock的大小。Dynamo使用一个clock truncation机制,Dynamo记录每个节点更新这条数据的ts,如果vector clock达到阈值,那么最老的ts对应的节点version信息会被清理。这样会导致后续无法判断继承关系,但是在生产环境中暂未发生
===== 4.5 Execution of get() and put() operations
// 在无故障的情况下,操作是如何被执行的
There are two strategies that a client can use to select a node: (1) route its request through a generic load balancer that will select a node based on load information, or (2) use a partition-aware client library that routes requests directly to the appropriate coordinator nodes.
客户端有2种策略来选择目标节点:
- 通过一个load balancer路由其请求,该load balancer会根据负载信息选择一个节点
- 使用支持分区的客户端库,将请求直接路由到适当的coordinator节点
使用前一种方式的客户端,无需在应用程序中链接任何Dynamo相关代码。而第二种方式则有更低的延迟
处理读写请求的节点被称为coordinator节点。通常情况下,coordinator节点是preference list的前N个节点中的第一个。如果请求通过 load balancer 接收,那么请求可能会被路由到任意节点上。如果该接收节点不是preference list的前N个节点,那么它会将请求转发给preference list前N个节点的第一个。
只有preference list的前N个节点才可以处理该key的请求,即可以成为coordinator节点。
读写操作涉及preference list中前N个健康节点,跳过异常节点。
Quorum:为了保证副本之间的一致性,Dynamo使用了类quorum系统的协议。该协议有2个关键配置:R&W。
- R是必须参与成功读取操作的最小节点数
- W是必须参与成功写入操作的最小节点数
将 R+W 设置为 大于N的值,就是一个类似quorum系统。而这样读(写)操作的等待时间由R(W)副本中最慢的副本决定。所以通常设置 R+W < N 以获得更低的延迟。
// 对于写操作,coordinator为object生成新的version(vector clock)并将其在本地写入,随后将数据复制到preference list的前N个节点上,等待W-1个节点完成数据同步后,写入判定成功。对于读操作, coordinator节点从preference list的前N个节点上读取数据,从R个节点上拿到结果后返回给client(有分歧的数据会全部返回给客户端)
===== 4.6 Handling Failures: Hinted Handoff
当有节点异常的时候,传统的quorum协议会导致不可用。所以Dynamo使用 sloppy quorum(Hinted Handoff) :所有的读写操作在preference list的前N个健康节点上执行:( 比如如图2中)
- 如果nodeA不可用,那么写操作会将请求发送给D节点
- hinted replica:这个副本数据发送到D上会携带元信息知晓该副本数据本应属于A
- D收到该副本数据后将其存储在本地,并定期探测A节点状态
- 当A节点状态恢复后,D将数据发送给A并删除本地数据
// 如果应用期望更高的可用性,会将W设置为1。但是在实践中,大多数系统会设置较高的W以获得 durability。更详细讨论在section 6
===== 4.7 Handling permanent failures: Replica synchronization
Hinted Handoff 可以在节点短暂异常的场景下运行的很好。但是如果 hinted replica在写回原始节点前变的不可用就无法很好的工作。
为了解决这个问题,Dynamo 使用 Merkle trees[13] 来发现replica 中不一致的数据。每个物理节点为其维护的每个key范围(虚拟节点)维护一个单独的merkle tree。
Merkle tree是一个hash tree,每个叶子节点存储的hash值代表key的object,父节点存储的hash值代表其所有子节点。这样可以通过比较root节点的hash值是否相同而判断key区间数据是否相同,不同则进行逐级对比。
// 这种方式在key范围发生变化时,比如节点增删的场景,会导致tree被重新计算。解决方案详见 section 6.2
===== 4.8 Membership and Failure Detection
Ring Membership
节点异常经常是暂时的,所以支持显式的初始化节点or删除节点是比较合适的。administrator链接到Dyanmo上并声明membership的变化。
处理membership变化的请求的节点,将请求写入到其持久化存储。membership变化形成历史(因为节点可以多次被添加删除)。
通过基于gossip的协议将 membership变化传播给其他节点,最终达到一致。(每个节点每秒随机选择一个节点进行信息交互)。
当一个新的节点启动后,选择其持有的virtual node数量,将其映射到哈希环上,并将映射信息持久化到磁盘。随后映射信息随着上述membership变化信息交互一起进行,最终系统达到一致。
External Discovery
上述信息交互机制可能会引发短暂的逻辑分区。比如管理员先后加入A&B两个节点,AB交互信息后认为自己已经在哈希环中,而其他节点并未感知这两个节点。为了解决这个问题,部分节点扮演者seed的角色。
seed节点被外部指定,并且知道所有的节点信息。所有的节点都和seed交互membership信息,所以逻辑分区就很难出现。
Failure Detection
Failure Detection机制用于避免尝试与异常节点交互。这里使用了一个比较简单的机制:当B未响应A节点的信息时,A节点就认为B节点异常(即使B节点可以正常响应C节点的请求)。
在正常用户流量的情况下,A可以很快发现B节点异常,并将本应该发给B节点的请求发给其他可替换的节点上。并且A定期探测B节点,以判断其是否恢复正常。
当没有用户流量的时候,AB节点也不需要知道彼此是否异常。
Failure Detection使用基于gossip的协议来完成,具体机制可以参考[8]
===== 4.9 Adding/Removing Storage Node
当有节点增加到系统中时,对于每一个需要分配给新节点(虚拟节点)的key区间,系统中已经存在多个节点持有这些key区间。无须持有这些key区间的节点将他们的数据传输给新节点。
对于节点从系统中删除的场景,则与添加节点的过程完全相反。
===== 5. Implementation
Dynamo的存储节点有三个重要的组件:request coordinator, membership and failure detection, local persistence engine. // 均基于Java实现
local persistence engine:Dynamo采用插件的方式支持不同的存储引擎(以应对不同的用户场景)。目前可以使用的有Berkeley Database (BDB) Transactional Data Store , BDB Java Edition, MySQL, and an in-memory buffer with persistent backing store.
Dynamo生产场景主要使用的是Berkeley Database (BDB) Transactional Data Store
request coordinator 组件构建在事件驱动的消息传递基础上,其中消息处理管道被分为多个阶段(与SEDA架构[24]类似)。
接收客户端请求的节点为每个请求创建一个state machine,这个状态机包含所有逻辑:识别key所属节点、发送请求、等待响应、重试、处理响应、打包结果发送给客户端。 // 每个状态机仅用于处理一个请求
- read repair:在读请求返回给调用方以后,state machine仍然会等待一小段时间,以接收任何未完成的响应。如果收到了过时的版本,coordinator会使用最新的版本来更新那些节点。这个过程叫做 read repair,因为他可以在读取过程中修复错过的最近更新版本
- 写请求的coordinator是preference list中 topN 的节点,并且被期望是topN中的第一个节点。但是这会导致负载不均,因为不同object造成的load是不均衡的,所以topN中的任意节点都可以是coordinator。因为写请求往往在读请求之后,所以写请求选择前面响应读请求最快的节点(通过context)作为coordinator可能会更好。这种方式下,可以增大 read-your-writes 的机会,同时也会增大99.9分位性能
===== 6. Experiences & Lessons Learned
Dynamo最大的优势就是 应用可以通过调整NRW来达到他们预期的performance, availability, durability之间的取舍。
W&R 的配置影响availablity, durability, consistency。比如W=1的情况下,只要系统中有一个存活的节点就不会拒绝写入。配置较低的W&R 会增加不一致的风险
// 传统观点认为 durability 和 availability 是密不可分的。但这不完全正确,我们可以通过增加W来提高 durability ,但是这就增加了拒绝请求的可能性(需要更多存活的节点)
// 常用的NRW的配置是3,2,2
===== 6.1 Balancing Performance and Durability
Dynamo上一个典型的SLA要求是 99.9% 的读写操作要在 300ms 内完成。
完成这样的SLA有很多挑战。比如使用商业硬件,其IO性能远低于高端服务器;另外多个存储节点,使得操作响应时间受限于RW中响应最慢的节点。
图4是 Dynamo一段30天的响应时间数据。可以看到:1. 有明显的昼夜交替;2. 写延迟明显高于读延迟,因为写操作往往需要访问磁盘;3. 99.9分位延迟大约在200ms左右,高于avgRT。
对于对性能要求更高的应用,Dynamo提供了一个优化方案(Durability 换取 Performance):每个存储节点内存中维护一段buffer,每次写操作存储在buffer中,定期有 write thread 触发落盘。读操作优先从内存中进行读取。
这样的一个优化,在即使只有一小段能容纳1000个object的场景下,极端情况下能给系统带来5倍的性能提升。同时也让系统响应时间变得更平滑。
===== 6.2 Ensuring Uniform Load distribution
这里我们定义 imbalance ratio > 15%为不均衡,从图中可以看到,随着系统负载的增加,imbalance ratio是降低的。
这里我们讨论分区方案是如何演进的,以及他们是如何影响负载的。
Strategy 1: T random tokens per node and partition by token value // 这是初始的策略(即是4.2中描述的策略)。
- 当有新节点接入的时候,它需要从其他节点来获取数据,这些节点需要扫描local storage来获取数据。scan操作极度消耗资源,所以他们需要后台执行这些操作。这样就会导致节点启动任务处于低优先级,这样的行为较难接受
- 当有节点加入或者离开时,许多节点的key range都会发生变化,导致 Merkle tree 需要重新计算,这在生产环境下是一个非常见操作
- 很难拿到一个所有key space 的快照,因为key范围的随机性,而这导致归档操作变得复杂
这个策略的根本问题是 data partitioning 和 data placement 耦合在一起。改进的方法就是解耦合,因此有了Strategy 2 & Strategy 3
Strategy 2: T random tokens per node and equal sized partitions
Hash环被均匀的分成了Q份(节点数为S,每个节点token数为T),保证Q >> N && Q >> S*T。这样Token只负责构建映射到hash环上有序节点列表(data placement),而不负责data partition。
A partition is placed on the first N unique nodes that are encountered while walking the consistent hashing ring clockwise from the end of the partition.
Partition的数据放置在从partition末尾开始顺时针的前N个节点上。即从上图来看,ABC负责A前面两段阴影中数据的存放。
The primary advantages of this strategy are: (i) decoupling of partitioning and partition placement, and (ii) enabling the possibility of changing the placement scheme at runtime.
这个策略主要的好处是:1. 解耦了partitioning 和 data placement;2. 使运行时调整数据存储位置称为可能
节点增删时,都可以以分区为单位进行transfer,merkle tree的计算以及archival都可以以分区为单位完成
Strategy 3: Q/S tokens per node, equal-sized partitions
和Strategy 2比较相似,hash环被分成了Q等份。另外每个节点持有 Q/S 个token。当有节点离开系统时,他的token随机被其他节点持有。当有节点加入系统时,他从其他节点获取部分token
- 【advantage】更快的启动&恢复:partition 区间是固定的,所以他们可以存储在单独的文件中,这样简化了文件传输的流程
- 【advantage】更简单的归档:同样因为独立的文件,所以归档也变得更简单
- 【disadvantage】更改节点需要进行协调(coordination)来保留分配所需的属性
===== 6.3 Divergent Version: When and How Many
有2种场景可能会造成 divergent version:
- 系统有异常:节点异常、网络异常
- 大量对同一个数据项进行更新,多节点并发的完成更新操作
从可用性&效率方向考虑,我们期望 divergent version尽可能少。经验上来看, 99.94%请求仅返回一个版本,0.00057%返回2个,0.00047%返回3个,0.00009%返回4个。
===== 6.4 Client-driven or Server-driven Coordination
Section 5中提到,任意请求Dynamo都需要有coordinator来维系状态机。任何节点都可以作为读请求的coordinator,而写请求的coordinator必须是当前preference list中的一个节点,这是因为coordinator需要为写请求生成一个version信息。
一个可以替代的方案就是状态机的维护由client来完成。client定期从任意节点dump其存储的路由信息。
- 读请求可以直接由 client 作为 coordinator(少一跳)
- 写请求 则可以发送给key的preference list 或者 在本地 coordinator(使用基于时间戳作为版本信息)
这样一个比较明显的优势就是 不再需要 load balancer 来进行负载均衡。
当然也引入问题,这种方式的效率又获取到的路由信息 fresh 程度来决定。(这里选择pull 的方式来拉取,pull的方式有很好的扩展性。但有可能导致路由信息过老,当client 发现路由信息过老的时候,就立即再次拉取数据)
===== 6.5 Balancing background vs. foreground tasks
每个节点都会维护一些 background 任务(比如复制、handoff等)。这个任务可能会造成资源争抢,而影响正常的foreground请求。
所以这里引入了一个 admission control 机制,每个background 都使用这个 controller 来获取 执行时间片,而同时通过监控正常用户请求来引入一个 feedback 机制,实时调整 background 任务可获取的时间片。
admission controller 监控的信息包括: latencies for disk operations / failed database accesses due to lock-contention and transaction timeouts / request queue wait times