深入分析美团的Ursa分布式存储系统
1.ursa
云硬盘对iaas云计算平台有至关重要的作用,几乎已成为必备组件,如亚马逊的ebs(elastic block store)、阿里云的盘古、openstack中的cinder等。云硬盘可为云计算平台带来许多优良特性,如更高的数据可靠性和可用性、灵活的数据快照功能、更好的虚拟机动态迁移支持、更短的主机故障恢复时间等等。随着万兆以太网逐渐普及,云硬盘的各项优势得到加强和凸显,其必要性变得十分强烈。
云硬盘的底层通常是分布式块存储系统,目前开源领域有一些此类项目,如ceph rbd、sheepdog。另外moosefs和glusterfs虽然叫做文件系统,但由于其特性与块存储系统接近,也能用于支持云硬盘。我们在测评中发现,这些开源项目均存在一些问题,使得它们都难以直接应用在大规模的生产系统当中。例如ceph rbd的效率较低(cpu使用过高);sheepdog在压力测试中出现了数据丢失的现象;moosefs的posix语义支持、基于fuse的架构、不完全开源的2.0版本等问题给它自身带来了许多的局限性;glusterfs与ceph同属红帽收购的开源存储系统,主要用于scale-out文件存储场景,在云计算领域使用不多。此外,这些存储系统都难以充发挥用万兆网卡和ssd的性能潜力,难以在未来承担重任。
由于以上原因,美团云研发了全新的分布式块存储系统ursa,通过简单稳固的系统架构、高效的代码实现以及对各种非典型场景的仔细考虑,实现了高可靠、高可用、高性能、低开销、可扩展、易运维、易维护等等目标。ursa的名字源于dota中的熊战士,他具有极高的攻击速度、攻击力和生命值,分别隐喻存储系统中的iops、吞吐率和稳定性。
分布式块存储相关项目与技术
2.1 ceph
(主要参考:https://www.ustack.com/blog/ceph_infra/)
ceph项目起源于其创始人sage weil在加州大学santa cruz分校攻读博士期间的研究课题。项目的起始时间为2004年。在2006年的osdi学术会议上,sage发表了关于ceph的论文,并提供了项目的下载链接,由此开始广为人知。2010年ceph客户端部分代码正式进入linux kernel 2.6.34。
ceph同时提供对象、块和文件这三个层次的分布式存储服务,其中只有块层存储与我们相关。由于块存储在iaas云计算系统中占有重要地位,ceph在近些年的关注度得到显著提高。许多云计算系统实例基于ceph提供块存储服务,如unitedstack、mirantis openstack等。
ceph性能测试
测试版本:0.81
操作系统:centos 6.x
测试工具:fio
服务器配置:
cpu: intel xeon e5-2650v2 @ 2.6ghz
ram: 96gb
nic: 10 gbe
hdd: 6 nl sas, 7200 rpm
raid controller: dell h710p (lsi 2208 with 1gb nvram)
服务器数量:4,其中一个为兼职客户端
注意:由于客户端位于一个存储服务器上,所以有1/4的吞吐率不经过网卡。
测试结果如下:
读iops:16 407(此时客户端cpu占用率超过500%,5台服务器cpu总使用率接近500%)
写iops:941
顺序读吞吐率:218 859 kb/s
顺序写吞吐率:67 242 kb/s
顺序读延迟:1.6ms (664 iops)
顺序写延迟:4.4ms (225 iops)
网络ping值:0.1324ms
本地硬盘顺序读写延迟:0.03332ms (29 126 iops)
从测试来看,ceph的读吞吐率正常,然而写吞吐率不足读的1/3,性能偏低;读写延迟比显著大于网络延迟与磁盘i/o延迟之和;cpu占用率过高。
2.2 sheepdog
(主要参考:http://peterylh.blog.163.com/blog/static/12033201221594937257/)
sheepdog是由日本ntt实验室的morita kazutaka专为虚拟化平台创立的分布式块存储开源项目, 于2009年开源[1]。从2011年9月开始, 一些淘宝的工程师加入了sheepdog项目,以及相关开源项目比如corosync、accord的开发。sheepdog主要由两部分组成:集群管理和存储服务,其中集群管理目前使用corosync或者zookper来完成,存储服务是全新实现的。
sheepdog采用无中心节点的全对称架构,基于一致性哈希实现从objectid到存储节点的定位:每个节点划分成多个虚拟节点,虚拟节点和objectid一样,采用64位整数唯一标识,每个虚拟节点负责一段包含节点id在内的objectid区间。dataobject副本存在objectid对应的虚拟节点,及在后续的几个节点上。sheepdog无单点故障问题,存储容量和性能均可线性扩展,新增节点通过简单配置即可加入集群,并且sheepdog自动实现负载均衡,节点故障时可自动发现并进行副本修复,还直接支持qemu/kvm。
sheepdog的服务进程既承担数据服务的职责,同时也是客户端(qemu)访问数据的gateway。qemu的sheepdog driver将对volume的请求转换成为对object的请求,然后通过unix domain socket或者tcp socket连接一个sheepdog服务进程,并将访问请求发给该进程来完成后续步骤。sheepdog的服务进程还可以开启数据缓存功能,以减少网络i/o。sheepdog的i/o路径是“client<->gateway<->object manager(s)”,读操作可以在任意副本完成,更新操作并行的发往所有副本, 当所有副本都更新成功之后,gateway才告诉客户端更新操作成功。
sheepdog的数据可靠性问题
我们对sheepdog开展了可靠性、可用性测试。在测试中有共3台服务器,每台配有6个机械硬盘,配置好sheepdog之后,每台服务器启动10个vm,每个vm内无限循环运行fio分别执行小块随机读、写和大块顺序读、写的测试。
在执行压力测试一周后,对集群中的全部数据进行一致性检测(collie cluster check),发现有些数据块副本与另外2个不一致(“fixed replica ...”),有些数据块的3个各不相同(“no majority of ...”):
[root@node3-10gtest ~]# collie cluster check
fix vdi test1-3
99.9 % [=================================================================>] 50 gb / 50 gb
fixed replica 3e563000000fca
99.9 % [=================================================================>] 50 gb / 50 gb
fixed replica 3e563000000fec
100.0 % [================================================================>] 50 gb / 50 gb
fixed replica 3e5630000026f5
100.0 % [================================================================>] 50 gb / 50 gb
fixed replica 3e563000002da6
100.0 % [================================================================>] 50 gb / 50 gb
fixed replica 3e563000001e8c
100.0 % [================================================================>] 50 gb / 50 gb
fixed replica 3e563000001530
...
fix vdi test2-9
50.9 % [=================================> ] 25 gb / 50 gb
no majority of d781e300000123
51.0 % [===================================> ] 26 gb / 50 gb
no majority of d781e300000159
51.2 % [===================================> ] 26 gb / 50 gb
no majority of d781e30000018a
53.2 % [====================================> ] 27 gb / 50 gb
…
2.3 moosefs
(主要参考:http://peterylh.blog.163.com/blog/static/12033201251791139592/)
moosefs是容错的分布式文件系统,通过fuse支持标准posix文件系统接口。 moosefs的架构类似于gfs,由四个部分组成:
管理服务器master:类似于gfs的master,主要有两个功能:(1)存储文件和目录元数据,文件元数据包括文件大小、属性、对应的chunk等;(2)管理集群成员关系和chunk元数据信息,包括chunk存储、版本、lease等。
元数据备份服务器metalogger server:根据元数据文件和log实时备份master元数据。
存储服务器chunk server:负责存储chunk,提供chunk读写能力。chunk文件默认为64mb大小。
客户端client:以fuse方式挂到本地文件系统,实现标准文件系统接口。
moosefs本地不会缓存chunk信息, 每次读写操作都会访问master, master的压力较大。此外moosefs写操作流程较长且开销较高。moosefs支持快照,但是以整个chunk为单位进行cow(copy-on-write),可能造成响应时间恶化,补救办法是以牺牲系统规模为代价,降低chunk大小。
moosefs基于fuse提供posix语义支持,已有应用可以不经修改直接迁移到moosefs之上,这给应用带来极大的便利。然而fuse也带来了一些负面作用,比如posix语义对于块存储来说并不需要,fuse会带来额外开销等等。
2.4 gfs/hdfs
(主要参考:http://www.nosqlnotes.net/archives/119)
hdfs基本可以认为是gfs的一个简化开源实现,二者因此有很多相似之处。首先,gfs和hdfs都采用单一主控机+多台工作机的模式,由一台主控机(master)存储系统全部元数据,并实现数据的分布、复制、备份决策,主控机还实现了元数据的checkpoint和操作日志记录及回放功能。工作机存储数据,并根据主控机的指令进行数据存储、数据迁移和数据计算等。其次,gfs和hdfs都通过数据分块和复制(多副本,一般是3)来提供更高的可靠性和更高的性能。当其中一个副本不可用时,系统都提供副本自动复制功能。同时,针对数据读多于写的特点,读服务被分配到多个副本所在机器,提供了系统的整体性能。最后,gfs和hdfs都提供了一个树结构的文件系统,实现了类似与linux下的文件复制、改名、移动、创建、删除操作以及简单的权限管理等。
然而,gfs和hdfs在关键点的设计上差异很大,hdfs为了规避gfs的复杂度进行了很多简化。例如hdfs不支持并发追加和集群快照,早期hdfs的namenode(即master)没原生ha功能。总之,hdfs基本可以认为是gfs的简化版,由于时间及应用场景等各方面的原因对gfs的功能做了一定的简化,大大降低了复杂度。
2.5 hlfs
(主要参考:http://peterylh.blog.163.com/blog/static/120332012226104116710/)
hlfs(hdfs log-structured file system)是一个开源分布式块存储系统,其最大特色是结合了lfs和hdfs。hdfs提供了可靠、随时可扩展的文件服务,而hlfs通过log-structured技术弥补了hdfs不能随机更新的缺憾。在hlfs中,虚拟磁盘对应一个文件, 文件长度能够超过tb级别,客户端支持linux和xen,其中linux基于nbd实现,xen基于blktap2实现,客户端通过类posix接口libhlfs与服务端通讯。hlfs主要特性包括多副本、动态扩容、故障透明处理和快照。
hlfs性能较低。首先,非原地更新必然导致数据块在物理上非连续存放,因此读i/o比较随机,顺序读性能下降。其次,虽然从单个文件角度看来,写i/o是顺序的,但是在hdfs的chunk server服务了多个hlfs文件,因此从它的角度来看,i/o仍然是随机的。第三,写延迟问题,hdfs面向大文件设计,小文件写延时不够优化。第四,垃圾回收的影响,垃圾回收需要读取和写入大量数据,对正常写操作造成较大影响。此外,按照目前实现,相同段上的垃圾回收和读写请求不能并发,垃圾回收算法对正常操作的干扰较大。
2.6 iscsi、fcoe、aoe、nbd
iscsi、fcoe、aoe、nbd等都是用来支持通过网络访问块设备的协议,它们都采用c/s架构,无法直接支持分布式块存储系统。
3.ursa的设计与实现
分布式块存储系统给虚拟机提供的是虚拟硬盘服务,因而有如下设计目标:
大文件存储:虚拟硬盘实际通常gb级别以上,小于1gb是罕见情况
需要支持随机读写访问,不需支持追加写,需要支持resize
通常情况下,文件由一个进程独占读写访问;数据块可被共享只读访问
高可靠,高可用:任意两个服务器同时出现故障不影响数据的可靠性和可用性
能发挥出新型硬件的性能优势,如万兆网络、ssd
由于应用需求未知,同时需要优化吞吐率和iops
高效率:降低资源消耗,就降低了成本
除了上述源于虚拟硬盘的直接需求意外,分布式块存储还需要支持下列功能:
快照:可给一个文件在不同时刻建立多个独立的快照
克隆:可将一个文件或快照在逻辑上复制成独立的多份
精简配置(thin-provisioning):只有存储数据的部分才真正占用空间
3.1 系统架构
分布式存储系统总体架构可以分为有master(元数据服务器)和无master两大类。有master架构在技术上较为简单清晰,但存在单点失效以及潜在的性能瓶颈问题;无master架构可以消除单点失效和性能瓶颈问题,然而在技术上却较为复杂,并且在数据布局方面具有较多局限性。块存储系统对master的压力不大,同时master的单点故障问题可采用一些现有成熟技术解决,因而美团ebs的总体架构使用有master的类型。这一架构与gfs、hdfs、moosefs等系统的架构属于同一类型。
如图1所示,美团ebs系统包括m、s和c三个部分,分别代表master、chunk server和client。master中记录的元数据包括3种:(1)关于volume的信息,如类型、大小、创建时间、包含哪些数据chunk等等;(2)关于chunk的信息,如大小、创建时间、所在位置等;(3)关于chunk server的信息,如ip地址、端口、数据存储量、i/o负载、空间剩余量等。这3种信息当中,关于volume的信息是持久化保存的,其余两种均为暂存信息,通过chunk server上报。
在元数据中,关于volume的信息非常重要,必须持久化保存;关于chunk的信息和chunk server的信息是时变的,并且是由chunk server上报的,因而没必要持久化保存。根据这样的分析,我们将关于volume的信息保存在mysql当中,其他元数据保存在redis当中,余下的集群管理功能由manager完成。master == manager + mysql + redis,其中mysql使用双机主从配置,redis使用官方提供的标准cluster功能。
3.2 cap取舍
c、a、p分别代表consistency、availability和partition-tolerance。分布式系统很难同时在这三个方面做到很高的保障,通常要在仔细分析应用需求的基础之上对cap做出取舍。
块存储系统主要用于提供云硬盘服务,每块云硬盘通常只会挂载到1台vm之上,不存在多机器并发读写的情况,因而其典型应用场景对一致性的需求较低。针对这一特性,我们可以在设计上舍c而取ap。
对于多机并行访问云硬盘的使用模式,若数据是只读的则无需额外处理;若数据有写有读,甚至是多写多读,则需要在上层引入分布式锁,来确保数据一致性和完整性。这种使用模式在san领域并不少见,其典型应用场景是多机同时挂载一个网络硬盘,并通过集群文件系统(而不是常见的单机文件系统)来协调访问存储空间。集群文件系统内部会使用分布式锁来确保数据操作的正确性,所以我们舍c的设计决策不会影响多机并行访问的使用模式。
3.3 并发模型
并发(不是并行!)模型的选择和设计无法作为实现细节隐藏在局部,它会影响到程序代码的各个部分,从底层到上层。基本的并发模型只有这样几种:事件驱动、多线程、多进程以及较为小众的多协程。
事件驱动模型是一种更接近硬件工作模式的并发模型,具有更高的执行效率,是高性能网络服务的普遍选择。为能充分发挥万兆网络和ssd的性能潜力,我们必须利用多核心并行服务,因而需要选用多线程或者多进程模型。由于多进程模型更加简单,进程天然是故障传播的屏障,这两方面都十分有利于提高软件的健壮性;并且我们也很容易对业务进行横向拆分,做到互相没有依赖,也不需要交互,所以我们选择了多进程模型,与事件驱动一起构成混合模型。
协程在现实中的应用并不多,很多语言/开发生态甚至不支持协程,然而协程在一些特定领域其实具有更好的适用性。比如,qemu/kvm在磁盘i/o方面的并发执行完全是由协程负责的,即便某些block driver只提供了事件驱动的接口(如ceph rbd),qemu/kvm也会自动把它们转化封装成多协程模式。实践表明,在并发i/o领域,多协程模型可以同时在性能和易用性方面取得非常好的效果,所以我们做了跟qemu/kvm类似的选择——在底层将事件驱动模型转换成了多协程模型,最终形成了“多进程+多协程+事件驱动”的混合并发模型。
3.4 存储结构
如图所示,ursa中的存储结构与gfs/hdfs相似,存储卷由64mb(可配置)大小的chunk组成。ursa系统中的数据复制、故障检测与修复均在chunk层次进行。系统中,每3个(可配置)chunk组成一组,互为备份。每2个chunk组构成一个stripe组,实现条带化交错读写,提高单客户端顺序读写性能。ursa在i/o栈上层添加cache模块,可将最常用的数据缓存在客户端本地的ssd介质当中,当访问命中缓存时可大大提高性能。
3.5 写入策略
最常见的写入策略有两种:(1)客户端直接写多个副本到各个chunk server,如图(a)所示,sheepdog采用此种办法;(2)客户端写第一个副本,并将写请求依次传递下去,如图(b)所示。这两种方法各有利弊:前者通常具有较小的写入延迟,但吞吐率最高只能达到网络带宽的1/3;后者的吞吐率可以达到100%网络带宽,却具有较高的写入延迟。
由于ursa可能用于支持各种类型的应用,必须同时面向吞吐率和带宽进行优化,所以我们设计采用了一种分叉式的写入策略:如图(c)所示,客户端使用write_replicate请求求将数据写到第一个副本,称为primary,然后由primary负责将数据分别写到其他副本。这样ursa可以在吞吐率和延迟两方面取得较好的均衡。为确保数据可靠性,写操作会等所有副本的写操作都完成之后才能返回。
3.6 无状态服务
chunk server内部几乎不保存状态,通常情况下各个请求之间是完全独立执行的,并且重启chunk server不会影响后续请求的执行。这样的chunk server不但具有更高的鲁棒性,而且具有更高的扩展性。许多其他网络服务、协议的设计都遵循了无状态的原则。
3.7 模块
如下图所示,ursa中的i/o功能模块的组织采用decorator模式,即所有模块都实现了istore抽象接口,其中负责直接与chunk server通信的模块属于decorator模式中的concrete component,其余模块均为concrete decorator。所有的decorator都保存数量不等的指向其他模块的指针(istore指针)。
在运行时,ursa的i/o栈层次结构的对象图如下所示。
3.8 产品界面
4.性能实测
如下图所示,测试环境由万兆以太网、1台client和3台chunk server组成,chunk文件存在tmpfs上,即所有写操作不落盘,写到内存为止。选择tmpfs主要是为了避免硬盘的i/o速度的局限性,以便测试ursa在极限情况下的表现。
测试环境的网络延迟如下:
在上述环境中,用fio分别测试了读写操作的吞吐率、iops以及i/o延迟,测试参数与结果如下:
从测试结果可以看出:
(1). ursa在吞吐率测试中可以轻易接近网卡理论带宽;
(2). ursa的iops性能可以达到ssd的水准;
(3). ursa的读延迟接近ping值,写操作需要写3个副本,延迟比读高68%。
作为对比,我们在测试ceph的过程中监测到服务端cpu占用情况如下:
此时机器上5个存储服务进程ceph-osd共占用123.7%的cpu资源,提供了4 101的读iops服务;而ursa的服务进程只消耗了43%的cpu资源,提供了61 340读iops服务,运行效率比ceph高43倍。在客户端方面,ceph消耗了500%+的cpu资源,得到了16 407读iops;而ursa只消耗了96%的cpu资源,得到了61 340读iops,运行效率比ceph高21倍。
总结与展望
ursa从零开始动手开发到内部上线只经历了9个月的时间,虽然基本功能、性能都已达到预期,但仍有许多需要进一步开发的地方。一个重要的方向是对ssd的支持。虽然将hdd简单替换为ssd,不修改ursa的任何代码、配置就可以运行,并取得性能上的显著改善,然而ssd在性能、成本、寿命等方面与hdd差异巨大,ursa必须做出针对性的优化才能使ssd扬长避短。另一个重要方向是对大量vm同时启动的更好的支持。如果同时有上百台相同的vm从ursa启动(即系统盘放在ursa上),会在短时间内有大量读请求访问相同的数据集(启动文件),形成局部热点,此时相关的chunk server服务能力会出现不足,导致启动变慢。由于各个vm所需的启动数据基本相同,我们可以采用“一传十,十传百”的方式组织一个应用层组播树overlay网络来进行数据分发,这样可以从容应对热点数据访问问题。随着一步步的完善,相信ursa将在云平台的运营当中起到越来越重要的作用。
下一篇: 做好网站SEO优化的几个关键点分析