310 likes | 591 Views
Dynamo: Amazon’s Highly Available Key-value Store 论文报告. 林丹. 报告内容. SYSTEM ARCHITECTURE System Interface Partitioning Replication Data versioning Handling Failures: Hinted Handoff Handling permanent failures: Replica synchronization Membership and Failure Detection. System Interface.
E N D
Dynamo: Amazon’s Highly Available Key-value Store论文报告 林丹
报告内容 SYSTEM ARCHITECTURE • System Interface • Partitioning • Replication • Data versioning • Handling Failures: Hinted Handoff • Handling permanent failures: Replica synchronization • Membership and Failure Detection
System Interface 将对象与key相关联 Get() 定位与key关联的对象副本,并返回一个对象或一个包含冲突版本和对应上下文的对象列表 Put() 基于对象的key决定将对象的副本放在哪,并将副本写入到磁盘。
Partitioning 增量可扩展性,将数据动态划分到系统中的节点上去 通过hash算法将对象映射到节点中 单调性 单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。即能够尽可能小的改变已存在 key 映射关系。 平衡性 平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。
Partitioning 基本场景 N 个服务器,将对象映射到这N个服务器上 hash(key)%N 一切都运行正常,再考虑如下的两种情况; • 1 一个 cache 服务器 m down 掉了(在实际应用中必须要考虑这种情况),这样所有映射到 cache m 的对象都会失效,怎么办,需要把 cache m 从 cache 中移除,这时候 cache 是 N-1 台,映射公式变成了 hash(key)%(N-1) ; • 2 由于访问加重,需要添加 cache ,这时候 cache 是 N+1 台,映射公式变成了 hash(key)%(N+1) ; 意味着突然之间几乎所有的映射都失效了,服务器出现大量的读写访问。 3 硬件能力越来越强,想让后面添加的节点多做点活。 有什么方法可以改变这个状况呢,这就是 consistent hashing...
Partitioning 构造hash空间 用 hash 算法将 value 映射到一个 32位的 key 值,也即是 0~2^32-1 次方的数值空间; 我们可以将这个空间想象成一个首( 0 )尾( 2^32-1 )相接的圆环 把对象映射到hash 空间 接下来考虑 4 个对象 object1~object4 ,通过 hash 函数计算出的 hash 值 key 在环上的分布如图 2 所示。 hash(object1) = key1; … … hash(object4) = key4;
Partitioning 把服务器映射到hash 空间 假设当前有 A,B 和 C 共 3 台 cache ,那么其映射结果将如图 3 所示,他们在 hash 空间中,以对应的 hash值排列。 hash(cache A) = key A; … … hash(cache C) = key C; 服务器的 hash 计算,一般的方法可以使用 cache 机器的 IP 地址或者机器名作为hash 输入。 把对象映射到cache 在这个环形空间中,沿着顺时针方向从对象的 key 值出发,直到遇见一个 cache ,那么就将该对象存储在这个 cache 上。
Partitioning 考察cache 的变动 移除cache 添加cache
Partitioning 为了解决平衡性-------虚拟节点 使用虚拟节点 不使用虚拟节点 点 cache A1 和 cache A2 的 hash 值: Hash(“202.168.14.241#1”); // cache A1 Hash(“202.168.14.241#2”); // cache A2 A 的 hash 值: Hash(“202.168.14.241”);
Replication 为了实现高可用性和耐用性,Dynamo将数据复制到多台主机上。 假设每个数据项被复制到N台主机上, Coordinator 对管理范围内的对象进行读写 复制这些key到环上顺时针方向的N-1后继节点。 这样的结果是,系统中每个节点负责环上的从其自己到第N个前继节点间的一段区域。 Preference list: the list of the nodes that is responsible for storing a particular key. 对于key K : B, C,D
Replication • 出于对节点故障的考虑 preference list可以包含超过N个节点。其他的节点可以是这个环上的,也可以时其他数据中心的,可以根据需要配置。 • 出于虚拟节点的考虑 由于使用了虚拟节点,对于key的后面的前N个后继位置可能少于N个物理节点。为了解决这个问题,首先列表的将跳过这个虚拟节点位置,也就是保证列表中只包含不同的物理节点。
数据版本 强一致性:假如A先写入了一个值到存储系统,存储系统保证后续A,B,C的读取操作都将返回最新值。如果不能保证,写操作就会失败。 Amazon:在任何时刻用户都可以进行写操作,根据CAP原理,在一致性上做出让步。 最终一致性:过程松,结果紧,最终结果必须保持一致性
数据版本 最终一致性允许更新操作可以异步地传播到所有副本。 问题: 更新的数据还没来得及传播到所有的副本节点,就返回数据给调用者。导致get()操作返回一个不是最新的对象。 如果没有失败,那么更新操作的传播时间将有一个上限。但是,在某些故障情况下,更新操作可能在一个较长时间内无法到达所有的副本。 在Amazon的平台,有一种类型的应用可以容忍这种不一致。。。
数据版本 购物车应用程序要求一个“添加到购物车“动作从来没有被忘记或拒绝. Most recent cart unavailable Make change to old version cart 当客户希望增加一个项目到购物车(或从购物车删除)但最新的版本不可用时,该项目将被添加到旧版本(或从旧版本中删除)。在Dynamo中“添加到购物车“和”从购物车删除项目“这两个操作被转成put请求。 不可用的新版本和变化后的旧版本中的信息都需要保留,因此不同版本将在后面进行协调(reconciled)。
数据版本 为了提供这种保证(保留所有版本的信息),Dynamo将每次数据修改的结果当作一个新的且不可改变的数据版本。 结果:造成了一份数据会存在多个版本,分布在不同的节点上。这种情况类似于版本管理中的多份副本同时有多人在修改。 多数情况下,系统会自动合并这些版本,一旦合并尝试失败,那么冲突的解决就交给应用层来解决。 这时系统表现出来的现象就是,一个GET(KEY)操作,返回的不是单一的数据,而是一个多版本的数据列表,由用户决定如何合并。这其中的关键技术就是Vector Clock。 一个Vector Clock可以理解为一个<节点编号,计数器>对的列表。每一个版本的数据都会带上一个Vector Clock。通过对比两份不同数据的Vector Clock就能发现他们的关系。
数据版本 假设该商城有A、B、C三个node,则我们的N是3。 W=1, 根据W+R>N有R=3。那么就有如下场景: Write by A, iphone 4000 A: 4000[A:1] 在数据被复制到B,C之前 Write by A, iphone 4500 4500[A:2],它覆盖了之前的[A:1],并复制到B,C 4500[A:2], A: 4500[A:2] Write by B, iphone 5000 在B上这个价格被复制到A,C之前,written by C,3000 C 3000[A:2,C:1], B 5000[A:2,B:1] C: 3000[A:2,C:1] A: 4500[A:2] 5000[A:2,B:1]。 最坏的情况,这时有人询问iphone的价格。R =3,读到所有的数据,
数据版本 C: 3000[A:2,C:1] A: 4500[A:2] B :5000[A:2,B:1]。 最坏的情况,这时有人询问iphone的价格。R =3,读到所有的节点,系统应该返回哪个? 显而易见,A上的版本最低,应被舍弃,那么B和C呢? 客户端拿到3000[A:2,C:1]和5000[A:2,B:1]确实有点手足无措,但我们可以让它有个判断依据——比如时间戳——现在客户端看到C上的数据是最新的,那么结论就是3000.
数据版本 W=2,R=2 Write by A, iphone 4000 A : 4000[A:1] B: 4000[A:1] 在数据被复制到C之前 Write by A, iphone 4500 4500[A:2],它覆盖了之前的[A:1],并复制到C 4500[A:2], A : 4500[A:2] B: 4500[A:2] Write by B, iphone 5000 复制到A之前,written by C,3000 B :5000[A:2,B:1] C: 5000[A:2,B:1] C: 3000[A:2,B:1,C:1] A: 3000[A:2,B:1,C:1] C: 3000[A:2,B:1,C:1] A: 3000[A:2,B:1,C:1] B: 5000[A:2,B:1]
数据版本 • C: 3000[A:2,B:1,C:1] A: 3000[A:2,B:1,C:1] B: 5000[A:2,B:1] • R=2,由于B的版本低,所以,无论读哪两个,都会返回3000,无需客户端做出协调 由此我们也可以看出提高W可以降低冲突,提高一致性。但代价也是显然的:写两份显然比写一份要慢,并且同时能写成功的概率也变低了——也就是Availability降低。这跟CAP理论也是吻合的。
数据版本 • 关于向量时钟一个可能的问题是,如果许多服务器协调对一个对象的写,向量时钟的大小可能会增长。实际上,这是不太可能的,因为写入通常是由Coordinator(也就是Preference list 的第一个节点)执行的。 • 在网络分裂或多个服务器故障时,写请求可能会被不是coordinator的节点执行,因此会导致矢量时钟的大小增长。在这种情况下,值得限制向量时钟的大小。为此,Dynamo采用了以下时钟截断方案:伴随着每个(节点,计数器)对,Dynamo存储一个时间戳表示最后一次更新的时间。当向量时钟中(节点,计数器)对的数目达到一个阈值(如10),最早的一对将从时钟中删除。显然,这个截断方案会导至在协调时效率低下,因为后代关系不能准确得到。不过,这个问题还没有出现在生产环境,因此这个问题没有得到彻底研究。
Handling Failures: Hinted Handoff 给定N=3。在这个例子中,如果写操作过程中节点A暂时Down或无法连接,然后通常本来在A上的一个副本现在将发送到节点D。这样做是为了保持期待的可用性和耐用性。发送到D的副本在其原数据中将有一个暗示,表明哪个节点才是在副本预期的接收者(在这种情况下A)。 D节点将数据保存在一个单独的本地存储中,他们被定期扫描。在检测到了A已经复苏,D会尝试发送副本到A。一旦传送成功,D可将数据从本地存储中删除而不会降低系统中的副本总数。
Handling Failures: Hinted Handoff “马虎仲裁”(“sloppy quorum”) :所有的读,写操作是由首选列表上的前N个健康的节点执行的,它们可能不总是在散列环上遇到的那前N个节点。 优点: 确保读取和写入操作不会因为节点临时或网络故障而失败。如果你的应用程序需用的可用性很高很高,可以将W设置为1, 这个时候,只要这个环上还有一个节点是可用的,就可以进行写操作。 甚至整个数据中心宕掉也不怕 Dynamo可以配置成跨多个数据中心地对每个对象进行复制。 也就是说,一个key的preference list的构造是基于跨多个数据中心的节点的。这些数据中心通过高速网络连接。这种跨多个数据中心的复制方案使我们能够处理整个数据中心故障。
Handling permanent failures: Replica synchronization “Hinted Handoff”的方式在少量的或是短暂的机器故障中表现很好,但是在某些情况下仍然会导致数据丢失。 如上所说,如果节点D发现A重新上线了,会将本应该属于A的副本回传过去,这期间D发生故障就会导致副本丢失。
Handling permanent failures: Replica synchronization Dynamo中用了anti-entropy协议来保持副本同步. anti-entropy: comparing all the replicas of each piece of data that exist (or are supposed to) and updating each replica to the newest version. 为了更快地检测副本之间的不一致性,并且减少传输的数据量,Dynamo采用MerkleTree。 MerkleTree是一个哈希树(Hash Tree),其叶子是各个key的哈希值。树中较高的父节点均为其各自孩子节点的哈希。
Handling permanent failures: Replica synchronization Dynamo中,每个节点为它承载的每个key范围维护一个单独的MerkleTree。 D除了承载c-d上的key范围,由于副本原因,还承载了B-C,A-B上的key范围。这种情况下就维护3个Merkle Tree. 当两个节点的merkel tree进行比较时,只针对共同承载的key范围上的merkle tree进行比较。也就是对副本进行比较。
Handling permanent failures: Replica synchronization • 1从根节点开始, • 2原来的节点将merket tree当前层次的hash列表传递给其他应该有副本的节点(目标节点) • 3目标节点接受到这个列表以后,就把这个列表与本身merkel tree的同一层次的hash列表做比较。如果都相同,说明不用同步。如果不同,找出不同hash 值的子树。并向原节点发出请求,告诉原节点哪些子树是不同的。 • 4 重复2,3步骤,直到找到叶子节点。这样就可以找出哪些key是不同的。 • 5 源节点将不同的keys的值传给目标节点。 优点: 从时间上:MT利用树形结构避免了可能出现的线性时间比较,迅速定位到差异的key值,时间复杂度为O(lgn); 从空间上:在分布式情况下,空间可以理解为相应的网络传输数据量。不必传输所有的节点,只需传输不同的节点。
Membership and Failure Detection Ring Membership: 由于各种各样的原因,暂时性的节点掉线是时有发生的。如果因为一个节点暂时性的掉线,而导致副本迁移等代价高昂的操作显然是不合适的。所以Dynamo提供了一组命令行接口和HTTP接口供管理员手工添加,删除节点。 一旦一个节点的角色发生改变(上线或下线等),它都会将这个改变和发生的时间存储到一个持久的数据库中,这些数据构成了一个节点的状态变迁历史。 通过一个Gossip-Based Protocol将这个消息传递出去。每个节点每间隔一秒随机选择随机的对等节点,两个节点有效地协调他们的状态变迁历史。最后每个节点都会得到整个集群的信息。
Membership and Failure Detection 一个节点对应一个token集,也就是在虚拟节点。 该映射被持久到磁盘上,当一个节点第一次启动时,最初只包含本地节点和Token集。 这种节点和token集的映射,在协调历史通信过程中一同被协调。也就是说划分和布局信息也是基于gossip协议传播的。 此每个存储节点都了解对等节点所处理的标记范围。这使得每个节点可以直接转发一个key的读/写操作到正确的数据集节点。
Membership and Failure Detection 上述机制可能会暂时导致logically partition. 例如,管理员可以将节点A加入到环,然后将节点B加入环。在这种情况下,节点A和B各自都将认为自己是环的一员,但都不会立即了解到其他的节点 (也就是A不知道B的存在,B也不知道A的存在,这叫逻辑分裂)。 解决方法:使用种子节点将他们连接起来。 有些Dynamo节点扮演种子节点的角色。种子的发现(discovered)是通过外部机制来实现的并且所有其他节点都知道(实现中可能直接在配置文件中指定seed node的IP,或者实现一个动态配置服务,seed register)。 因为所有的节点,最终都会和种子节点协调成员关系,逻辑分裂是极不可能的。
Membership and Failure Detection • 节点被永久性地删除 使用gossip协议,当节点退出时,持久化这个节点的状态变迁历史,并进行传播。最后换上所有节点都会知道该节点退出,unvailable。 • 节点暂时失效 失效检测:如果节点B不对节点A的信息进行响应(即使B响应节点C的消息),节点A可能会认为节点B失败。 只有在有客户端请求推动两个节点进行交流时,才进行检测。一般情况下, 节点双方并不真正需要知道对方是否可以访问或可以响应。