打印
[PIC®/AVR®/dsPIC®产品]

一文看懂分布式存储架构-分布式键值系统

[复制链接]
51817|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 资深从业爱好者 于 2022-11-7 14:07 编辑

分布式键值系统是用于存储关系简单的半结构化数据,半结构化数据均封装成由 键值对组成的对象,其中 key 为唯一标示符;value 为属性值,可以为任何类型,如文字、图片,也可以为空;timestamp 为时间戳,提供对象的多版本支持。分布式键值系统以键值对存储,它的结构不固定,每一元组可以有不一样的字段,可根据需要增加键值对,从而不局限于固定的结构,适用面更大,可扩展性更好。

分布式键值系统支持针对单个 键值对的增、删、查、改操作,可以运行在 PC 服务器集群上,并实现集群按需扩展,从而处理大规模数据,并通过数据备份保障容错性,避免了分割数据带来的复杂性和成本。

总体来说,分布式键值系统从存储数据结构的角度看,分布式键值系统与传统的哈希表比较类似,不同的是,分布式键值系统支持将数据分布到集群中的多个存储节点。分布式键值系统可以配置数据的备份数目,可以将一份数据的所有副本存储到不同的节点上,当有节点发生异常无法正常提供服务时,其余的节点会继续提供服务。

1、 Amazon Dynamo
Dynamo 以很简单的键值方式存储数据,不支持复杂的查询。Dynamo 中存储的是数据值的原始形式,不解析数据的具体内容。Dynamo 主要用于 Amazon 的购物车及 S3 云存储服务。在实现过程中解决了如下问题:


Dynamo 采用一致性哈希将数据分布到多个存储节点中,概括来说:给系统中的每个节点分配一个随机 token ,这些 token 构成一个哈希环。执行数据存放操作时,先计算主键的哈希值,然后存放到顺时针方向的第一个大于或者等于该哈希值的 token 所在的节点。一致性哈希的有点在于节点加入 / 删除只会影响到在哈希环相邻的节点,而对其他节点没影响。
A 、 Dynamo 架构


考虑到节点的异构性,不同节点的处理能力差别很大, Dynamo 使用了改进的一致性哈希算法:每个物理节点根据其性能的差异分配多个 token ,每个 token 对应一个虚拟节点,每个虚拟节点的处理能力基本相当,并随机分布在哈希空间中。存储时,数据按照哈希值落到某个虚拟节点负责的区域,然后被存储到该虚拟节点所对应的物理节点。

如下图,某 Dynamo 集群中原有 3 个节点,每个节点分配 3 个 token 。存放数据时,首先计算主键的哈希值,并根据哈希值将数据存放到对应 token 所在的节点。假设增加节点 4 ,节点 token 分配情况发生变化,这就实现了自动负载均衡。


为了找到数据所属的节点,要求每个节点维护一定的集群信息用于定位。Dynamo 系统中每个节点维护整个集群的信息,客户端也缓存整个集群的信息,因此,绝大部分请求能够一次定位到目标节点。

B 、 Gossip 协议
由于机器或者人为的因素,系统中的节点成员加入或者删除经常发生,为了保证每个节点缓存的都是 Dynamo 集群中最新的成员信息,所有节点每隔固定时间(比如 1s )通过 Gossip 协议的方式从其他节点中任意选择一个与之通信的节点。如果连接成功,双方交换各自保存的集群信息。

Gossip 协议用于 P2P 系统中自治的节点协调对整个集群的认识,比如集群的节点状态、负载情况。我们先看看两个节点 A 和 B 是如何交换对世界的认识的。

( 1 ) A 告诉 B 其管理的所有节点的版本(包括 Down 状态和 Up 状态的节点);

( 2 ) B 告诉 A 哪些版本它比较旧了,哪些版本它有最新的,然后把最新的那些节点发给 A (处于 Down 状态的节点由于版本没有发生更新所以不会被关注);

( 3 ) A 将 B 中比较旧的节点发送给 B ,同时将 B 发送来的最新节点信息做本地更新;

( 4 ) B 收到 A 发来的最新节点信息后,对本地缓存的比较旧的节点做更新。

由于种子节点的存在,新节点加入可以做得比较简单。新节点加入时首先与种子节点交换集群信息,从而对集群有了认识。DHT ( Distributed Hash Table ,也称为一致性哈希表)环中原有的其他节点也会定期和种子节点交换集群信息,从而发现新节点的加入。

集群不断变化,可能随时有机器下线,因此,每个节点还需要定期通过 Gossip 协议同其他节点交换集群信息。如果发现某个节点很长时间状态都没有更新,比如距离上次更新的时间间隔超过一定的阈值,则认为该节点已经下线了。

2、 Taobao Tiar
Tair 是一个分布式的 key/value 系统。

