第六章 设计键值对存储
键值对数据库是一种非关系型数据库。其中键是唯一的,值是不透明的,可以通过相关的键进行访问。本章的目标是设计一个键值存储,支持基本的插入和查询操作。其具有以下特点:
- 单个键值对大小较小,小于10KB
- 能够存储大数据
- 具有高可用性:在故障期间也可快速响应
- 高扩展性:可扩展至支持大型数据集
- 自动缩放:根据流量自动添加/删除服务器
- 可调一致性
- 低延迟
分布式键值存储
单个服务器可通过构建哈希表将键值对保存在内存中来实现键值对存储,针对内存空间限制的问题,也有着数据压缩和磁盘交换两种方式来节约单机空间。
即便如此,单个服务器也容易达到容量上限,分布式键值存储是大数据必须的基础数据架构。分布式键值存储也被称为分布式哈希表,其与单机的区别是将键值对存储在多台服务器上。
CAP定理:分布式系统不能同时保证一致性、可用性和分区容错性这三个中的两个以上。
一致性(C):所有客户端在同一时间访问到的数据内容都相同。
可用性(A):即便某些节点宕机,请求数据的客户端仍然接收响应。
分区容错(P):节点之间通信中断导致了网络分区,系统仍然可以运行。
由于网络故障不可避免,因此分布式系统必须要容忍网络分区,CA系统不能在实际应用中存在。当出现网络分区时,CP系统会禁止所有的读写操作来避免出现信息不一致。而选择AP系统时,系统仍然接受读操作,即便这样可能会返回过时数据。
构建分布式键值存储
数据分区
面临两个挑战:
- 将数据均匀分配到多个服务器上
- 在增加/减少服务器数量的时候最小化数据迁移量
使用一致性哈希解决该问题的优点:
- 自动缩放:根据当前负载自动添加和删除服务器
- 异构性:一个服务器的虚拟节点数量与该服务器的容量成正比
数据复制
为了实现高可用性和可靠性,必须异步复制到N台服务器上,其中N为可调整参数。顺时针选择环上前N台服务器存储数据副本。
一致性
由于数据在多个节点上存在副本,必须在复制副本之间进行同步。
Quorum共识可以保证读写一致性。N=副本数量。W=写操作成功需要获取到至少W个副本的确认。R=读操作成功需要获取至少R个副本的确认。W、R和N配置是延迟和一致性之间的权衡。 如果W+R>N,则保证强一致性,系统中至少有一个确保数据一致性的节点。
W=1意味着协调器在认定写入操作成功之前必须受到一个确认,并不意味着只写在一个服务器上
数据一致性的程度分为强一致性、弱一致性、最终一致性
- 强一致性:任何读取操作都会返回与最新写入数据项结构对应的值。
- 弱一致性:后续操作可能不会看到最新值。
- 最终一致性:给定足够时间,所有更新都会被传播,所有副本最终一致。
Dynamo和Cassandra采用最终一致性,也是较为推荐的关键值存储的一致性模型。从并发写操作来看,最终一致性允许不一致的值进入系统,并迫使客户端读取这些值以进行协调。
不一致性解决
版本化和向量锁可用于解决数据不一致问题。
版本化意味着将每个数据修改看作数据的一个新的不可变版本。
向量时钟用于解决同一数据项内不同版本冲突的问题。其具体表现为【服务器,版本对】,可用于检查一个版本是否先于、后于或与其他版本冲突。如果版本Y的向量时钟中每个参与者的计数都大于或等于X中对应计数器的计数器,可以确定版本X是版本Y的祖先(即没有冲突),反之存在冲突。
存在一定的缺陷:
- 向量时钟的冲突解决逻辑需要客户端实现
- 向量时钟中的【服务器,版本】对可能会快速增长。
如果为其长度是设置阈值,达标后删除最旧的对可能会导致冲突解决效率低下,甚至无法确定后代关系。然而,Dynamo论文中指出亚马逊还未碰到这个问题可以作为备选方案
处理故障
故障检测的思想是从至少两个独立的信息来源标记一台服务器宕机。
可以通过流言协议来优化原始的点对点多播的故障检测:
- 每个节点都维护一个表,其中记录成员节点和心跳书
- 每个节点周期性增长心跳值
- 每个节点周期性给一个集合的随机节点发送心跳,并借此中转传播到另一个节点集合。
- 一旦节点收到心跳就更新信息
- 某节点注意到x节点心跳计数在特定周期内没有增长,就会向一组随机节点发送验证,一旦确认,节点x会被标记下线并且传播到其他节点。
在故障检测后,严格的多数派方法可能会堵塞读写操作,系统可使用松散多数(Sloppy quorum)技术来确保可用性。
该技术下的系统会忽略离线服务器,选择哈希环上的第一个W健康的服务器和R健康服务器用于读写。当不可用服务器重新上线时,推回更改以实现数据一致性,也被称为隐式移交(Hinted handoff)。
对于永久下架的副本,可以使用反熵增协议来保证个副本同步,即比较所有副本中的每个数据片,从而更新到最新版本。Merkle树就是实现一致性检测和最小数据迁移量的最好工具。
构建树的步骤如下:
- Step1:将整个key空间分解为多个桶用于控制整个树高度
- Step2:使用相同的哈希函数计算桶内每个空间中键值对的哈希值
- Step3:每个桶设置对应的哈希节点,对应哈希值可以是排序后拼接求哈希也可以是树状组织
- Step4:通过计算子项的哈希值向上构建树。
对应比较Merkle树就是比较根哈希,然后比较左子树和右子树,从而找到哪些桶未同步。同步的数据量与副本之间的差异成正比。
系统体系结构图
基于以上技术可以设计对应的架构:
- 客户端与键值存储分离,通过API通信
- 存在协调器代理客户端进行操作
- 使用一致性哈希分布数据,实现去中心化和多节点复制
写路径
- 写请求提交
- 写数据载入存储服务器的内存缓存
- 内存缓存满或者达到阈值时,数据刷新到磁盘的排序字符串表(SSTable)
读路径
- 读请求提交到某个节点,首先检查是否在内存缓存中,如果为是,则直接返回到客户端
- 否则使用布隆过滤器来确定哪些SSTable可能包含该键
- SSTable返回数据集结果
- 结果返回给客户端
2025年7月15日阅读随笔
浙公网安备 33010602011771号