Gizzard简介

1. Sharding的介绍

随着互联网中信息量的增大,单台服务器已无法为现有大多数网站提供高效的服务。解决该问题的一种可行的方案就是将海量的数据进行分割,即将数据分散在多个节点上,而非单个计算机。这种方案被称之为数据分片技术(sharding)。

数 据分片主要包括两种策略:分割和复制。分割策略是将数据分割成多个小块,将各个小块分别存储在不同的节点上。每个小块都足够小,使得任一节点都能够高效地 提供相关服务。复制策略是将数据的多个副本存储在不同的节点上。这样每个节点都能够独立运行,整个系统通过简单地增加一些副本来提高系统的并发度。复制策 略同样可以解决故障问题,若有一副本出现故障,只需切换到另外一个副本即可。与复制策略不同,分割策略中的节点由于不包含全局的数据信息,因此需要一些路 由开销。

数 据分片是一件很困难的事情。为某类数据设计一款灵活的分片策略需要考虑的地方有很多,尤其是要考虑到复杂的环境(通信不可靠,节点容易出现故障等等)中的 数据一致性问题。最近几年,为了解决这些问题,一些开源的分布式数据库开始被提出和使用。但是就目前来说,大多数的开源系统不能够处理互联网中面临的一些 问题,这些系统要不太不成熟要不就是功能受限但是不可否认,这些系统都是非常有用的有缺的工作,只是我们需要一个更具有实际运行价值的可定制的系统,而不 受具体应用场景的影响。

2. Sharding框架

Twitter已经开发了若干分布式数据存储架构,这些架构之间有着很多的相似之处。也正是因为这些相似性的存在,我们开始考虑如何从不同的架构中抽取出相同点,继而能够更好的维护和复用这些系统。因此,Gizzard应运而生。Gizzard是一个Scala框架,它能够很轻松的创建用户定制的支持容错的分布式数据库。

我们将Gizzard称之为框架,是因为它是为解决某一类问题而提出的一个基本的模板。这个模板不一定适合于所有的应用场景,但是它能够解决现有的分布书存储中的大多数难点。从更高的层次上看,Gizzard将数据管理在配置好的不同的节点上,从而扮演着网络中间件服务的作用。数据分割的规则保存在转发表中,该表标识了每个切片的key值的区间。每个切片通过事先描述的复制树来维持自己的副本。Gizzard支持数据迁移(例如,添加新的机器到cluster中),并且能够很平滑的处理一些故障。Gizzard采用的是eventually consistent,所有的写操作最终都会更新到每个相关的副本处。

Rowz是Gizzard的一个简单示例,可以先通过学习Rowz来进一步了解和使用Gizzard。

首先,我们看看Gizzard究竟是如何运行的。

3. Gizzard如何工作?

3.1 Gizzard是一个中间件

如上图所示,Gizzard扮演的是网络中间件服务的角色,它位于底层数据与上层应用(PHP,Ruby,Rails等应用)的中间,所有的查询请求都会经过Gizzard。每个Gizzard的实例都是无状态的,因此可以根据吞吐量的需要来启动多个Gizzard实例。因为运行于JVM之上,Gizzard的效率很好。Twitter的一个应用实例(FlockDB,Twitter的图片数据库)即采用了Gizzard,它能够支持每秒钟10000次的查询请求。

3.2 Gizzard支持不同的底层数据存储

Gizzard是被用来在一个数据存储服务中进行数据的复制,这些数据可以被存储在关系数据库,Lucene,Redis等等存储系统中。一个基本的规则就是Gizzard要求所有的写操作都必须是幂等的(即写操作与顺序无关),这个规则也决定了应用底层所采用的存储系统。另外需要说明的是,Gizzard不保证所有的写操作都能够顺序的被执行,因此我们需要保证整个系统中数据一致性不受写操作的顺序的影响。

3.3 Gizzard通过转发表来处理数据切分

Gizzard是通过将不同的区间映射到不同的数据分片上来实现整个数据的切分(将一个很大区间的数据划分到不同的节点上)。转发表维持了这些映射关系,表中每项都标记了各个分片的key值的下限,如下图所示。

为了更适应用户的需求,Gizzard运行用户自行编写哈希函数,用来将一个给定的key值映射到一个具体的数值。允许用户自己编写哈希函数是为了让其能够根据自身的需求实现locality或者负载均衡的优化。

