(1)数据规模
Bigtable类数据库系统(HBase,Cassandra等)是为了解决海量数据规模的存储需要设计的。这里说的海量数据规模指的是单个表存储的数据量是在TB或者PB规模,单个表是由千亿行*千亿列这样的规模组成的。提到这个数据规模的问题,不得不说的就是现在在Nosql市场中,最火的四种Nosql系统依次是Mongodb,Redis,Cassandra,HBase。我们知道Cassandra和HBase都是Bigtable类系统,而且都是名门出身(得到了Facebook,Yahoo,Twitter等的大力支持)。那么为什么最火的是Mongodb呢?难道是因为HBase不够优秀么?我认为原因很简单,毕竟大部分公司的数据规模还达不到Facebook,Yahoo等那么大,使用Mongodb足以满足他们的需求。Mongodb所提供的Auto-sharding,schema-less等功能,正好解决了这样数据规模的公司在使用RDBMS过程中遇到的问题。
(2)数据模型
而且Bigtable类数据库系统的数据模型相对简单,一般不涉及多表的JOIN操作。在这样的规模下,传统的RDBMS应用越来越受到限制,维护和升级的成本越来越高。而且传统RDBMS由于基于share-storage的设计,scale-out的能力不强。把基于share-storage的RDBMS做成分布式数据库,需要用户来开发Proxy层。上述种种问题使得我们在面对海量数据的时候不得不考虑像Bigtable这样的Nosql存储方案。那么对于习惯了为RDBMS设计schema的DBA们来说,迁移到Bigtable类Nosql系统时的schema设计问题,就需要换一个思路来考虑这个问题了。这篇文章就是介绍在Bigtable类系统中如何设计table的schema,以及随着数据规模的扩展,一些传统RDBMS应用向Bigtable系统迁移过程中需要注意的问题。
Nosql数据库把可扩展性放到了首位,那么必然会造成一定量的数据冗余,通过数据冗余的方式实现在RDBMS中的不同表格之间的关系表达。而且在Bigtable类系统中,不会提供sql类的复杂查询表达和各种优化功能,仅仅提供海量的数据存储能力。所以,就像在Facebook的统一消息系统中一样,很多时候是用一行来存储一个用户的所有信息。那么在Bigtable类系统中,一行所能存储的数据量就是非常大的。前段时间在微博上有rumor说apple的siri系统后台是用的HBase,我想如果是真的话,那么一个用户的个人助理信息也应该是存在一行里吧,呵呵。更有意思的是,apple的保密工作做的真好,而且声东击西。明明用的是HBase,招聘的时候非说会Cassandra和Mongodb的有加分。。。
在Bigtable类系统schema设计中还需要注意的就是列族特性。因为Bigtable类系统本质上是按照列族存取的,同一个列族里不同列有一个共同点就是数据类型相同。相同的数据类型就会使得数据在磁盘和内存之间IO时的压缩率非常高,这是所有面向列的存储系统的共同优势。那么我们在考虑一行所要存储的信息时,就可以按照各个属性的数据类型的不同存放到相应的列族中。由于Bigtable是一个稀疏的表格系统,所以可能某一行具备的某一属性在其他所有的行里都不存在,但是这个属性的数据类型(例如int)的属性在其他行基本上肯定会存在所以在实际的存储中,同一列族的属性是存放到一起的。
(3)非规范化
在Nosql系统数据建模中,经常提到一个Denormalization的概念,就是非规范化。举个简单的例子就是在RDBMS中的Entity和这些Entity之间的关系存储到Nosql中的同一个表格中。例如在RDBMS的规范化数据建模中,有两个表格:Student(StudentID,StudentName,Tutor,CourseID),Course(CourseID,CourseName)。而在Bigtable类Nosql系统中,只有一个表格Student(StudentID,CourseID,CourseName)。那么对于在传统RDBMS中需要读取两个表格的信息,然后JOIN在一起获得或者聚合某些用户的信息,在Nosql系统中只需要读取一次就可以获取某些用户的信息了。
(4)Row Key
Bigtable类系统schema设计需要注意的另外一个问题就是Row的天然有序性。Bigtable类系统把Row Key都是解释成String的,并且按照String的字母顺序来组织Row的。所以这一特性就可以被我们的schema设计所利用。例如我们的应用经常需要用到某一属性的索引或者几个属性组合的索引,那么就可以用这一属性或者属性组合来做Row Key。这一点非常类似于RDBMS中的索引和组合索引,只不过在Bigtable类系统中,这是天然存在的。需要注意的是,在HBase系统中属性组合作为Row Key时,需要用特殊的符号将各个单独的组成部分拼接起来,但是“/”是不能作为Row Key中不同属性的分割符的,我们可以用“_”。
(5)数据一致性和事务
在数据一致性方面,在传统的RDBMS系统中,每一列的属性可以规范成NOT NULL,UNIQUE或者CHECK等,由RDBMS系统来为用户保证数据的一致性需求。在Bigtable类系统中,这一需求在DB层并没有保证,而是由用户层程序来保证的。例如Bigtable类系统是一个大的稀疏的表格,那么对于表格中如果某个cell为NULL,那么在实际物理存储中是不存储这个cell的,也就是Bigtable类系统中不保存内容为NULL的value。由于开源系统HBase具备行一致性和行原子性,而且一般一行存放一个用户的信息,所以维护数据一致性的代价相对较小。如果Bigtable类系统的schema设计不佳,造成复杂的数据冗余,那么对于应用层来维护数据一致性的代价就很大了。
关于Bigtable类系统的事务支持说起来就很复杂了。简单的就是HBase已只支持单行的事务,今天听说HBase也开始支持单机多行事务。但是如果打算实现类似于RDBMS的事务特征,就得结合HBase和Zookeeper了。关于这方面在本文不做详细讨论,后面会专门发文讨论Google的关于Percolator和Megastore的paper。这两篇paper主要讨论了如何在利用Nosql系统实现事务,如何打通Nosql和sql的。但是就像在Cloud Foundry中Chris说到的一样,未来的数据库系统是polyglot persistent storage,不会出现大而全的数据库一统天下。那么在Bigtable/HBase上开发越来越复杂的feature也得有自己的取舍,试图打造Nosql领域的DB通是不现实的。
(6)索引
关于索引是每个DB系统都需要考虑的问题。从Bigtable的论文中可以看出其为每列维护特殊的单列索引,允许创建多列索引。这些索引由Bigtable自动维护,查询时由Bigtable自动选择使用哪些索引。这一点和RDBMS就比较接近了。而开源实现的HBase除了自动有序的Row Key作为索引以外,只提供一个自动维护secondary index。但是查询时该使用那些索引,得由应用层来决定。关于HBase的secondary index的实现有多种方式,貌似最近还喝coprocessor扯上了关系,可以参考这个http://kenwublog.com/hbase-secondary-index-and-join。 HBase同时允许创建和使用存储在文件系统上的Lucene索引。关于HBase和Lucene的结合,可以参考这里http://www.infoq.com/articles/LuceneHbase。
以上主要是从理论上说明了如何为Bigtable类系统设计schema。下一篇文章会以一些实例的方式说明这篇文章的内容,其中会涉及到Facebook统一消息系统的例子。
转自:http://yanbohappy.sinaapp.com/?p=20
上一篇文中从理论上讲了如何针对应用需求选择Bigtable类系统和Bigtable类系统schema设计的原则。这一篇文章举例说明目前在工业界大家是怎么做的。一下以Facebook统一消息系统的设计为例来说明这个问题。
We’relaunching a new version of Messagestoday that combines chat,SMS,email,and Messages into a real-time conversation.
(1) 统一消息系统的规模和应用需求
每个月多于350 million的用户会发送多于 135 billion的person-to-person messages, 120 billion的chat messages。即使这些messages都被限制在160个字符之内,那也意味着21,600,000,000 bytes的数据。而且数据是在一直增长的,因为用户收到消息或者邮件之后一般是不会删除的。而且统一消息系统所存储的数据主要有以下两种类型:一是经常变化的较小的临时数据集(聊天内容),一是很少被访问的不断增加的数据集(邮件,阅读了收件箱里的邮件以后就很少去再看它一眼。但是还不能使用archive的方式(类似于磁带)存储,因为这些数据必须available all times and with low latency)。同时,对于每个用户,我们需要维护这个用户的所有消息的逆向索引,因为每个用户都想像使用gmail一样通过关键字的形式搜索自己以往的邮件或者聊天记录。
同时消息系统需要high write throughput。现在的情况是Facebook Message每天约80亿的消息,75亿以上的读和写操作。高峰时期每分钟150万的读和写操作,其中约55%的读和45%的写操作,每次的写操作会插入16个记录。Nosql系统的Denormalization特性带来了schema-less的优势,同时由于同一份信息可能在不同的列中冗余存在,也使得写的次数增加。
(2)为什么选择HBase?
在统一消息系统建立之初,对于Infrastructure最重要的就是数据库的选择了。 Facebook的Data team面临的选择主要有三种:Cassandra,MysqL和HBase。经过在evaluate clusters上的测试,他们最终选择了HBase。原因如下:MysqL首先是不能很好的auto sharding的问题,也不能很好的处理长尾数据,而且随着数据集的不断增长,更新索引的性能会下降,统计一些变量会变得很慢。而Cassandra的最终一致性模型对于消息系统来说又是非常不靠谱的。Cassandra保证每次读都能读到,这个是高可用的,然而读到的数据却不保证是最新的数据。如果不是最新的数据,会发生一个read repair process。而HBase在可扩展性和性能不会随着数据量增长而下降方面击败了MysqL,又具备比Cassandra更简单的一致性模型。(这是不是说明HBase的强一致性、低可用性战胜了Cassandra的最终一致性和高可用性呢?笔者一直认为Cassandra/Dynamo的思路在设计上非常先进和超前,但是实际系统中应用起来非常麻烦。工业界还是希望简单可依赖的东西。) Facebook在HBase上做了一些工作之后,发现HBase的许多feature(例如auto load balance,auto failover,compression support,multiple shards per server等)正好满足这个统一消息系统的需求。HBase底下的HDFS的诸多优点(replication,end-to-end checksum and automatic rebalance)也非常吸引人,而且Facebook在Hadoop/Hive上面有很多使用经验。因为在这之前,Facebook的数据仓库就已经转到Hadoop平台上了。
注:这里不得不说Hadoop/HDFS的replication策略和Cassandra的replication策略的不同。Cassandra在某一服务器收到用户的写入数据之后,异步的写到其他副本服务器上。而且多次写操作在不同副本的时间先后关系是不能保证一致的。但是HDFS是通过同步的pipeline的方式写入的,那么一旦给客户端返回成功,就能保证所有副本数据的一致性。而且HBase能够保证多次写入之间的顺序关系,这在消息系统中是非常重要的。因为多条消息的先后到达关系如果混乱,会给用户带来麻烦。但是pipeline的复制有个缺点,就是木桶的短板效应,一旦3副本中有一个写入的速度变慢,就会导致整体pipeline变慢,那么HBase的写入吞吐率就会降低。
(3)Facebook Messages后台存储系统HBase的schema设计
包括Messages,Chats,Emails,SMS四种消息类型。由于本文主要讨论后台HBase系统的schema设计问题,关于Facebook统一消息系统的架构和数据流描述详细请见这篇文章http://blog.huihoo.com/?p=688。概括起来就是用HBase作为Backend storage infrastructure,每个用户数据存储在 HBase 的单独一行里,每个实体都存储在自己的HBase列中;邮件的大附件是存储在Facebook自己开发的专用存储系统 HayStack 中;使用Apache Lucene维护反向索引列表。在整个数据流中,用户的每个行为都是通过应用服务器代理用户完成的。所以整个数据流就是应用服务器与后台存储基础设施之间的互动。针对同一个用户的不同更新可能同时发生,也就是对HBase中一行的操作需要同步,这也是由应用服务器来控制的。也就是说每个用户在某一时间段本质上所有的操作都会通过某一台同步服务器控制的。
这是统一消息系统中Hbase的schema设计图。从这张图中可以看出,一个用户是Hbase中的一行,包括Metadata Entities,Metadata indexes,Action logs,Keyword search index四个列族。
用户消息系统中的任何更新操作(如发表和删除消息,标记为已读等)会立即append到Action Logs列族中,这称为一个action log。一些很小的消息实体也存储在每个对应的action log中。这个设计很像Log-Structured File System(http://www.cs.berkeley.edu/~brewer/cs262/LFS.pdf),把每块数据都append到磁盘,然后维护索引以保持文件整体性和结构。这里也是通过Metadata indexes列族来维护消息数据的结构。例如我们要加载未读邮件列表,那么先通过Metadata indexes获取指定tag/time区间的邮件的索引,然后去Metadata Entities中寻找索引对应的实体(比如邮件内容)显示出来。所以可以这么理解,Metadata Entities列族中每一列存放着不同的消息实体。
Actin log的作用和文件系统或者数据库中log的作用类似,可以通过replay action log的方式来构造或者恢复用户邮箱的当前状态。通过最后的action log id和Metadata entities,Metadata indexes配合来恢复或者更新用户消息系统的内容。Action log可以通过批量执行大量的异步写节省带宽,提高IO吞吐率。而且action log还可以被复制到其他地方以实现数据的备份。
Keyword search index列族是为了用户通过关键字搜索自己消息系统中内容而设计的,维护了一个从keyword到匹配的消息的逆向索引。每一列是一个keyword和存在这个keyword的消息列表。当用户写入一个消息是,通过Lucene去解析和转化它为一个(keyword,messageID,position)的tuple,然后append到这个Hbase列族中相应关键字的列中。这样所有的消息都被实时索引。关于如何集成Lucene和Hbase,请看这篇文章http://www.infoq.com/cn/articles/LuceneHbase。
(4)Hbase在Facebook的部署情况
这是Facebook的部署方案:每个rack里放20台server,每个集群里至少5个racks。
据称Facebook的消息系统的Hbase集群的规模已经超过了1000台servers。可以说是目前那Hbase用作online storage应用的最大规模的集群了。而且目前在微博上有rumor说Facebook正准备把用户数据从MysqL移到hbase。看来Facebook要全面拥抱Hbase了。
参考文献:
http://www.facebook.com/note.php?note_id=454991608919
http://dbpedias.com/wiki/HBase:Facebook_Messaging_-_HBase_Comes_of_Age