Archive for the ‘translation’ category

[译文] Flushing out pdflush

February 9th, 2012

按:谨以此译文献给仍然挣扎在 CentOS 5/RHEL 5 上的同学们,RHEL 6/CentOS 6 已经包含这个改进了。

原作者:Goldwyn Rodrigues
原文时间:April 1, 2009
原文链接:http://lwn.net/Articles/326552/
译者:王旭( @gnawux, http://wangxu.me/blog/
翻译时间:2012年2月7-9日

在内核的页缓存(page cache)之中,存放着永久存储设备上的文件内容的内存副本。当程序把数据写入到页面之中,但还没写到磁盘上的时候,这些数据就放在缓存之中,这就是“脏页”。脏页的数量可以从 /proc/meminfo 中看到。每30秒,脏页都会被刷入(flush)磁盘。Pdflush 是一组负责将脏页刷入磁盘的内核线程,它或者是由显式的 sync() 调用出发,或者在页面被清出 page cache 时隐式地自动触发,具体情况包括,如果页面在内存中的时间过长,或者 page cache 中有过多的脏页(过多的界定由 /proc/sys/vm/dirty_ratio 确定)。

在任意给定的时间,系统中会有 2-8 个 pdflush 线程。pdflush 线程的数量由 page chche 的负载确定,如果一秒钟之内,没有空闲的 pdflush线程,但工作队列里还有很多工作要做,就会创建一个新的 pdflush 线程。另一方面,如果最后一个活跃的 pdflush 线程也已经睡了超过1秒了,那就会有一个 pdflush 线程被终止,知道只剩下了两个 pdflush 线程。当前的 pdflush 线程数可以通过 /proc/sys/vm/nr_pdflush_threads 查看。

很久以来,就有很多 pdflush 相关问题让它饱受诟病。Pdflush 线程是所有块设备共享的,但是,如果它们能专注于每一个磁臂的话,性能可能会更好一些。通过 backing_dev_info 结构的 BDI_pdflush 标志,可以避免 pdflush 线程的竞争,但是,互相影响还是会影响写回的性能。Pdflush的另一个问题是请求的饥饿问题。系统的每个队列中都可以有固定个数的I/O请求。如果超过了限制,所有应用的I/O请求都会被阻塞住,直到有新的空位出现。因为 pdflush 是为多个队列工作的,它不会阻塞在某一个队列上。这样,它会设置 wbc->nonblocking 写回信息标志。如果其他应用继续在设备上写入数据,pdflush 就无法分配出空位了。如果 pdflush 不断发现一个队列处于拥塞状态,就会导致这个队列的请求被饿死。

Jens Axboe 在一组 patch 中提出了一个新的方案,使用为每个块设备(backing device info, BDI)配置的 flusher 线程作为 pdflush 的替代品。与 pdflush 不同,BDI 专属的 flusher 线程专注于一个磁臂。在 BDI 刷写的情况下,当请求队列出现拥塞的时候,请求的分配会被阻塞住,避免饿死请求,提供了更好的公平性。

使用 pdflush,脏的 inode list 存储于文件系统的超级块。由于 per-BDI flusher 需要直到要写入的脏页属于那个块设备,所以,这个列表现在存储到了 BDI 里。这样,要刷入一个 superblock 上的所有脏 inode,就回导致刷入这个文件系统占用的所有后端设备上的脏 inode 列表。

与使用 pdflush 时类似,per-BDI 的写回是通过 writeback_control 数据结构控制的,写回程序从中知道写回什么、如何去做。这个结构中的重要字段包括:

  • sync_mode: 定义对于 inode 加锁的情况下,进行同步的方式。如果设置为 WB_SYNC_NONE,写回操作会跳过加锁的 inode,而如果设置为 WB_SYNC_ALL,则会等待加锁的 inode 被 unlock,再进行写回。
  • nr_to_write:要写的页的数量。这个值随着页面被写入而减少。
  • older_than_this:如果不为NULL,所有比这个字段中给出的 jiffies 值老的页面都会被刷入块设备。这个字段会优先于 nr_to_write。

bdi_writeback 结构保存了所有用于刷入脏页的信息:

struct bdi_writeback {
	struct backing_dev_info *bdi;
	unsigned int nr;
	struct task_struct	*task;
	wait_queue_head_t	wait;
	struct list_head	b_dirty;
	struct list_head	b_io;
	struct list_head	b_more_io;

	unsigned long		nr_pages;
	struct super_block	*sb;
};

bdi_writeback结构是在设备使用 bdi_register() 进行注册的时候初始化的。结构的各个字段的含义如下:

  • bdi: 和本结构实例相关联的 backing_device_info,
  • task: 指向缺省flusher线程的指针,这个线程用于启动进行刷写工作的线程,
  • wait: 用于同步flusher线程的等待队列,
  • b_dirty: 本 BDI 上面,需要刷入块设备的所有脏 inode 的 list,
  • b_io: 要进行 I/O 的 inodes ,
  • b_more_io: 更多的要进行 I/O 的 inodes ;所有要刷入的 inode 都先被插入到这个队列中来,之后再移送到 b_io,
  • nr_pages: 要刷入的页的总数,以及
  • sb: 指向当前 BDI 上的文件系统的超级块的指针。

nr_pages 和 sb 是异步传送给 BDI 刷写线程的参数,并且在 bdi_writeback 的生命周期中是可能发生变化的。这是因为有些设备上会有多个文件系统,也就会有多个超级块。当一个设备上有多个超级块的时候,sync 操作可以针对设备上的某一个特定的文件系统。

bdi_writeback_task 函数会周期性发起 wb_do_writeback(wb) 调用,调用周期为 dirty_writeback_interval,缺省值是5秒。如果5分钟内都没有页面被写入,flusher 线程就回退出(计时精度为 dirty_writeback_interval)。(退出)之后如果又需要工作了,那么就会由缺省写回线程来启动一个新的 flusher 线程。

写回的flush有两种工作方式:

  • pdflush方式:由显式的协会请求触发,比如 sync 一个超级块的所有 inode 页面。这时会以超级块的信息和要刷入的页面数量为参数调用 wb_start_writeback()。这个函数会去获取与此 BDI 相关联的 bdi_writeback 结构。如果获取成功的话,会将 superblock 指针和要刷入的页的数量写入 bdi_writeback 结构,并唤醒 flusher 线程,为该超级块进行实际的写操作。这个操作和 pdflush 的写入操作有所不同:pdflush 会根据写的路径来获取设备,阻塞住其他进城的写操作。
  • kupdated 方式:如果没有显式的写回请求,写回线程会周期性地刷入脏数据。当一个 BDI 的一个 inode 的页面第一次变脏的时候,会将 dirtying-time 记入 inode 的地址空间中。当周期性的写回代码检查到这个超级块的 inode 列表的时候,就回写回比某一时间更早变脏的 inode。这一操作的周期是 dirty_writeback_interval,缺省是5秒钟。

第一轮的讨论之后,Jens 根据 Andrew Morton 的建议,添加了每个设备有多个 flusher 线程的支持。Dave Chinner 建议,文件系统可能会希望每个分配组有一个 flusher 线程。在随后的(第二轮)补丁中,Jens 在 superblock 中添加了一个新的接口,来通过一个 inode 获取相关联的 bdi_writeback 结构:

struct bdi_writeback *(*inode_get_wb) (struct inode *);

如果 inode_get_wb 为空,就会返回 BDI 缺省的 bdi_writeback 结构,这就意味着这个 BDI 只有一个 bdi_writeback 线程。每个 BDI 可以启动的最大线程数为 32。

Jens 对一块 SATA 硬盘使用 Flexible File System Benchmark (ffsb) 进行的测试显示,可以获得 8% 的性能提升。通过 vmstat 观察,与未打过补丁的内核相比,文件操作的分布更加平滑 ,缓冲区的写回呈现出均匀分布。而对于一个10块硬盘的 btrfs 文件系统,per-BDI 刷写可以获得 25% 的提速。这组代码位于 Jens 的块设备层 git tree  (git://git.kernel.dk/linux-2.6-block.git)  的 writeback 分支。目前,第二轮讨论还没有新的意见出现,但 per-BDI flusher 线程还不足以进入 2.6.30 内核。

致谢:感谢 Jens Axboe 审阅本文并对这组补丁的一些内容进行了解释。【译文完】

[译文]Ext4新进展:bigalloc, inline data, 和元数据校验和

February 4th, 2012

原文:http://lwn.net/Articles/469805/
原作者:Jonathan Corbet
原文发布时间:November 29, 2011
译者:王旭 ( @gnawux ;  http://wangxu.me/blog/ )
翻译时间:2012年2月3日

ext4是一个刚刚发布一两年的文件系统。不过,这个坚实稳定的系统是基于一个很古老的设计演化而来的。对于一些被作为“下一代文件系统”开发的 FS,比如 btrfs,本文要提到的一些特性可能都已经具备了。然而,btrfs这样的文件系统还需要相当长的时间,才能在更广泛的用户群中获得足够的信心,同时,日益增长的 ext4 用户对这些特性同样心仪已久。近来提交的一些补丁就让我们看到,虽然 ext4 在很久以前就已经进入一个相对稳定的阶段了,但新特性的开发也并未停止。

Bigalloc

在 Linux 早期,磁盘的大小还是MB级的,文件系统的块大小也只是 1KB 到 4KB。而在本文写作的时候,TB级的硬盘虽然在最近涨了一点价,但无碍一个事实——硬盘已经变大太多了,存储在上面的文件也是如此。但 ext4 文件系统仍然以 4KB 为单位来管理数据。其结果就是要管理的块数太多了,相关联的位图大小必然随之增长,管理这些块的开销也就极具增大。

在内核中增加文件系统的块大小会对内存管理、page cache 等产生深远影响,因此是件很恐怖的工作。所以大家都不愿意一下子触及这么多地方,但这并不能阻止文件系统被放在越来越大的磁盘上。在 3.2 内核中,ext4 将可以解决这个问题。“bigalloc”补丁给文件系统引入了一个“块簇(block cluster)”的概念,这样,在一个更大的块组中进行分配时,将一次分配一个块簇而不是一个单独的块。在内核中,这些更大的块和原始的4KB块的映射关系由文件系统负责维护。

簇的大小由管理员在创建文件系统时确定(使用 e2fsprogs 的一个开发版本),不过这个值必须是 2 的整数次幂。在很多情况下,64KB 是个合适的值,对于那些只存放大文件的文件系统,1MB 的簇大小可能是更佳选择。必须指出的是,如果文件系统专用于存放小文件,那么指定一个过大的簇大小回造成很多空间浪费。

使用簇可以减少块位图和其他管理用数据结构的开销。但是,根据 Ted Ts’o 在 7 月份给出的数据。因为这个特性可以降低磁盘的碎片化,对文件IO的性能同样有改进。这个特性预计会成为 3.2 内核(以及 e2fsprogs 1.42)对很多用户的一个杀手级特性。

inline data

inode 是文件系统中用于描述一个文件的数据结构。对于大多数文件系统而言,有两类 inode:文件系统无关的内核数据结构(struct inode),以及文件系统相关的 on-disk 版本。常规地说,如果内核没有一份 inode 的副本的话,就根本无法操作一个文件。所以,本质上说,inode 是很多 block I/O 的关键入口点。

在 ext4 文件系统中,磁盘上的 inode 尺寸可以在文件系统创建时指定,缺省是 256 字节。不过,磁盘数据结构(struct ext4_inode) 只需要大概一般的空间。ext4_inode 结构之后剩下的空间通常用于存储扩展属性。比如 SELinux label 就存放在这里。在没大量使用扩展属性的系统中,磁盘 inode 结构的空余空间就直接浪费掉了。

同时,文件数据的空间分配是以文件系统块为单位的,与 inode 彼此独立。如果一个文件非常小(即使在现在的文件系统中,还是有很多小文件),用于存放这个文件的块就浪费了很多空间。如果文件系统使用了上面提到的块簇,那么浪费的空间还会更多,这里,用户可能就会开始抱怨了。

马涛(@淘伯瑜)同学的 ext4 inline data 补丁将会改变这一局面。这个想法非常简单:很小的数据可以直接存放在 inode 之间的空余空间里,根本无需单独分配数据块。对于使用 256个字节的 inode 的文件系统,全部空余空间将会被用于存放这些小文件。如果文件系统使用更大的 inode,只有一半的剩余空间会用于存储文件,剩下的空间留给后面可能要添加的扩展属性,否则的话,这些扩展属性就不得不存放在 inode 之外了。

涛哥提到,在使用了这个补丁的情况下,用于存放内核源码的空间会减少大约 1%,而 /usr 会减少大约 3%。当启用簇之后,节省的空间应该会更多,但这也不能保证对所有情况都能减少。仍然有些细节问题没有完全完成——包括 e2fsck 支持以及扩展属性存放在 inode 之外带来的开销——所以,这个特性最早要在 3.4 kernel 中才会出现。

元数据校验和

存储设备并不总是像我们期望的那样可靠的,由于硬件原因造成的数据损坏案例屡见不鲜。正因为如此,关心数据安全的人们使用了 RAID 这样的技术,或者像 Btrfs 这样的文件系统可以保存数据和元数据的校验和,以确保内容不被硬件弄丢。不过,ext4 文件系统没有这一能力。

Darrick Wong 的校验和补丁并没有解决全部问题。实际上,它似乎进一步印证了那个老笑话——文件系统开发者并不真的关心他们存储数据的正确性,只要文件系统的元数据没错就行了。这组补丁通过给 ext4 文件系统的各个数据结构加上校验和,包括 superblock、bitmap、inode、dir index、extent tree 等,并在读取时检验校验和,以保证元数据的正确性。校验和失败可能导致文件系统失败,如果这发生在一个挂载着的文件系统上,会把它变为只读,并在系统日志中输出一些相关信息。

Darrick并未提及任何给用户数据添加校验和的计划。给数据添加校验会是一个更宏大的工程——为已有的元数据数据结构添加一个校验字段相对简单,但要存储数据块的校验和就得给文件系统增加全新的数据结构了。而且,全数据校验的性能损失也会更高。所以,尽管未来可能有人会来介入这个问题,但迄今为止还没有这个动向。

即使只是元数据的校验和,对文件系统的改变仍然是很大的,不过相当一部分工作是属于 e2fsprogs 的。特别的,e2fsck 将具有检查元数据校验和的功能,并在某些校验和出错时进行修复。校验和可以在 mke2fs 时打开,或是通过 tune2fs 开启。总的说,这是个很大的工作,但确实可以帮助用户增加对文件系统结构的信心。根据 Darrick 的介绍,计算和检验校验和的开销在大部分情况下是可以忽略不计的。这个特性目前还没有收到很多反馈,或许已经接近被接纳进内核,但还不清楚什么时候会进来。

[译文] SCSI Target 之双城记

November 18th, 2011
作者:Goldwyn Rodrigues
原文发布日期:January 22, 2011
来源:http://lwn.net/Articles/424004/
译者:王旭( http://wangxu.me , @gnawux )
翻译时间:2011年11月17日

按:上次翻译 LWN 的文章似乎还是 两年前翻译空指针的乐趣的事呢,时间好快,这次来深圳高交会看展台,晚上无聊,就翻译了这个。作为这个故事的结局,LIO已经在 2.6.38 进入 kernel 了。

2010年底,LIO 项目获选成为新的内核态的 SCSI target,取代原有的用户态的 STGT 项目。当时有两个主要的竞争项目(LIO和SCST),都在努力将代码并入主线内核。本文将比较着两个项目,并尽力描述他们都提供了什么东西。

什么是 SCSI Target?

SCSI 子系统使用了一种客户机-服务器(C/S)模型。通常,一台计算机是这个模型中的客户机,称为 initiator(发起者),想 target (目标)发起块操作请求,这个 target 通常是一个存储设备。SCSI Target 子系统可以让一台计算机作为一台 SCSI 存储设备来工作,响应其他 SCSI initiator 节点的存储请求。这样就可以定制 SCSI 存储设备,并让存储设备工作得更加“智能”了。

一个智能 SCSI Target 的例子是Data Domain的在线备份设备,它具有数据排重的功能,可以节约空间。这个设备从功能上说是个 SCSI target,但它实际是一台智能的计算机,它只存储那些还没有的数据块,对于已经存在的数据,只是增加引用计数,这样只写入那些上次备份之后有变动的块。而在 SCSI 连接的另一边,initiator 看到的设备就是一个普通的、共享的 SCSI 存储设备,只要使用任意的备份软件,将备份写向这个 target就行了。

最常见的 SCSI target 子系统的实现是 iSCSI 服务器,它使用标准的 TCP/IP 来封装 SCSI 指令,通过网络来提供一个 SCSI 设备。大多数 SCSI target 项目在最初都会先支持 iSCSI 协议。这事因为 iSCSI initiator 和 iSCSI target 之间只需要一个网络连接,差不多所有计算机都可以使用,对它的支持不需要任何特殊硬件。不过,大部分的 SCSI target 还能支持已有的 initiator 卡,所以,如果你又一个光纤通道、SAS 或并行 SCSI 卡,可能某个 SCSI target 项目可以支持这些特定设备的 SCSI 总线。

当前状态

当前 Linux 内核的 SCSI 子系统使用 STGT 来实现 SCSI target 功能;STGT 是在 2006 年末,由 Fujita Tomonori 引入的。它在内核中有一个库,来配合内核中的 target 驱动工作。而所有的 target 处理都在用户空间完成,这可能回带来一些性能瓶颈。

有两个还没有并入内核的 SCSI target 实现尅考虑用来替换 STGT:LIO SCST。SCST 至少在2008年就试图推入 Linux 内核。当时认为 STGT 项目海可以为内核服务稍长一段时间。但随着时间的推移,STGT 的设计局限被发现,并且有了一个可用的替换方案。替换 SCSI target 子系统的主要条件是由 James Bottomley 确定的,他是 SCSI 的维护者,条件如下:

  1. 它将更换掉已有的 STGT,因为只能有一个 SCSI target 基础设施。
  2. 要使用现代的、基于 sysfs 的控制与配置方式。
  3. 代码要被仔细审查,确定足够干净、可以进入内核。

第一个条件被证实太过于严苛了,会不可避免地破坏整个 ABI。所以,当前的目标变成了寻找一种方法,来让 STGT 用户平滑地过渡到新接口上来。

LIO 替代 STGT 的项目开始于 2010 年的 Linux 存储与文件系统峰会(LSF 2010)。Cristoph Hellwig 志愿来审阅并清理代码,他尽力将代码缩减到一万行以内,使之可以被并入内核。

对比

两个项目都在他们的官方网站(LIO 和 SCST)上提供了他们的特性对比图表。不过,在探讨它们的不同之前,先来看一下相似性吧。两个项目都实现了一个内核态的 SCSI target 核心。他们都提供了类似 loop device 的本地 SCSI target,这让使用他们的 target 创建虚拟设备变得很方便。两个项目都支持 iSCSI,这是他们的项目的最初的也是最主要的动机。

两个项目的后端存储管理都可以在内核空间或是用户控件进行。后端存储管理器让 target 的管理员可以控制设备如何输出服务给 initiator。比如,pass-through 后端允许将一个 SCSI 硬件直接提供给用户,而不屏蔽掉这个设备的任何细节;而 virtual-disk 后端则允许将一个文件作为虚拟磁盘来输出给 initiator。

两个项目都支持永久性预留(Persistent Reservation, PR);这是一个用于高可用集群中的存储设备的 I/O 隔离与存储设备故障切换、接管的特性。通过使用 PR 命令,initiator 可以在一个 target 上建立、抢占、查询、重置预留策略。在故障接管过程中,新的虚拟资源可以重置老的虚拟资源的预留策略,从而让故障切换更快、更容易地进行。

SCST

SCSI target 子系统的主要用户是提供存储解决方案的存储公司。大部分这些存储解决方案都提供了即插即用的设备,可以只进行很少的配置,甚至是无需配置,就被加入到一个存储网络。SCST 拥有更广泛的用户群,这可能是因为它们支持更多的传输方式。

SCST 支持 Qlogic 和 Emulex 的光纤通道卡,而 LIO 目前只支持 Qlogic 的 target 驱动,并且这个驱动也还在 beta 测试截断。SCST 支持 SCSI RDMA 协议(SRP),并宣称对于以太网传输的光纤通道协议(FCoE)、LSI 的并行 SCSI 光纤通道以及串行 SCSI(SAS)等协议的的开发也处于领先地位。目前,它已经对 IBM pSeries 的虚拟 SCSI 提供了支持。目前,Scalable Informatics、Storewize、Open-e 等公司都基于 SCST target 开发了它们的即插即用设备。

SCST 可以使用异步事件通知(AEN)来通告会话状态的变更。AEN 是一个 SCSI target 用来向 initiator 进行 target 端的事件告知的协议特性,即使在没有服务请求的时候也可以进行。于是 initiator 就可以在 target 端发生事件时,如设备插入、移除、调整尺寸或更换介质时,可以得到通知。这让 initiator 可以以即插即用的方式看到 target 的变化。

SCST 的开发者声称,它们的设计在健壮性和安全性方面更加符合 SCSI 标准。SCSI 协议要求,如果一个 initiator 要清除另一个 initiator 的预留资源时,预留者必须要得到清除通知,否则,多个 initiator 都可能来改变预留数据,就可能会破坏数据。SCST 可以实现安全的预留、释放操作,避免类似事情发生。

依照 SCSI 协议,initiator 和 target 可以协商决定传输尺寸。一个 initiator 端错误的传输尺寸通信可能会导致 target 设备端的锁死或是崩溃。SCST 的安全保障机制可以在传输尺寸或方向出错时避免这个问题。他们的代码中具有良好的内存管理策略来避免内存耗尽的情况。还可以限制介入 target 的 initiator 的数量,避免过多连接占用资源。SCST 还支持每个 portal 的可见性管理,也就是说,可以让一个 target 只对一组 initiator 可见。

LIO

LIO 项目最初是以 iSCSI 作为核心目标的,创建了一个支持 iSCSI 的通用 SCSI target 子系统。简单性是项目的一个重要设计目标,因此,LIO 也更容易理解。除此之外,LIO 的开发者表现得更乐于和内核开发者合作,正如 James 对 SCST 的维护者 Vladislav Bolkhovitin 所指出的:

来,让我们把事情说得简单一点:在这个社区里,不是让你直接把菜上到桌上就行了,你需要加入到这个社区当中来,称为 linux 内核社区的一部分。更广泛的社区焦炉是开源项目成功的必要条件。你曾经有过这样的机会了:我们在其他地方已经使用了 sysfs,但在 STGT 这里,你就说了一句——这事我们的接口,用它好了。而 LIO 则问了他们所需要的东西,并设法来使用 sysfs 接口。事已至此,你为什么还会对 STGT 的人更倾向于 LIO 而表示大惊小怪呢?

LIO 项目还提供了一些 SCST 没有或刚刚开始开发的特性。比如,LIO 支持非对称逻辑卷分配(ALUA)。ALUA 允许 target 管理员来管理 target 的访问状态和路径属性。这让多路径路由机制可以选择最好的路径,从而根据 target 的访问状态,优化带宽的使用。换句话说,在多路径环境下,target 管理员可以通过改变访问状态来调整 initiator 的路径。

LIO 海支持管理信息数据库(MIB),会让管理 SCSI 设备更简单。SCSI target 设备可以按照 RFC4455 SCSI MIB 的描述方式来输出管理信息,这些信息会被 SNMP agent 收集起来。这个特性扩展了 iSCSI 设备,在管理有很多 SCSI 的存储网络时好处会更加明显。

iSCSI 连接的错误可能会发生在三个层面上:会话、校验或是连接层。错误恢复工作也可以在这三个层面开始进行,这样就可以在当前的层面开始进行恢复,不会让错误到达下一个层面。错误恢复首先是检查断开的连接。在这种情况下,iSCSI initiator 驱动会主动建立新的到 target 的 TCP 连接它会告诉 target,SCSI 指令路径已经变到新的连接上了。这样 target 就可以在新的连接上处理 SCSI 命令了。这时,上层的 SCSI 驱动对新的连接已经建立、控制信息已经通过新连接传输的事还是毫无知觉的。iSCSI 会话在这期间会保持正常,不会重新变换状态。LIO 支持的最大错误恢复级别(ERL)为2,这就是说,它可以在会话、校验或连接层进行错误恢复。而SCST 支持的 ERL 为 0,也就是说,它智能恢复会话级别的错误,所有连接层面的错误都会转到 SCSI 驱动层面来处理。

LIO 还支持“会话多连接”(MC/S)。MC/S 让 initiator 可以和 target 在一条或多条物理路径上建立多条连接。这样,在一条路径发生错误的时候,已经建立好的会话可以不中断会话,直接使用其他的路径。MC/S 还可以用来进行所有连接之间的负载均衡。这种情况下,会在所有通信路径上保持会话命令的顺序性。

LIO 还宣称,他们的代码被用于了很多设备之中,虽然他们的用户看起来和 SCST 的差不多。

没有性能对比的对比不是个完整的对比。SCST 的开发者经常会放出他们的性能数据。但是,所有数据都是和 STGT 进行的对比。SCST 的对比页面说,他们的性能好于 LIO,但是是根据代码研究而非真实世界测试的结果。SCST 指责 LIO 没有发布数据,(在我的印象里)确实没有性能数据来在两者之间进行直接对比。

不过最后,决定已经做出了,虽然有一点反对的声音。现在的任务就是把所有 LIO 没有但有用的特性从 SCST 移植到 LIO 来。尽管这个决定有些争议,但这俨然又是一次试图把不能和内核社区合作的代码并入内核的失败尝试。

 

暂时放弃译书工作了

August 1st, 2011

其实近期也没打算开始译书,这是时间和精力的原因。但这两天看到china-pub 的一些书评,心里不怎么舒服,在自己的汉语水平得到有效提高之前,我还是暂时不要接这类工作了。

我可以自我安慰地说某些网友可能就是来踢场子的,毕竟调戏译者也是件挺好玩的事;同时也见到有人在夸我或者安慰我。但译书这件工作本身,是有责任的。我眼里这个责任,不是说我尽力了就可以了,而是确实做好了才行,这是能力问题,而非纯粹的态度问题。就我的个人能力而言,只要认真看,技术文章还是能看懂的,但中文作文的能力自己也没什么信心,我自己能保证所有内容我自己看得懂,却无法让它达到读者所期望的流利水平。

再者,计算机领域,尤其是快速发展的互联网相关的技术领域,知识的新陈代谢速度非常之快,本书的翻译时间是三个月,前面是原书的出版周期和版权谈判等时间,后面是出版社的整理出版周期。这样,原书是针对0.6 正式版和0.7 Beta 版的,等到中文版上架的时候,0.8 都已经发布了,所幸,主要是增加了几个新特性,而非去掉本书介绍的东西。

那么我这三个月是怎么干的呢?我是业余翻译,支撑我的是兴趣和责任,在翻译期间,用掉了几乎所有的业余时间,甚至在医院一边排队等号,一边用iPad 翻译,为此还特意买了个蓝牙键盘。所有内容都是过了两遍的:第一遍初译,纯直译方式;第二遍整理,按照我的语言习惯来理顺语言,统一表达等。时间紧迫的情况下,有可能影响整理的精细程度,所以,如果有人说部分内容译得像机器的话,我想应该不是来无理取闹的。

或许我可以说,快餐的质量无需和大餐去比较,不过我也看过李松峰等优秀译者翻译的文章和一些不错的译著,我必须承认,我在翻译方面的水平仅限于自我娱乐,一两天或三五天翻译一篇自己喜欢的文章还不错,但长篇翻译一本书还是欠火候,我还没有能力在短时间内遣词造句,兼顾表达愿意和语句流畅两个方面。出版业是一个严肃的行业,没能力的同学应该自觉靠边或是回家学习去。

嗯,当然,在不污染视听的角落里,我还是会悄悄地在我的blog 里,在不违反原作者意愿的情况下,翻译一些我喜欢的文章的,对于读者,可以任其选择读与不读。

 

ZooKeeper Watches

June 6th, 2011

按:王旭(http://wangxu.me/blog, @gnawux)于2011年6月6日译自 ZooKeeper程序员指南 (http://zookeeper.apache.org/doc/r3.3.3/zookeeperProgrammers.html)的同名章节。似乎很少有文档提这个啊,我其实在看这个之前一直不明白这东东是怎么用的。

所有的Zookeeper读操作,包括getData()、getChildren()和exists(),都有一个开关,可以在操作的同时再设置一个watch。在ZooKeeper中,Watch是一个一次性触发器,会在被设置watch的数据发生变化的时候,发送给设置watch的客户端。watch的定义中有三个关键点: 

  • 一次性触发器

    一个watch事件将会在数据发生变更时发送给客户端。例如,如果客户端执行操作getData(“/znode1″, true),而后 /znode1 发生变更或是删除了,客户端都会得到一个  /znode1 的watch事件。如果  /znode1 再次发生变更,则在客户端没有设置新的watch的情况下,是不会再给这个客户端发送watch事件的。

  • 发送给客户端

    这就是说,一个事件会发送向客户端,但可能在在操作成功的返回值到达发起变动的客户端之前,这个事件还没有送达watch的客户端。Watch是异步发送的。但ZooKeeper保证了一个顺序:一个客户端在收到watch事件之前,一定不会看到它设置过watch的值的变动。网络时延和其他因素可能会导致不同的客户端看到watch和更新返回值的时间不同。但关键点是,每个客户端所看到的每件事都是有顺序的。

  • 被设置了watch的数据

    这是指节点发生变动的不同方式。你可以认为ZooKeeper维护了两个watch列表:data watch和child watch。getData()和exists()设置data watch,而getChildren()设置child watch。或者,可以认为watch是根据返回值设置的。getData()和exists()返回节点本身的信息,而getChildren()返回子节点的列表。因此,setData()会触发znode上设置的data watch(如果set成功的话)。一个成功的 create() 操作会触发被创建的znode上的数据watch,以及其父节点上的child watch。而一个成功的 delete()操作将会同时触发一个znode的data watch和child watch(因为这样就没有子节点了),同时也会触发其父节点的child watch。

Watch由client连接上的ZooKeeper服务器在本地维护。这样可以减小设置、维护和分发watch的开销。当一个客户端连接到一个新的服务器上时,watch将会被以任意会话事件触发。当与一个服务器失去连接的时候,是无法接收到watch的。而当client重新连接时,如果需要的话,所有先前注册过的watch,都会被重新注册。通常这是完全透明的。只有在一个特殊情况下,watch可能会丢失:对于一个未创建的znode的exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个watch事件可能会被丢失。 


ZooKeeper对Watch提供了什么保障

对于watch,ZooKeeper提供了这些保障:

  • Watch与其他事件、其他watch以及异步回复都是有序的。 ZooKeeper客户端库保证所有事件都会按顺序分发。

  • 客户端会保障它在看到相应的znode的新数据之前接收到watch事件。

  • 从ZooKeeper接收到的watch事件顺序一定和ZooKeeper服务所看到的事件顺序是一致的。


关于Watch的一些值得注意的事情

  • Watch是一次性触发器,如果你得到了一个watch事件,而你希望在以后发生变更时继续得到通知,你应该再设置一个watch。

  • 因为watch是一次性触发器,而获得事件再发送一个新的设置watch的请求这一过程会有延时,所以你无法确保你看到了所有发生在ZooKeeper上的一个节点上的事件。所以请处理好在这个时间窗口中可能会发生多次znode变更的这种情况。(你可以不处理,但至少请认识到这一点)。

  • 一个watch对象或一个函数/上下文对,为一个事件只会被通知一次。比如,如果同一个watch对象在同一个文件上分别通过exists和getData注册了两次,而这个文件之后被删除了,这时这个watch对象将只会收到一次该文件的deletion通知。

  • 当你从一个服务器上断开时(比如服务器出故障了),在再次连接上之前,你将无法获得任何watch。请使用这些会话事件来进入安全模式:在disconnected状态下你将不会收到事件,所以你的程序在此期间应该谨慎行事。

 

 

[译文]Cassandra实例

May 27th, 2010

原文: http://www.rackspacecloud.com/blog/2010/05/12/cassandra-by-example/#
原作者:Eric Evan 
原文发布日期:May 12, 2010
译者:王旭(http://wangxu.me/blog/ , @gnawux)
翻译时间:2010年5月15,25,26日

近来 Cassandra 备受瞩目,很多人正在评估是否可以应用 Cassandra。由于这些人更多的追求速度,相应的,我们的文档就过于粗浅了。这些文章中,最差的是为有关系数据库基础的人解释Cassandra数据模型的那些。

Cassandra 数据模型实际和传统的数据库差异非常大,足够让人眩晕,而且很多误解都需要修正。

有些人把这个数据模型描述成存放map的map,或对于super column的场景,是存放map的map的map。这些解释经常用类似 JSON 标记的视觉辅助展示方法来进行佐证。其他人则把列族看做是系数表,还有人把列族看作是存放列对象的集合容器。甚至有人有时把列看走势三元组。我觉得所有这些解释都不够好。

问题在于很难去用类比的方法来确切解释一个新的东西,而且如果比较的不准确的话常常把人搞糊涂。我仍然期望有人能解释清楚这个数据模型,但同时我觉得确切的例子可能更容易说明白一些。

Twitter

尽管 Twitter 本身就是 Cassandra 的一个实际的应用场景,它仍然是一个不错的教学实例,因为它众所周知而且易于抽象。在例子中,和很多站点一样,每个用户都有一份用户数据(显示名称、密码、email等),这些信息链接到朋友(译注:用户follow的人)和 follower(译注:follow用户的人)。此外,如果没有那些短 tweets 的话也就不是 twitter 了,tweet每条140个字符,它们都关联着诸如时间戳和惟一的id这样的元数据,这个id我们可以从URL里看到。

现在我们在一个关系数据库里来直接进行建模,我们首先需要一个表来存放用户。

CREATE TABLE user (
    id INTEGER PRIMARY KEY,
    username VARCHAR(64),
    password VARCHAR(64)
);

我们还需要两张表来存储一对多的follow关系。

CREATE TABLE followers (

    user INTEGER REFERENCES user(id),

    follower INTEGER REFERENCES user(id)

);

 
CREATE TABLE following (

    user INTEGER REFERENCES user(id),

    followed INTEGER REFERENCES user(id)

);

显然,我们还需要表来存储tweets。

CREATE TABLE tweets (
    id INTEGER,
    user INTEGER REFERENCES user(id),
    body VARCHAR(140),
    timestamp TIMESTAMP
);

由于仅仅是个例子,我已经极大简化了情况,但仅仅是这个极度简化的模型,也还有很多需要做的工作。例如,要以可行的方法达到达到数据归一化就需要一个外部键值约束,而因为我们需要从多张表join信息,我们需要对任意值建索引,以保证高效。

但是让一个分布式系统正常工作相当有挑战性,几乎不可能不做任何折衷。对Cassandra来说也是如此,而且这也是为什么上述数据模型对我们来说是无法工作的的原因。对于入门者,没有可供参考的完整性,缺乏次索引使得join很难进行,所以,你必须反归一化。另一方面,你被迫思考你要进行的查询的方式和期望结果,因为这差不多就是数据模型看起来的样子。

Twissandra

那么如何把上述模型翻译到Cassandra中呢?十分幸运,我们只需要看看 Twissandra,这是 Eric Florenzano 写的一个 Twitter 的简化版克隆,用作例子。那么让我们来使用 Twitter 和 Twissandra 作为例子来看看 Cassandra 的数据模型是如何的。

Schema

Cassandra 是一种无 schema 的数据存储方式,但为你的应用做一些特定的配置还是必要的。Twissandra 给出了一个可以工作的 Cassandra 配置,不过研究一下关于数据模型方面的配置还是物有所值的。

Keyspaces

Keyspaces 是 Cassandra 中最顶层的命名空间。在未来版本的 Cassandra 中,将可以动态创建 keyspace,正如在 RDBMS 中创建数据库一样,但是对于 0.6 和以前的版本,这些都在主配置文件中定义,如:

<Keyspaces>
  <Keyspace Name="Twissandra">
  ...
  </Keyspace>
</Keyspaces>
Column Families

对于每个 keyspace,都可以有一个或多个列族。列族是用于关联类型相近的记录的命名空间。Cassandra 在写操作时,在一个列族内部允许有记录级的原子性,对它们进行查询非常高效。这些特性十分重要,在进行你的数据建模前必须记牢,它们会在下面讨论到。

和keyspace类似,列族也在主配置文件中定义,虽然在将来的版本中你将可以在运行时创建列族,正像在RDBMS中创建表一样。

<Keyspaces>
  <Keyspace Name="Twissandra">
    <ColumnFamily CompareWith="UTF8Type"  Name="User"/>
    <ColumnFamily CompareWith="BytesType" Name="Username"/>
    <ColumnFamily CompareWith="BytesType" Name="Friends"/>
    <ColumnFamily CompareWith="BytesType" Name="Followers"/>
    <ColumnFamily CompareWith="UTF8Type"  Name="Tweet"/>
    <ColumnFamily CompareWith="LongType"  Name="Userline"/>
    <ColumnFamily CompareWith="LongType"  Name="Timeline"/>
  </Keyspace>
</Keyspaces>

需要指出的是,上面的配置片段中,指定名字的时候同时指定了一个比较者类型。这凸显了 Cassandra 和传统数据库的又一个重大不同,记录按照设计的顺序存储,在之后不能轻易改变。

这些列族都是什么?

一下子看所有的七个Twissandra列族是干什么的可能不那么直观,所以,我们来逐个仔细看一下:

  • User

User用于存储用户信息,大致相当于上面描述的用户表。列族中的每条记录以UUID为键值,并包含用户名和密码列。

  • Username

在User列族中查询一个用户需要知道用户的键值,但从用户名怎么找到这个UUID键值呢?在上面描述的SQL关系数据库里的话,我们就在User表里来一个匹配用户名的SELECT语句(WHERE username = ‘jericevans’)就行了。但这对于Cassandra来说却不可能。

首先,关系数据库可以顺序地扫描全表来进行这样一个 SELECT,但由于记录是基于键值分布在 Cassandra 集群中的,这个匹配将可能会在多个节点上进行,可能是很多节点。而且,即使是数据就在一个节点上,仍然有一个原因会让这一操作远没有关系数据库效率高,因为关系数据库可以对username列有索引。前面提到过,Cassandra是不支持第二索引的。

解决方案就是,建立一个我们自己的反向索引,进行用户名到UUID键值的映射,这就是Username列族的用途。

  • Friends
  • Followers

Friends 和 Follower 列族可以回答这些问题:用户X follow了哪些人?谁follow了用户X?这两个列族的键值都是这个唯一的用户ID,其中包含了哪些有follow关系的用户以及它们创建的时间。

  • Tweet

Tweet 列族用于存放所有的tweets。这个列族以每个 tweet 的 UUID为键值,还包含了用户id,tweet内容以及tweet时间这些列。

  • Userline

这是属于每个用户的时间线。记录的键值是用户的ID,其他的列中,包含有一个数字时间戳到Tweet列族中的tweet ID的映射。

  • Timeline

最后,Timeline列族类似于Userline,只是这里存储着每个用户的朋友的tweet的时间线视图。

有了上面这些列祖,现在我们可以看一些常用的操作都是如何发生的。

把这些列族放在一起来试一下

添加一个新用户

首先,新用户需要一个方法来注册一个账户,当他们注册的时候,组要将他们添加到Cassandra数据库中去。对于Twissandra,我们来看看里面的内容:

username = 'jericevans'
password = '**********'
useruuid = str(uuid())   
columns = {'id': useruuid, 'username': username, 'password': password}   
USER.insert(useruuid, columns)
USERNAME.insert(username, {'id': useruuid})

Twissandra是用Python写成的,使用 Pycassa 作为访问 Cassandra的客户端,上述大写的 USER 和 USERNAME 是 pycassa.ColumnFamily 的实例,它们需要在使用之前的某个位置被分别初始化。

这里说明一下,这不是从 Twissandra 里原样摘出来的。我让他们更加简单而且是自包含的。比如,在上面的例子中,如果没有对用户名和密码的赋值的话,可能不那么好理解,不过一个 web 应用只能从用户注册表单里得到这些内容。

从这个例子中回来,有两个不同的 Cassandra 写操作(insert()),第一个创建了一个用户列族,另一个更新了用户名到用户 UUID 键值的反向映射表。在两个例子中,参数都是用于查找记录的键值,以及包含列名和值的map。

Following 一个朋友
frienduuid = 'a4a70900-24e1-11df-8924-001ff3591711'   
FRIENDS.insert(useruuid, {frienduuid: time.time()})
FOLLOWERS.insert(frienduuid, {useruuid: time.time()})

这里我们再来两个不同的insert()操作,这次是加入一个用户到我们的朋友列表,并加入反向关系:给被 follow 用户添加一个 follower。

发出Tweet
tweetuuid = str(uuid())
body = '@ericflo thanks for Twissandra, it helps!'
timestamp = long(time.time() * 1e6)   
columns = {'id': tweetuuid, 'user_id': useruuid, 'body': body, '_ts': timestamp}
TWEET.insert(tweetuuid, columns)   
columns = {struct.pack('&gt;d', timestamp: tweetuuid}
USERLINE.insert(useruuid, columns)   
TIMELINE.insert(useruuid, columns)
for otheruuid in FOLLOWERS.get(useruuid, 5000):
    TIMELINE.insert(otheruuid, columns)

要存储一条新的tweet,我们需要使用一个新的UUID作为键值,在 Tweet列族创建一个记录,其中的列包含作者的用户ID,创建的时间,当然还有tweet的文本内容本身。

此外,用户的 Userline 中也要加入tweet的时间和它的id。如果这是用户的第一条tweet的话,这个insert()会产生一条新的纪录,后面的只是为这条记录添加新列。

最后要给发出tweet的用户和其他follower的 timeline 列族添加这条tweet的ID和时间。

值得注意的一件事是,这里,时间戳使用的是64位长整型变量,而当它成为一个列的名字的时候,它会被打包为网络字节序的二进制值。这是因为Userline和Timeline列族使用了一个LongType Comparator,允许我们使用数值区间指定查找指定范围,所以它们被按照数值来存放起来。

接收一个用户的 tweets
timeline = USERLINE.get(useruuid, column_reversed=True)
tweets = TWEET.multiget(timeline.values())

接收一个用户的tweet,首先从Userline获取tweet ID的一个列表,然后从Tweet列族通过multiget()方法莱读取这些tweet。得到的结果将是通过着数值表示的时间戳逆序排列的,因为Userline使用了LongTyper comparator,并且reversed设置为了True。

获取一个用户的时间线
start = request.GET.get('start')
limit = NUM_PER_PAGE   
timeline = TIMELINE.get(useruuid, column_start=start,
column_count=limit, column_reversed=True)
tweets = TWEET.multiget(timeline.values())

和上一个例子类似,这次是从 Timeline 读取 tweet ID,不过这次我们还使用了 start 和 limit 来控制读取列的范围。这样有助于输出结果的分页。

那么,下一步呢?

希望这足够提供给你一个大致的概念。重复一下,我从代码中提取了一些例子,为了简明起见,略去了一些操作,所以现在可能是 check out 出 Twissandra 的源代码并进行下一步深入研究的好时候了。有很多功能,诸如 retweet 和 lists,都还空着没有实现,可以作为一个练习的起点。如果你已经熟悉 Python 和 Django 的话,那你可以考虑实现一下这些方法。

Cassandra 的 wiki 包含了大量的信息,而且还在不断增多,还包括一个实时更新的其他人贡献的文章与幻灯片的列表。

如果你喜欢IRC的话,你可以加入 irc.freenode.net 的 #cassandra 频道,来和那里的人聊天,他们总是热衷于提供帮助和回答问题。如果你更青睐 email 的话,cassandra-user 邮件列表上也有很多可以提供帮助的人。

[译文]理解Cassandra源代码

May 13th, 2010

原文: http://prettyprint.me/2010/05/02/understanding-cassandra-code-base/
原作者:Ran Tavory 
原文发布日期:May 2nd, 2010
译者:王旭(http://wangxu.me/blog/ , @gnawux)
翻译时间:2010年5月12日

最近我为 cassandra 添加了一些小特性,于是我花了些时间来更仔细地考察了这个系统的内部设计。诸如 embedded service 这样的特性实际上并不需要对源代码和设计的深入理解,但其他特性,比如 truncate 就需要对系统中使用的不同算法有深入的了解,如写操作时如何进行的,读操作时如何进行的,值时如何删除的(提示:他们没有……)等等。

源代码虽然不是非常长,有大约91136行,但非常密级,而且有很多算法,所以直接读这些代码对我来说并不是非常简单。我是用如下的外科手段来进行行数计数的($ cassandra/trunk $ find * -name *.java -type f -exec cat {} \;|wc -l

我写这篇文章希望可以帮助其他人能更快地阅读这些代码。我不会介绍那些基础信息,比如“什么是Cassandra”,如何部署,如何检出代码,如何编译,如何下载thrift等。我也不想介绍算法最复杂的部分,比如ae-service使用的merkle-tree如何 ,Cassandra 中不同部分都是用的 bloom filter 是什么、如何工作,以及 gossip 是如何使用的。我不认为我适于解释所有这些问题,而且在cassandra的开发者wiki上也已经介绍这些了。我要写的就是我学习cassandra的途径,以及我在这个过程中学到了什么。我没有在其他地方发现过类似文档(当我完全完成的时候,可能我会把这些写入wiki),所以我觉得这对我下次再深入新的源代码会非常有用。

最后是一个免责声明:这里仅仅是我对系统如何工作的个人理解,它们是不完整、不确切的,特此警告。注意我也只是在学习,或多或少也是Cassandra菜鸟。也请注意,Cassandra 是一个运动目标,它一直在快速开发者,任何一个代码的快照或早或晚都会发生些变化。在本文写作的时候,官方版本是 0.6.1,不过我工作在 trunk 上,这个分支将来会成为 0.7.0。

这里是我所采取的几个步骤以及我学到的东西的一个描述。

下载,配置,运行…

首先你需要下载代码并运行单元测试。如果你使用 eclipes,IDEA,netbeans,vi,emacs等等这些的话,可能还需要配置一下。这非常简单,这里有更多介绍。

阅读

接下来你需要读一些背景材料,这依赖于你想搞哪个部分。我希望理解读操作、写操作以及值如何删除,所以我把下面每个文档都看了差不多五遍。没错,每个五遍。它们包含了大量的信息,我发现每次读我都能被更多的一些细节所吸引。我先读过这些文档,然后读源代码,确定我理解了算法如何在类和方法中实现,然后再读文档,然后再读源代码,读单元测试(并用debugger运行它们)等等。这是这些文档:

http://wiki.apache.org/cassandra/ArchitectureInternals

SEDA paper

http://wiki.apache.org/cassandra/HintedHandoff

http://wiki.apache.org/cassandra/ArchitectureAntiEntropy

http://wiki.apache.org/cassandra/ArchitectureSSTable

http://wiki.apache.org/cassandra/ArchitectureCommitLog

http://wiki.apache.org/cassandra/DistributedDeletes

我还看了 Google BigTable 的论文和让人着迷的亚马逊的 Dynamo 的论文,不过这都是很久以前的事情了。它们是很好的背景材料,不过对于理解实际代码并不是必须的。

好了,读完所有这些文档,我开始知道能做什么、如何做了,但我感觉我还没有到达能写新特性的阶段。在读代码几次之后,我发现我有点晕了,还是不了解诸如“值到底是不是真的被删除了”,那个累负责哪个功能,有几个Stage,Stage之间的数据流是什么样的,还有“如何标记整个列族为删除”,这是我在truncate操作中真正想做的。

Stages

Cassandra 的操作使用的并发模型在 SEDA 论文中有介绍。这个并发模型大致是这样的,和很多其他兵法系统不同,一个操作,比如一个写操作,并不是在同一个线程中开始和结束的。相反,一个操作在一个线程中开始,之后把操作(异步)交给了另一个线程,然后再传递到下一个进程,直至完成。事实上,操作并不是在线程间流转,而是在stage间流转。操作从一个stage转向另一个。每个stage都和一个线程池相关联,这个线程池在方便的时候来执行这个操作。一些操作是 IO bound 的(译注:这里应该是CPU吧,猜的),另一些则受限于磁盘或网络,所谓“方便”取决于资源的可用性。SEDA 论文把这个过程解释得非常好(很好的文章,值得一读),简单地说你从中得到的是更高级别的并发性和更好的资源管理,资源包含 CPU、磁盘、网络等。

所以,要理解 Cassandra 的数据流,你首先需要理解 SEDA。然后你需要了解 Cassandra 中有哪些 Stage,以及这些 stage 之间数据时如何流动的。

十分幸运,作为一个起点,StageManager 类中包含了一个不完整 stage 列表:

public final static String READ_STAGE = "ROW-READ-STAGE";
public final static String MUTATION_STAGE = "ROW-MUTATION-STAGE";
public final static String STREAM_STAGE = "STREAM-STAGE";
public final static String GOSSIP_STAGE = "GS";
public static final String RESPONSE_STAGE = "RESPONSE-STAGE";
public final static String AE_SERVICE_STAGE = "AE-SERVICE-STAGE";
private static final String LOADBALANCE_STAGE = "LOAD-BALANCER-STAGE";

 

我就不具体介绍每个 stage 都负责什么了(因为我也不知道……)但我可以说大致说,ROW-READ-STAGE 在读操作中,ROW-MUTATION-STAGE 参与了写和删除操作,而 AE-SERVICE-STAGE 负责 anti-entropy (译注:整理?不知道怎么确切用中文表达了)。这不是一个完整的 stage 列表,根据你感兴趣的代码路径,用这个方法,你可以找到更多。比如,查看文件ColumnFamilyStore你可以找到更多的stage,如FLUSH-SORTER-POOL, FLUSH-WRITER-POOL 和 MEMTABLE-POST-FLUSHER。在 Cassandra 中,stage 由 ExecutorService 的实例来唯一标识,这差不多是一个线程池,他们有全大写的名字,如 MEMTABLE-POST-FLUSHER。

我画了一张混有类和stage的图来便于理解。这不是合法的UML,但我觉得这对于了解数据在系统中如何流动是个很好的方法。这不是全部类和stage的完整的图示,仅仅是我感兴趣的一部分。

cassandra-1 
[SSTableTracker], [ColumnFamilyStore]->[Memtable (memtable_)], [CommitLog|CommitLogExecutor], [DeletionService|FILEUTILS-DELETE-POOL], [StorageLoadBalancer| lb_:LB-OPERATIONS; lbOperations_:LB-TARGET], [StorageService| consistencyManager_:CONSISTENCY-MANAGER], [StageManager| READ_STAGE; MUTATION_STAGE; STREAM_STAGE; GOSSIP_STAGE; RESPONSE_STAGE; AE_SERVICE_STAGE; LOADBALANCE_STAGE; MIGRATION_STAGE]">yUML source

Debugging

可以使用一个 debugger 来读代码,运行一个单元测试是了解事情如何工作的非常棒的方法。我不是一个debugger的铁杆粉丝,但是他们有一个可取之处就是通过单步执行单元测试来学习新代码。所以我所做的证实单步执行代码中的单步执行。这非常酷。我还运行了 Hector 的单元测试,它使用 thrift 接口并运行一个嵌入的 cassandra 服务器,这个方法一针见血、界面友好而且还能学到更多东西。

类图

接下来我所做的是使用一个工具来从已有代码中提取类图。这不是非常有用。

好,我使用的工具不是很棒,但这不是最关键的问题,问题是 cassandra 的代码书写方法使得类图对于理解代码作用很有限。UML 类图对于面向对象设计非常有效。类图的必要性在于列出类、成员以及它们的关系。比如类 A 是类 B 的一个列表,这样通过 UML 类图可以看出 A 是 B 的聚合,而且仅仅通过类图就可以学到很多。比如一架飞机有很多乘客。

Cassandra 是一个拥有坚实算法后台和优秀性能的系统,但是,老实说,依我之见,从好的面向对象实践的视角来看,它可不是一个很好的研究案例……它的类包含很多静态方法和成员,而且在很多地方,你可以看到一个类调用另一个类的静态方法。纯粹的C风格,所以我发现类图尽管从类的可视化以及类之间的关系方面有些帮助,但并不是非常有用。

我放弃了类图,继续进入下一种兔——序列图。

序列图

序列图非常适于实体之间的交互的抽象和可视化。这里,一个实例可能是一个类,一个 STAGE 或一个 thrift 客户端。很幸运,使用序列图,你不必太专注于序列图里实体的类型,你只需要把他们都表示为 actor 就行了(至少我觉得这么做就够了,希望UML大神们原谅)。

下面的图通过运行 Hector 的单元测试并使用一个(单节点)嵌入式 Cassandra 服务器得到。这个序列图并不很通用,它仅仅描述了一种可能的执行路径,而实际上可能有很多种,但我尼克让它们更简单一些,尽管有点不太精确。

我使用了一个简单的在线序列图编辑器(http://www.websequencediagrams.com)来生成他们。

读操作:

cassandra-2

写操作:

cassandra-3

Table is a Keyspace

最后提示:作为 Cassandra 的用户我应该使用 Keyspace, ColumnFamily, Column 这些名词。不过,代码中使用了 Table 这个名词。啥是 Table 呢?……原来,Table实际上就是 Keyspace…… 就是一个提示,仅此而已。

研究代码是一项艰巨而有成就感的工作,我希望这篇文章帮助你也有个好的起点,快点跑起来。

HBase vs Cassandra: 我们迁移系统的原因

March 25th, 2010

原文: http://ria101.wordpress.com/2010/02/24/hbase-vs-cassandra-why-we-moved/
原作者:Dominic Williams
原文发布日期:February 24, 2010 at 7:27 pm
译者:王旭(http://wangxu.me/blog/ , @gnawux)
翻译时间:2010年3月21-25日

我的团队近来正在忙于一个全新的产品——即将发布的网络游戏 www.FightMyMonster.com。这让我们得以奢侈地去构建一个全新的 NOSQL 数据库,也就是说,我们可以把恐怖的 MySQL sharding 和昂贵的可伸缩性抛在脑后了。最近有很多人一直在问,为什么我们要把注意力从 HBase 上转移到 Cassandra 上去。我确认,确实有这样的变化,实际上我们基本上已经把代码移植到了 Cassandra 上了,这里我将给出解释。

为了那些不熟悉 NOSQL 的读者,后面的其他文章中,我会介绍为什么我们将会在未来几年中看到地震式的从 SQL 到 NOSQL 的迁移,这正和向云计算的迁移一样重要。后面的文章还会尝试解释为什么我认为 NOSQL 可能会是贵公司的正确选择。不过本文我只是解释我们选择 Cassandra 作为我们的 NOSQL 解决方案的选择。

免责声明——如果你正在寻找一个捷径来决定你的系统选择,你必须要明白,这可不是一个详尽而严格的比较,它只是概述了另一个初创团队在有限时间和资源的情况下的逻辑。

Cassandra 的血统是否预言了它的未来

我最喜欢的一个工程师们用来找 bug 的谒语是“广度优先而非深度优先”。这可以可能对那些解决技术细节的人来说很恼人,因为它暗示着如果他们只是看看的话,解决方法就会简单很多(忠告:只对那些能够原谅你的同事说这个)。我造出这个谒语的原因在于,我发现,软件问题中,如果我们强迫我们自己在进入某行代码的细节层面之前,先去看看那些高层次的考虑的话,可以节省大量时间。

所以,在谈论技术之前,我在做 HBase 和 Cassandra 之间的选择问题上先应用一下我的箴言。我们选择切换的技术结论可能已经可以预测了:Hbase和Cassandra有着迥异的血统和基因,而我认为这会影响到他们对我们的业务的适用性。

严格的说,Hbase 和它的支持系统源于著名的 Google BigTable 和 Google 文件系统设计(GFS 的论文发于 2003 年,BigTable 的论文发于 2006 年)。而 Cassandra 则是最近 Facebook 的数据库系统的开源分支,她在实现了 BigTable 的数据模型的同时,使用了基于 Amazon 的 Dynamo 的系统架构来存储数据(实际上,Cassandra 的最初开发工作就是由两位从 Amazon 跳槽到 Facebook 的 Dynamo 工程师完成的)。

在我看来,这些不同的历史也导致Hbase更加适合于数据仓库、大型数据的处理和分析(如进行Web页面的索引等),而 Cassandra 则更适合于实时事务处理和提供交互型数据。要进行系统研究来证明这个观点超出了本文的范畴,但我相信你在考虑数据库的时候总能发现这个差异的存在。

注意:如果你在寻找一个简单的证明,你可以通过主要 committer 的关注点来进行验证:大部分 HBase 的 committer 都为 Bing 工作(M$ 去年收购了他们的搜索公司,并允许他们在数月之后继续提交开源代码)。与之对应,Cassandra 的主要 committer 来自 Rackspace,用来可以自由获得的支持先进的通用的 NOSQL 的解决方案,用来和 Google, Yahoo, Amazon EC2 等提供的那些锁定在专有的 NOSQL 系统的方案相抗衡。

Malcolm Gladwell 会说只是根据这些背景的不同就可以简单地选择了 Cassandra。不过这是小马过河的问题。但当然,闭着眼睛就进行一个商业选择是相当困难的……

哪个 NOSQL数据库风头更劲?

另一个说服我们转向 Cassandra 的原因是我们社区中的大风向。如你所知,软件平台行业里,大者恒大——那些被普遍看好的平台,会有更多人聚集在这个平台周围,于是,从长远看,你可以得到更好的生态系统的支持(也就是说,大部分支持的软件可以从社区中获得,也有更多的开发者可以雇佣)。

如果从 HBase 开始时,我的印象就是它后面有巨大的社区力量,但我现在相信,Cassandra 更加强大。最初的印象部分来源于 StumpleUpon 和 Streamy 的两位 CTO 的两个非常有说服力的出色的讲演,他们是 Web 行业中两个在 Cassandra 成为一个可选系统之前的 HBase 的两个重要的贡献者,同时也部分来源于快速阅读了一篇名为“HBase vs Cassandra: NoSQL 战役!”的文章(大部分内容都被广泛证实了)。

势头是很难确证的,你不得不自己进行研究,不过我可以找到的一个重要的标志是 IRC 上的开发者动向。如果你在 freenode.org 上比较 #hbase 和 #cassandra 的开发这频道,你会发现 Cassandra 差不多在任何时候都有两倍的开发者在线。

如果你用考虑 HBase 一般的时间来考察 Cassandra,你就能发现 Cassandra 的背后确实有非常明显的加速势头。你可能还会发现那些逐渐出现的鼎鼎大名,如 Twitter,他们也计划广泛使用 Cassandra(这里)。

注:Cassandra 的网站看起来比 HBase 的好看多了,但认真的说,这可能不仅是市场的趋势。继续吧。

深入到技术部分: CAP 和 CA 与 AP 的神话

对于分布式系统,有个非常重要的理论(这里我们在讨论分布式数据库,我相信你注意到了)。这个理论被称为 CAP 理论,由 Inktomi 的 联合创始人兼首席科学家 Eric Brewer 博士提出。

这个理论说明,分布式(或共享数据)系统的设计中,至多只能够提供三个重要特性中的两个——一致性、可用性和容忍网络分区。简单的说,一致性指如果一个人向数据库写了一个值,那么其他用户能够立刻读取这个值,可用性意味着如果一些节点失效了,集群中的分布式系统仍然能继续工作,而容忍分区意味着,如果节点被分割成两组无法互相通信的节点,系统仍然能够继续工作。

Brewer教授是一个杰出的人物,许多开发者,包括 HBase 社区的很多人,都把此理论牢记在心,并用于他们的设计当中。事实上,如果你搜索线上的关于 HBase 和 Cassandra 比较的文章,你通常会发现,HBase 社区解释他们选择了 CP,而 Cassandra 选择了 AP ——毫无疑问,大多数开发者需要某种程度的一致性 (C)。

不过,我需要请你注意,事实上这些生命基于一个不完全的推论。CAP 理论仅仅适用于一个分布式算法(我希望 Brewer 教授可以统一)。但没有说明你不能设计一个系统,在其中的各种操作的底层算法选择上进行这种。所以,在一个系统中,确实一个操作职能提供这些特性中的两个,但被忽视的问题是在系统设计中,实际是可以允许调用者来选择他们的某个操作时需要哪些特性的。不仅如此,现实世界并不简单的划分为黑白两色,所有这些特性都可以以某种程度来提供。这就是 Cassandra。

这点非常重要,我重申:Cassandra 的优点在于你可以根据具体情况来选择一个最佳的折衷,来满足特定操作的需求。Cassandra 证明,你可以超越通常的 CAP 理论的解读,而世界仍然在转动。

我们来看看两种不同的极端。比如我必须从数据库中读取一个要求具有很高一致性的值,也就是说,我必须 100%保证能够读取到先前写入的最新的内容。在这种情况下,我可以通过指定一致性水平为“ALL”来从 Cassandra 读取数据,这时要求所有节点都有数据的一致的副本。这里我们不具有对任何节点失效和网络分裂的容错性。在另一个极端的方面,如果我不特别关心一致性,或仅仅就是希望最佳性能,我可以使用一致性级别“ONE”来访问数据。在这种情况下,从任意一个保存有这个副本的节点获取数据都可以——即使数据有三个副本,也并不在意其他两个有副本的节点是否失效或是否有不同,当然,这种情况下我们读到的数据可能不是最新的。

不仅如此,你不必被迫生活在黑白世界中。比如,在我们的一个特定的应用中,重要的读写操作通常使用“QUORUM”一致性级别,这意味着大部分存有此数据的节点上的副本是一致的——我这里是个简要描述,具体写你的 Cassandra 程序之前最好还是仔细研究一下。从我们的视角看,这这提供了一个合理的节点失效与网络分裂的耐受性,同时也提供了很高的一致性。而在一般情况下,我们使用前面提到的“ONE”一致性级别,者可以提供最高的性能。就是这样。

对我们来说,这是 Cassandra 的一个巨大的加分项目。我们不仅能轻易地调整我们的系统,也可以设计它。比如,当一定数量的节点失效或出现网络连接故障时,我们的大部分服务仍然可以继续工作,只有那些需要数据一致性的服务会失效。HBase并没有这么灵活,它单纯地追求系统的一个方面(CP),这让我再次看到了 SQL 开发者和查询优化人员们之间的那道隔阂——有些事情最好能够超越它,HBase!

In our project then, Cassandra has proven by far the most flexible system, although you may find your brain at first loses consistency when considering your QUORUMs.在我们的项目之后,卡桑德拉已被证明是迄今为止最灵活的系统,虽然你可能发现一致性第一失去你的大脑在考虑您的法定人数。

在我们的项目中,Cassandra 已经证明了它是有史以来最灵活的系统,虽然你可能在对这个问题进行投票(QUORUM)的时候发现的大脑失去了一致性。

什么时候单体会比模块化强?

Cassandra 和 HBase 的一个重要区别是, Cassandra 在每个节点是是一个单 Java 进程,而完整的 HBase 解决方案却由不同部分组成:有数据库进程本身,它可能会运行在多个模式;一个配置好的 hadoop HDFS 分布式文件系统,以及一个 Zookeeper 系统来协调不同的 HBase 进程。那么,这是否意味着 HBase 有更好的模块化结构呢?

虽然 HBase 的这种架构可能确实可以平衡不同开发团队的利益,在系统管理方面,模块化的 HBase 却无法视为一个加分项目。事实上,特别是对于一些小的初创公司,模块化倒是一个很大的负面因素。

HBase的下层相当复杂,任何对此有疑惑的人应该读读 Google 的 GFS 和 BigTable 的论文。即使是在一个单一节点的伪分布式模式下来架设 HBase 也很困难——事实上,我曾经费力写过一篇快速入门的教程(如果你要试试HBase的话看看这里)。在这个指南里你可以看到,设置好 HBase 并启动它实际包含了两个不同系统的手工设置:首先是 hadoop HDFS,然后才是 HBase 本身。

然后,HBase 的配置文件本身就是个怪兽,而你的设置可能和缺省的网络配置有极大的不同(在文章里我写了两个不同的Ubuntu的缺省网络设置,以及 EC2 里易变的 Elastic IP 和内部分配的域名)。当系统工作不正常的时候,你需要查看大量的日志。所有的需要修复的东西的信息都在日志里,而如果你是一个经验丰富的管理员的话,就能发现并处理问题。

但是,如果是在生产过程中出现问题,而你又没有时间耐心查找问题呢?如果你和我们一样,只有一个小的开发团队却有远大的目标,没有经历去 7*24 的进行系统监控管理会怎么样呢?

严肃地说,如果你是一个希望学习 NoSQL 系统的高级 DB 管理员的话,那么选择 HBase。这个系统超级复杂,有灵巧双手的管理员肯定能拿到高薪。

但是如果你们是一个向我们一样尽力去发现隧道尽头的小团队的话,还是等着听听别的闲话吧

胜在 Gossip

Cassandra 是一个完全对称的系统。也就是说,没有主节点或像 HBase 里的 region server 这样的东西——每个节点的角色是完全一样的。不会有任何特定的节点或其他实体来充当协调者的角色,集群中的节点使用称为 “Cossip” 的纯 P2P 通信协议来协调他们的行为。

对 Gossip 的详细描述和使用 Gossip 的模型超过了本文的内容,但 Cassandra 所采用的 P2P 通信模型都是论证过的,比如发现节点失效的消息传播到整个系统的时间,或是一个客户应用的请求被路由到保存数据的节点的时间,所有这些过程所消耗的时间都毫无疑问的非常的短。我个人相信,Cassandra 代表了当今最振奋的一种 P2P 技术,当然,这和你的 NOSQL 数据库的选择无关。

那么,这个基于 Gossip 的架构究竟给 Cassandra 用户带来什么显示的好处呢。首先,继续我们的系统管理主体,系统管理变得简单多了。比如,增加一个新节点到系统中就是启动一个 Cassandra 进程并告诉它一个种子节点(一个已知的在集群中的节点)这么简单。试想当你的分布式集群可能运行在上百个节点的规模上的时候,如此轻易地增加新节点简直是难以置信。更进一步,当有什么出错的时候,你不需要考虑是哪种节点出了问题——所有节点都是一样的,这让调试成为了一个更加易于进行且可重复的过程。

第二,我可以得出结论,Cassandra 的 P2P 架构给了它更好的性能和可用性。这样的系统中,负载可以被均衡地三步倒各个节点上,来最大化潜在的并行性,这个能力让系统面临网络分裂和节点失效的时候都能更加的无缝,并且节点的对称性防止了 HBase 中发现的那种在节点加入和删除时的暂时性的性能都懂(Cassandra 启动非常迅速,并且性能可以随着节点的加入而平滑扩展)。

如果你想寻找更多更多的证据,你会对一个原来一直关注 hadoop 的小组(应该对 HBase 更加偏爱)的报告很感兴趣……

一份报告胜过千言万语。我是指图表

Yahoo!进行的第一个 NOSQL 系统的完整评测。研究似乎证实了 Cassandra 所享有的性能优势,从图表上看,非常倾向于 Cassandra。

目前这些论文还是草稿,你可以从这里找到这些论文:
http://www.brianfrankcooper.net/pubs/ycsb-v4.pdf
http://www.brianfrankcooper.net/pubs/ycsb.pdf

注意:这份报告中 HBase 仅在对一个范围的记录进行扫描这一项上优于 Cassandra。虽然 Cassandra 团队相信他们可以很快达到 HBase 的时间,但还是值得指出,在通常的 Cassandra 配置中,区间扫描几乎是不可能的。我建议你可以无视这一点,因为实际上你应该在 Cassandra 上面来实现你自己的索引,而非使用区间扫描。如果你对区间扫描和在 Cassandra 中存储索引相关问题有兴趣,可以看我的这篇文章

最后一点: 这篇文章背后的 Yahoo!研究团队正尝试让它们的评测应用通过法律部门的评估,并将它发布给社区。如果他们成功的话,我当然希望他们成功,我们将能够看到一个持续的竞争场面,不论 HBase 还是 Cassandra 无疑都会进一步提高他们的性能。

锁和有用的模块性

毫无疑问,你会从 HBase 阵营听到这样的声音:HBase 的复杂结构让它可以提供 Cassandra 的 P2P 架构无法提供的东西。其中一个例子可能就是 Hbase 提供给开发者行锁机制,而 Cassandra 则没有(在 HBase 中,因为数据副本发生在 hadoop 底层,行锁可以由 region server 控制,而在 Cassandra 的 P2P 架构中,所有节点都是平等的,所以也就没有节点可以像一个网管囊样负责锁定有副本的数据)。

不够,我还是把这个问题返回到关于模块化的争论中,这实际是对 Cassandra 有理的。Cassandra 通过在对称节点上分布式存储数据来实现了 BigTable 的数据模型。它完整地实现了这些功能,而且是以最灵活和高性能的方式实现的。但如果你需要锁、事务和其它功能的话,这些可以以模块的方式添加到你的系统之中——比如,我们发现我们可以使用 Zookeeper 和相关的工具来很简单地为我们的应用提供可扩展的锁功能(对于这个功能,Hazelcast 等系统可能也可以实现这个功能,虽然我们没有进行研究)。

通过为一个窄领域目的来最小化它的功能,对我来说,Cassandra 的设计达到了它的目的——比如前面指出可配置的 CAP 的折衷。这种模块性意味着你可以依据你的需求来构建一个系统——需要锁,那么拿来 Zookeeper,需要存储全文索引,拿来 Lucandra ,等等。对于我们这样的开发者来说,这意味着我们不必部署复杂度超出我们实际需要的系统,给我们提供了更加灵活的构建我们需要的应用的终极道路。

MapReduce,别提 MapReduce!

Cassandra 做的还不够好的一件事情就是 MapReduce!对于不精通此项技术同学简单的解释一句,这是一个用于并行处理大量数据的系统,比如从上百万从网络上抓取的页面提取统计信息。MapReduce 和相关系统,比如 Pig 和 Hive 可以和 HBase 一起良好协作,因为它使用 HDFS 来存储数据,这些系统也是设计用来使用 HDFS 的。如果你需要进行这样的数据处理和分析的话,HBase 可能是你目前的最佳选择。

记住,这就像小马过河!

因此,我停止了对 Cassandra  的优点的赞美,实际上,HBase 和 Cassandra 并不一定是一对完全的竞争对手。虽然它们常常可以用于同样的用途,和 MySQL 和 PostgreSQL 类似,我相信在将来它们将会成为不同应用的首选解决方案。比如,据我所知 StumbleUpon 使用了 HBase 和 hadoop MapReduce 技术,来处理其业务的大量数据。Twitter 现在使用 Cassandra 来存储实时交互的社区发言,可能你已经在某种程度上使用它了。

作为一个有争议的临别赠言,下面我们进入下一个话题。

注意:在继续下一个小节之前,我要指出,Cassandra 在 0.6 版本会有 hadoop 支持,所以 MapReduce 整合能获得更好的支持。

兄弟,我不能失去数据…

作为先前 CAP 理论争议的一个可能结果,可能有这样的印象,HBase 的数据似乎比 Cassandra 中的数据更安全。这是我希望揭露的最后一个关于 Cassandra 的秘密,当你写入新数据的时候,它实际上立刻将它写入一个将要存储副本的仲裁节点的 commit log 当中了,也被复制到了节点们的内存中。这意味着如果你完全让你的集群掉电,只可能会损失极少数据。更进一步,在系统中,通过使用 Merkle tree 来组织数据的过分不一致(数据熵),更加增加了数据的安全性:)

事实上,我对 HBase 的情况并不是非常确切——如果能有更细节的情况,我回尽快更新这里的内容的——但现在我的理解是,因为 hadoop 还不支持 append,HBase 不能有效地将修改的块信息刷入 HDFS (新的对数据变化会被复制为多个副本并永久化保存)。这意味着会有一个更大的缺口,你最新的更改是不可见的(如果我错了,可能是这样,请告诉我,我回修正本文)。

所以,尽管希腊神话中的 Cassandra 非常不幸(译注:Cassandra 是希腊神话里,特洛伊的那个可怜的女先知的名字,如果你不知道详情的话,可以参考wiki),但你的 Cassandra 中的数据不会有危险。

注意:Wade Amold 指出, hadoop .21 很快就会发布,其中将会解决 HBase 的这个问题。

[译文] Observers: 让ZooKeeper更具可伸缩性

December 22nd, 2009

原文: http://www.cloudera.com/blog/2009/12/15/observers-making-zookeeper-scale-even-further/
Tuesday, December 15th, 2009 at 10:30 am by Henry Robinson
译文: 王旭(http://wangxu.me, @gnawux)2009年12月16,21日

看过我们之前相关文章的读者都知道,ZooKeeper是一个分布式协作服务,用于实现锁和并发事务排队等协作原语。ZooKeeper 的一个优势是可伸缩性(Scalability)。五台或七台机器集群可以满足很多大型应用的需求。

最近我们给ZooKeeper增加了一个新的特性,进一步增强了它可伸缩性——一种新的称为 Observers 的服务器。在这篇文章中,我想要介绍一下添加这个特征的动机,并解释这个服务器如何帮助我们的系统更具有可伸缩性。可伸缩性对不同的人意味着不同的事情,而在这里是说,如果我们的工作负载可以通过给系统分配更多的资源来分担,那么这个系统就是可伸缩的;一个不可伸缩的系统却无法通过增加资源来提升性能,甚至会在工作负载增加时,性能急剧下降。

要了解 Observers 为何能影响 ZooKeeper 的可伸缩性,我们需要首先了解一下这个服务时如何工作的。宽泛地说,ZooKeeper 集群上的任何操作不是读操作就是写操作。ZooKeeper 确保所有读和写操作在所有客户端看来,都具有完全相同的顺序,这样他们就不会为操作的顺序而疑惑了。

在提供强一致性保障的同时,ZooKeeper同时给出高可用性承诺,这可以被简单地解释为它可以在多台服务器失效的情况下仍然为客户端提供服务。ZooKeeper使用一个传统的手段来达到可用性——通过将数据读写分布到几台机器上来实现,这样如果一台失效了,其它的可以接管它的服务,而无需让客户端更聪明。

然而,一致性和可用性这两个属性是很难同时达到的,目前,ZooKeeper 必须确保集群中的每个副本都对读写操作是顺序性的。它通过一个一致性协议来达到这一目标。简单地说,这个协议由一个选定的领导者将新操作高速其他服务器,所有节点投票支持并反馈给领导。一旦领导节点收集到过半数的投票,它就认为投票已经获得了通过,并将进一步消息传送给服务器们,以使他们可以继续工作,将操作提交到内存中。

这个从始至终的数据流如下图所示。客户进程将一个值提交给它连接的服务器。服务器将消息转送给领导节点,它发起这个一致性协议,一旦最初的服务器从领导节点得到结果,它就可以返回给用户了。

Simplified flow of a ZooKeeper write request

图1:简化的写请求工作流

Observers 的需求源于 ZooKeeper 服务器在上述协议中实际扮演了两个角色。它们从客户端接受连接与操作请求,之后对操作结果进行投票。这两个职能在 ZooKeeper集群扩展的时候彼此制约。如果我们希望增加 ZooKeeper 集群服务的客户数量(我们经常考虑到有上万个客户端的情况),那么我们必须增加服务器的数量,来支持这么多的客户端。然而,从一致性协议的描述可以看到,增加服务器的数量增加了对协议的投票部分的压力。领导节点必须等待集群中过半数的服务器响应投票。于是,节点的增加使得部分计算机运行较慢,从而拖慢整个投票过程的可能性也随之提高,投票操作的会随之下降。这正是我们在实际操作中看到的问题——随着 ZooKeeper 集群变大,投票操作的吞吐量会下降。

所以,这让我们不得不在增加客户节点数量的期望和我们希望保持较好吞吐性能的期望间进行权衡。要打破这一耦合关系,我们引入了不参与投票的服务器,称为 Observers。 Observers 可以接受客户端的连接,将写请求转发给领导节点。但是,领导节点不会要求 Observers 参加投票。相反,Observers 不参与投票过程,仅仅在上述第3歩那样,和其他服务节点一起得到投票结果。

这个简单的扩展给 ZooKeeper 的可伸缩性带来了全新的镜像。我们现在可以加入很多 Observers 节点,而无须担心严重影响写吞吐量。规模伸缩并非无懈可击——协议中的一歩(通知阶段)仍然与服务器的数量呈线性关系。但是,这里的穿行开销非常低。我们可以认为在通知服务器阶段的开销无法成为主要瓶颈。

Observers Write Throughput Benchmark

图2: Observers 写吞吐量 Benchmark

图2 显示了一个简单评测的结果。纵轴是我能够从一个单一的客户端发出的每秒钟同步写操作的数量(一个调优的 ZooKeeper 可以得到更好的每秒钟操作数——这里我们更感兴趣的是相对值)。横轴是 ZooKeeper 集群的尺寸。蓝色的是每个服务器都是 voting 服务器的情况,而绿色的则只有三个是 voting 服务器,其它都是 Observers。图中显示,我们扩充 Observers,写性能几乎可以保持不便,但如果同时扩展 voting 节点的数量的话,性能会明显下降。显然 Observers 是有效的。

Observers 同样提升读性能的可伸缩性

增加客户端的数量是 Observers 的一个重要用例,但是实际上它们还给集群带来很多其它的好处。

作为一个优化,ZooKeeper 服务器可以直接读取它们的本地数据存储,而无需经过投票过程,这面临一定的“时光旅行”风险,可能在读到新值之后又读到老值,但这只在服务器故障时才会发生。事实上,在这种情况下客户端可以请求一个 ‘sync’ 操作来保证下一个值是最新的。

因此,在大量读操作的工作负载下,Observers 是个巨大的性能提升。写操作直接进入标准的投票路径,这样,与客户端可扩展性类似,提高投票服务器数量来承担读操作会影响写性能。Observers 允许我们将读性能和写性能分开。这适用于 ZooKeeper 的很多应用场景,大部分客户端很少写,但经常读。

Observers 提供了广域网能力

Observers 还能做更多。Observers 对于跨广域网连接的客户端来说是很好的候选方案。这有三个原因。为了获得很好的读性能,有必要让客户端离服务器尽量近,这样往返时延不会太高。然而,将 ZooKeeper 集群分散到两个集群是非常不可取的设计,因为良好配置的 ZooKeeper 应该让投票服务器间用低时延连接互联——否则我们将会遇到上面提到的低反映速度的问题。

而 Observers 可以被部署在需要访问 ZooKeeper 的任意数据中心中。这样,投票协议不会受到数据中心间链路的高时延的影响,性能得到提升。投票过程中 Observers 和领导节点间的消息远少于投票服务器和领导节点间的消息。这有助于在远程数据中心高写负载的情况下降低带宽需求。

最后,由于Observers即使失效也不会影响到投票集群,这样如果数据中心间链路发生故障,不会影响到服务本身的可用性。这种故障的发生概率要远高于一个数据中心中机架间的连接的故障概率,所以不依赖于这种链路是个优点。

如何开始使用 Observers

Observers 还没有成为某个 ZooKeeper release 的一部分,所以要使用它,你需要从 Subversion trunk 获取源代码。

下面的内容提取自 Observers 用户手册,可以在源代码的 docs/zooKeeperObservers.html 文件中看到。

如何使用 Observers

注意,在ZOOKEEPER-578 解决之前,你必须在每个服务器的配置文件中设置 electionAlg=0 。否则当你启动服务的时候会抛出一个异常。

原因:因为 Observers 并不参与领导节点的选举,它们依赖于投票 Followers 来获知领导的变动。目前,只有基本选举算法启动一个线程来响应 Observers 确定当前领导的请求。其他 JIRA 上的工作将会让其他所有的领导选举协议都支持这一功能。

设置 ZooKeeper 使用 Observers 非常简单,只需要在配置文件中有两处改动。首先是每个 Observer 的配置文件中都要有这么一行:

peerType=observer

这行让服务器作为一个 Observer 来工作。之后,在每个服务器配置文件中,你必须在服务器定义行给每个 Observer 加入 :o bserver  。比如:

server.1:localhost:2181:3181:observer

这让每个其他服务器知道 server.1 是一个 Observer,就不会期望它进行投票了。这就是要加入一个 Observer 的时候,所有你需要做的配置。现在可以将它作为一个正常的 Follower 来看待了。可以这么试试:

bin/zkCli.sh -server localhost:2181

这里 localhost:2181 是 Observer 在每个配置文件中指定的主机名和端口号。你应该看到命令行提示了,这时就可以查询 Zookeeper 服务了。

下一步工作

Observers 特性还有很多工作要做。短期内,我们将致力于让 Observers 与 ZooKeeper 中的所有领导选举算法完全兼容 —— 我们希望这个可以在未来几天内完成。长期看,我们希望能研究一下进行性能优化,比如基于 Observers 的集群的批量和离线读取,来更好的利用 Observers 不像一般 ZooKeeper 服务器一样严格要求时延的好处。

我们希望 Observers 能进入明年年初的 ZooKeeper 3.3.0。我们会很高兴能听到你的反馈,不管是在邮件列表里还是直接发送email。ZooKeeper 长期招募贡献者,我们有足够多的有趣的问题来解决,所以,如果你想加入的话请联系我,我回很高兴能帮你开始的。

[译文] NOSQL们背后的共有原则

December 20th, 2009

原文: http://natishalom.typepad.com/nati_shaloms_blog/2009/12/the-common-principles-behind-the-nosql-alternatives.html
Posted by Nati Shalom at 12:01 PM Dec 15, 2009
译文: 王旭(http://wangxu.me , @gnawux)2009年12月16/19日

几个星期之前,我写了一篇文章描述了常被称作 NOSQL 的一类新型数据库的背后驱动。几个星期之前,我在Qcon上发表了一个演讲,其中,我介绍了一个可伸缩(scalable)的 twitter 应用的构建模式,在我们的讨论中,一个显而易见的问题就是数据库的可扩展性问题。要解答这个问题,我试图寻找隐藏在各种 NOSQL 之后的共有模式,并展示他们是如何解决数据库可扩展性问题的。在本文中,我将尽力勾勒出这些共有的原则。

nosql

实现们的共有原则

假设失效是必然发生的

与我们先前通过昂贵硬件之类的手段尽力去避免失效的手段不同,NOSQL实现都建立在硬盘、机器和网络都会失效这些假设之上。我们需要认定,我们不能彻底阻止这些时效,相反,我们需要让我们的系统能够在即使非常极端的条件下也能应付这些失效。Amazon S3 就是这种设计的一个好例子。你可以在我最近的文章 Why Existing Databases (RAC) are So Breakable! 中找到进一步描述。哪里,我介绍了一些来自 Jason McHugh 的讲演的面向失效的架构设计的内容(Jason 是在 Amazon 做 S3 相关工作的高级工程师)。

对数据进行分区

通过对数据进行分区,我们最小化了失效带来的影响,也将读写操作的负载分布到了不同的机器上。如果一个节点失效了,只有该节点上存储的数据受到影响,而不是全部数据。

保存同一数据的多个副本

大部分 NOSQL 实现都基于数据副本的热备份来保证连续的高可用性。一些实现提供了 API,可以控制副本的复制,也就是说,当你存储一个对象的时候,你可以在对象级指定你希望保存的副本数。在 GigaSpaces,我们还可以立即复制一个新的副本到其他节点,甚至在必要时启动一台新机器。这让我们不比在每个节点上保存太多的数据副本,从而降低总存储量以节约成本。

你还可以控制副本复制是同步还是异步的,或者两者兼有。这决定了你的集群的一致性、可用性与性能三者。对于同步复制,可以牺牲性能保障一致性和可用性(写操作之后的任意读操作都可以保证得到相同版本的数据,即使是发生失效也会如此)。而最为常见的 GigaSpaces 的配置是同步副本到被分界点,异步存储到后端存储。

动态伸缩

要掌控不断增长的数据,大部分 NOSQL 实现提供了不停机或完全重新分区的扩展集群的方法。一个已知的处理这个问题的算法称为一致哈希。有很多种不同算法可以实现一致哈希。

一个算法会在节点加入或失效时通知某一分区的邻居。仅有这些节点受到这一变化的影响,而不是整个集群。有一个协议用于掌控需要在原有集群和新节点之间重新分布的数据的变换区间。

另一个(简单很多)的算法使用逻辑分区。在逻辑分区中,分区的数量是固定的,但分区在机器上的分布式动态的。于是,例如有两台机器和1000个逻辑分区,那么每500个逻辑分区会放在一台机器上。当我们加入了第三台机器的时候,就成了每 333 个分区放在一台机器上了。因为逻辑分区是轻量级的(基于内存中的哈希表),分布这些逻辑分区非常容易。

第二种方法的优势在于它是可预测并且一致的,而使用一致哈希方法,分区之间的重新分布可能并不平稳,当一个新节点加入网络时可能会消耗更长时间。一个用户在这时寻找正在转移的数据会得到一个异常。逻辑分区方法的缺点是可伸缩性受限于逻辑分区的数量。

更进一步的关于这一问题的讨论,建议阅读 Ricky Ho 的文章 NOSQL Patterns

查询支持

在这个方面,不同的实现有相当本质的区别。不同实现的一个共性在于哈希表中的 key/value 匹配。一些市县提供了更高级的查询支持,比如面向文档的方法,其中数据以 blob 的方式存储,关联一个键值对属性列表。这种模型是一种无预定义结构的(schema-less)存储,给一个文档增加或删除属性非常容易,无需考虑文档结构的演进。而 GigaSpaces 支持很多 SQL 操作。如果 SQL查询没有指出特定的简直,那么这个查询就会被并行地 map 到所有的节点去,由客户端完成结果的汇聚。所有这些都是发生在幕后的,用户代码无需关注这些。

使用 Map/Reduce 处理汇聚

Map/Reduce 是一个经常被用来进行复杂分析的模型,经常会和 Hadoop 联系在一起。 map/reduce 常常被看作是并行汇聚查询的一个模式。大部分 NOSQL 实现并不提供 map/reduce 的内建支持,需要一个外部的框架来处理这些查询。对于 GigaSpaces 来说,我们在 SQL 查询中隐含了对 map/reduce 的支持,同时也显式地提供了一个称为 executors 的 API 来支持 map/reduce。在质疑模型中,你可以将代码发送到数据所在地地方,并在该节点上直接运行复杂的查询。

这方面的更多细节,建议阅读 Ricky Ho 的文章 Query Processing for NOSQL DB

基于磁盘的和内存中的实现

NOSQL 实现分为基于文件的方法和内存中的方法。有些实现提供了混合模型,将内存和磁盘结合使用。两类方法的最主要区别在于每 GB 成本和读写性能。

最近,斯坦福的一项称为“The Case for RAMCloud”的调查,对磁盘和内存两种方法给出了一些性能和成本方面的有趣的比较。总体上说,成本也是性能的一个函数。对于较低性能的实现,磁盘方案的成本远低于基于内存的方法,而对于高性能需求的场合,内存方案则更加廉价。

内存云的显而易见的缺点就是单位容量的高成本和高能耗。对于这些指标,内存云会比纯粹的磁盘系统差50到100倍,比使用闪存的系统差5-10倍(典型配置情况和指标参见参考文献[1])。内存云同时还比基于磁盘和闪存的系统需要更多的机房面积。这样,如果一个应用需要存储大量的廉价数据,不需要高速访问,那么,内存云将不是最佳选择。
然而,对于高吞吐量需求的应用,内存云将更有竞争力。当使用每次操作的成本和能量作为衡量因素的时候,内存云的效率是传统硬盘系统的 100 到 1000 倍,是闪存系统的 5-10 倍。因此,对于高吞吐量需求的系统来说,内存云不仅提供了高性能,也提供了高能源效率。同时,如果使用 DRAM 芯片提供的低功耗模式,也可以降低内存云的功耗,特别是在系统空闲的时候。此外,内存云还有一些缺点,一些内存云无法支持需要将数据在多个数据中心之间进行数据复制。对于这些环境,更新的时延将主要取决于数据中心间数据传输的时间消耗,这就丧失了内存云的时延方面的优势。此外,跨数据中心的数据复制会让内存云数据一致性更能难保证。不过,内存云仍然可以在夸数据中心的情况下提供低时延的读访问。

仅仅是炒作?

近来我见到的最多的问题就是 “NOSQL 是不是就是炒作?” 或 “NOSQL 会不会取代现在的数据库?”

我的回答是——NOSQL 并非始于今日。很多 NOSQL 实现都已经存在了十多年了,有很多成功案例。我相信有很多原因让它们在如今比以往更受欢迎了。首先是由于社会化网络和云计算的发展,一些原先只有很高端的组织才会面临的问题,如今已经成为普遍问题了。其次,已有的方法已经被发现无法跟随需求一起扩展了。并且,成本的压力让很多组织需要去寻找更高性价比的方案,并且研究证实基于普通廉价硬件的分布式存储解决方案甚至比现在的高端数据库更加可靠。(进一步阅读)所有这些导致了对这类“可伸缩性优先数据库”的需求。这里,我引用 AWS团队的接触工程师、VP, James Hamilton 在他的文章 One Size Does Not Fit All 中的一段话:

“伸缩性优先应用是那些必须具备无限可伸缩性的应用,能够不受限制的扩展比更丰富的功能更加重要。这些应用包括很多需要高可伸缩性的网站,如 Facebook, MySpace, Gmail, Yahoo 以及 Amazon.com。有些站点实际上使用了关系型数据库,而大部分实际上并未使用。这些服务的共性在于可扩展性比功能公众要,他们无法泡在一个单一的 RDBMS 上。”

总结一下——我认为,现有的 SQL 数据库可能不会很快淡出历史舞台,但同时它们也不能解决世上的所有问题。NOSQL 这个名词现在也变成了 Not Only SQL,这个变化表达了我的观点。

参考:

Switch to our mobile site