Tair 有四种引擎:mdb, rdb, kdb 和 ldb 。分别基于四种开源的 key/value 数据库:memcached, Redis, Kyoto Cabinet 和 leveldb 。Tair 可以让你更方便地使用这些 KV 数据库。比如 Redis 没有提供 sharding 操作,如果有多个 Redis Server ,你需要自己写代码实现 sharding , Tair 帮你封装了这些。

Tair 有以下优点:

( 1 )统一的 API 。无论底层使用何种引擎,上层的 API 是一样的。

( 2 ) Tair 将集群操作封装起来,解放了开发者。淘宝内部在使用 Tair 时,一般都是双机房双集群容错,利用 invalid server 保证两个集群间的一致性,这些对于开发者都是透明的。

A 、 Tair 使用场景
( 1 )非持久化 (mdb,rdb)

• 数据可以以 key/value 的形式存储

• 数据可以接受丢失

• 访问速度要求很高

• 单个数据大小不是很大,一般在 KB 级别

• 数据量很大,并且有较大的增长可能性

• 数据更新不频繁

( 2 )持久化 (kdb,ldb)

• 数据可以以 key/value 的形式存储

• 数据需要持久化

• 单个数据大小不是很大,一般在 KB 级别

• 数据量很大,并且有较大的增长可能性

• 数据的读写比例较高

B 、 Tair 的架构
Tair 作为一个分布式系统,是由一个中心控制节点和若干个服务节点组成,

a 、 config server 功能:

( 1 )通过维护和 data server 心跳来获知集群中存活节点的信息;

( 2 )根据存活节点的信息来构建数据在集群中的分布表;

( 3 )根据数据分布表的查询服务;

( 4 )调度 data server 之间的数据迁移、复制;

b 、 data server 功能

( 1 )提供存储引擎;

( 2 )接受 client 的 put/get/remove 等操作;

( 3 )执行数据迁移,复制等;

( 4 )插件:在接受请求的时候处理一些自定义功能;

( 5 )访问统计;

c 、 client 功能

( 1 )在应用端提供访问 tair 集群的接口;

( 2 )更新并缓存数据分布表和 invalid server 地址等;

( 3 ) local cache ,避免过热数据访问影响 tair 集群服务;

( 4 )流控;

在下图中,。客户端首先请求 Config Server 获取数据所在的 Data Server ,接着往 Data Server 发送读写请求。Tair 允许将数据存放到多台 Data Server ,以实现异常容错。


C 、数据分布均衡性
Tair 的分布采用的是一致性哈希算法,对于所有的 key ,分到 Q 个桶中,桶是负载均衡和数据迁移的基本单位, config server 根据一定的策略把每个桶指派到不同的 data server 上,因为数据按照 key 做 hash 算法,保证了桶分布的均衡性,从而保证了数据分布的均衡性。

D 、容错
当某台 Data Server 故障不可用时, Config Server 能够检测到。每个哈希桶在 Tair 中存储多个副本,如果是备副本,那么 Config Server 会重新为其指定一台 Data Server ,如果是持久化存储,还将复制数据到新的 Data Server 上。如果是主副本,那么 ConfigServer 首先将某个正常的备副本提升为主副本,对外提供服务。接着,再选择另外一台 Data Server 增加一个备副本,确保数据的备份数。

E 、数据迁移
增加或减少 data server 的时候, config server 会发现这个情况, config server 负责重新计算一张新的桶在 data server 上的分布表,将原来由减少的机器服务的桶的访问重新指派到其他的 data server 中,这个时候就会发生数据的迁移。比如原来由 data server A 负责的桶,在新表中需要由 B 负责,而 B 上并没有该桶的数据,那么就将数据迁移到 B 上来,同时 config server 会发现哪些桶的备份数目减少了,然后根据负载均衡情况在负载较低的 data server 上增加这些桶的备份。当系统增加 data server 的时候, config server 根据负载,协调 data server 将他们控制的部分桶迁移到新的 data server 上,迁移完成后调整路由;

数据迁移时 data server 对外提供服务的策略,假设 data server A 要把桶 1,2,3 迁移到 data server B ,因为迁移完成前,客户端的路由表没有变化,客户端对 1,2,3 的访问请求都会路由到 A ,现在假设 1 还没迁移, 2 正在迁移, 3 已经迁移完成,那么如果访问 1 ,则还是访问 data server A ,如果访问 3 ,则 A 会把请求转发给 B ,并且将 B 的返回结果返回给客户,如果访问 2 ,则在 A 上处理,同时如果是对 2 的修改操作,会记录修改 log ,当桶 2 完成迁移的时候,还有把 log 发送给 B ,在 B 上应用这些 log ,最终 AB 数据一致才是真正完成迁移。如果 A 是由于宕机而引发的迁移,客户端会收到一张中间临时状态的分配表,把宕机的 data server 负责的桶临时指派给有其备份的 data server 来处理,此时服务是可用的,负载可能不均衡,当迁移完成后,又能达到一个新的负载均衡状态。