这种扁平的结构不同的其它的一些分布式存储中使用的consistent hashing(环形的)。这种结构可以运行构造不同大小的分块,能够很轻松的管理一些热点。在实际应用中,Gizzard允许用户实现自己定制的转发策略,例如consistent hashing,但是不推荐用户这么做。

3.4 Gizzard通过replication tree来维护不同的副本

转发表中每个数据项所指向的数据分片可以是物理分片或者逻辑分片。物理分片是指一个具体的后台数据存储,例如一个SQL数据库。相反,一个逻辑的分片指向的只是一颗包含着其它分片的树结构(replication tree)。在该树结构中,每个分支都代表着对数据的一些逻辑转换,而每个节点都是一个真实的底层数据存储。这些分支上的逻辑转换通常用来指导读写操作如何路由到相应分支的孩子节点上。下图所示的是一个两层的树结构,需要说明的是这只是一个数据分片的replication tree结构。真正的数据都是存储在所有的叶子节点处,而这些中间节点只是为了将操作请求路由到相应的节点处。

图中“Replicate”这个分支只是一个简单的策略,用来将所有的写操作路由到所有的孩子节点中运行,根据孩子节点的状态和负载来均衡所有的读操作。用户可以根据自己的需求来定制不同的分支/逻辑分片,例如添加一些transaction或者quorum的策略。但是为了扩展其用途,Gizzard利用了一些现有的标准策略,例如Replicating,Write-Only,Read-Only,以及Blocked(读写都不允许)。这些复杂的分片类型将在下面详细描述。

每个分片具体的复制策略都不尽相同,这意味着用户可以为那些hotter的分片提供更高层的复制策略,而给那些cooler的分片提供低层次的复制策略。这也使得整个系统更容易被配置以满足某些系统的需求。例如,你可以采用简单的primary-secondary-tertiary-etc的策略来做数据镜像,或者通过stripe的方式来更好的实现容错(每个机器都不会是另外一个机器的镜像)。

3.5 容错

容错是分布式系统所必须要考虑的一点。因为分布式系统由多台计算机组成,因此每个节点在任何时刻都有可能出现问题。Gizzard能够避免任何的单点故障问题。如果某个副本出现故障而不能提供服务,那么Gizzard会将之后相关的查询路由给该数据的其它的可用的副本处处理。如果一个数据分片的所有副本都奔溃了,那么Gizzard将无法提供该数据分片的读操作,但是这不会影响到其它的数据分片。针对于该数据分片的所有的写操作都将被缓存起来,等待这个分片重新开始提供服务。

实际上,如果一个数据分布任意个副本奔溃时,Gizzard将会尝试将所有的写操作都路由给当前可用的其它副本,并将这些针对于失效副本的写操作缓存起来直至这些失效副本重新开始运行。这种简单的策略就是将所有的写操作持久化(transactional),写操作都会异步的更新到一个数据分片的每个副本处。如果某个数据分片失效,则写操作将被放入一个error queue中等待之后继续尝试写(retry later策略)。注意这里针对一个失效的副本和一个失效的数据分片(该分片的所有副本都失效)的策略是不同的。

为了实现eventual consistency,这种“retry later”策略要求所有的写操作都必须是幂等的。这是因为retry later策略要求操作不受顺序的影响(一个新到达的写操作可能比那些之前失败的写操作更早被执行)。大多数的应用都能够支持这一点,Rowz就提供的一个很好的例子。

3.6 快速的迁移

因为负载均衡或硬件故障等等原因,我们经常需要将数据从一台机器拷贝或者迁移到另外一台机器,这在Gizzard中是很容易实现的。整个的过程如下所述:

  • 首先在原始的数据分片的前面复制一个新的数据分片。
  • 这个新的数据分片将会被插入到Replicating分片的后面,并且中间会有一个WriteOnly的数据分片。
  • 这个新的数据分片能够接受所有的写操作,但是无法提供读操作的服务,因为WriteOnly分片的原因。
  • 接下来,原始的数据分片上的数据将会被拷贝到新的数据分片上。
  • 当整个拷贝过程完成时,这个WriteOnly的分片将会被移除,因此读操作在新的和老的数据分片上都可以执行。
  • 另外,对于迁移数据这种情况,老的数据分片将被从replication树中删除。

需要说明的是,这里的WriteOnly分片都是物理分片,可以看作是replication tree的一个节点。

发表评论