3、 ETCD
ETCD etcd 是一个高可用的键值存储系统,主要用于共享配置和服务发现。

(1) 由 CoreOS 开发并维护的,灵感来自于 ZooKeeper 和 Doozer ;

(2) 它使用 Go 语言编写,并通过 Raft 一致性算法处理日志复制以保证强一致。

(3) Google 的容器集群管理系统 Kubernetes 、开源 PaaS 平台 Cloud Foundry 和 CoreOS 的 Fleet 都广泛使用了 etcd ;

(4) 当集群网络出现动荡,或者当前 master 节点出现异常时, etcd 可以进行 master 节点的选举工作,同时恢复集群中损失的数据

A 、 ETCD 的特点
( 1 )简单:基于 HTTP+JSON 的 API 让你用 curl 就可以轻松使用。

( 2 )安全:可选 SSL 客户认证机制。

( 3 )快速:每个实例每秒支持一千次写操作。

( 4 )可信:使用 Raft 算法充分实现了分布式。

B 、提供的能力
Etcd 主要提供以下能力

(1) 提供存储以及获取数据的接口,它通过协议保证 Etcd 集群中的多个节点数据的强一致性。用于存储元信息以及共享配置。

(2) 提供监听机制,客户端可以监听某个 key 或者某些 key 的变更。用于监听和推送变更。

(3) 提供 key 的过期以及续约机制,客户端通过定时刷新来实现续约( v2 和 v3 的实现机制也不一样)。用于集群监控以及服务注册发现。

(4) 提供原子的 CAS ( Compare-and-Swap )和 CAD ( Compare-and-Delete )支持( v2 通过接口参数实现, v3 通过批量事务实现)。用于分布式锁以及 leader 选举。

C 、 ETCD 架构
( 1 ) Etcd v2 存储, Watch 以及过期机制

Etcd v2 是个纯内存的实现,并未实时将数据写入到磁盘,持久化机制很简单,就是将 store 整合序列化成 json 写入文件。数据在内存中是一个简单的树结构。

store 中有一个全局的 currentIndex ,每次变更, index 会加 1. 然后每个 event 都会关联到 currentIndex.

当客户端调用 watch 接口(参数中增加 wait 参数)时,如果请求参数中有 waitIndex ,并且 waitIndex 小于 currentIndex ,则从 EventHistroy 表中查询 index 小于等于 waitIndex ,并且和 watch key 匹配的 event ,如果有数据,则直接返回。如果历史表中没有或者请求没有带 waitIndex ,则放入 WatchHub 中,每个 key 会关联一个 watcher 列表。当有变更操作时,变更生成的 event 会放入 EventHistroy 表中,同时通知和该 key 相关的 watcher 。


( 2 ) Etcd v3 存储, Watch 以及过期机制

Etcd v3 将 watch 和 store 拆开实现,我们先分析下 store 的实现。Etcd v3 store 分为两部分,一部分是内存中的索引, kvindex ,是基于 google 开源的一个 golang 的 btree 实现的,另外一部分是后端存储。

按照它的设计, backend 可以对接多种存储,当前使用的 boltdb 。boltdb 是一个单机的支持事务的 kv 存储, Etcd 的事务是基于 boltdb 的事务实现的。Etcd 在 boltdb 中存储的 key 是 reversion , value 是 Etcd 自己的 key-value 组合,也就是说 Etcd 会在 boltdb 中把每个版本都保存下,从而实现了多版本机制。


4 、产品选型比较( Etcd , Zookeeper , Consul 比较)
这三个产品是经常被人拿来做选型比较的。

(1) Etcd 和 Zookeeper 提供的能力非常相似,都是通用的一致性元信息存储,都提供 watch 机制用于变更通知和分发,也都被分布式系统用来作为共享信息存储,在软件生态中所处的位置也几乎是一样的,可以互相替代的。二者除了实现细节,语言,一致性协议上的区别,最大的区别在周边生态圈。Zookeeper 是 apache 下的,用 java 写的,提供 rpc 接口,最早从 hadoop 项目中孵化出来,在分布式系统中得到广泛使用( hadoop, solr, kafka, mesos 等)。Etcd 是 coreos 公司旗下的开源产品,比较新,以其简单好用的 rest 接口以及活跃的社区俘获了一批用户,在新的一些集群中得到使用(比如 kubernetes )。虽然 v3 为了性能也改成二进制 rpc 接口了,但其易用性上比 Zookeeper 还是好一些。

(2) Consul 的目标则更为具体一些, Etcd 和 Zookeeper 提供的是分布式一致性存储能力,具体的业务场景需要用户自己实现,比如服务发现,比如配置变更。而 Consul 则以服务发现和配置变更为主要目标,同时附带了 kv 存储。在软件生态中,越抽象的组件适用范围越广,但同时对具体业务场景需求的满足上肯定有不足之处。


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

57

主题

89

帖子

0

粉丝