Cassandra数据模型

来源:互联网 发布:淘宝如何用信用卡付款 编辑:程序博客网 时间:2024/04/29 06:50
1 简述cassandra数据模型

cassandra定义了column family的概念,用于逻辑上的分割,将同类的数据联系在一起。例如,我们可能会有User column family、Hotel column family、addressbook column family等。这种方式下,一个column family多少类似于关系世界中的table。

cassandra最基本的数据结构是column,它是由一个name和value对(再加一个客户端提供的时间戳,它常记录了最后一次变更的时间)。column family是一个有类似column集合但并不并不一定完全一致的rows容器。

在关系数据库中,我们习惯使用字符串来存储column名字,这也是唯一被允许的。但在cassandra中,没有这个限制。记录的keys和columns name可以像关系字段名那样都是字符串,也可以是长整型数值、UUID或是任何字节数组。所以,可将key name设置成多种形式。

这也揭示了一个有趣的实质:cassandra的columns无需简单的预先定义好name、value对;可在其自身的key中存放有用的数据,而不仅仅是存放在value中。在cassandra中,创建索引是非常正常的。

现在,我们无需每次存储新的记录是为其赋予每个字段的值。也许我们并不知道一条给定的记录每一个column的值。例如,有些人有第二个电话号码,有些人却没有。一个依赖cassandra的在线表单,某些字段可以是可选的,有些是必须的。cassandra不是在那些不知道的字段中存储null值浪费空间,而是根本就不存储那些字段。所以我们拥有的是一个稀疏的多维数组结构,如图:

下面这个关系图可能更让人容易理解:

这里有两个column families,musician和band。其中musician column family有两条记录,bootsy和george。这两条记录包含的columns参差不齐。这就是cassandra的优点之一。第二个column family是band,它也包含了george记录。

在cassandra中的columns实际上还有第三个方面的值:时间戳,它记录了该column最后一次被修改的时间点。但它不是一个自动的元数据性能。客户端在执行写操作时,必须提供相应的时间戳值。是无法通过查询语句访问时间戳的,它的存在纯粹是为了解决在服务器节点之间的冲突的。(记录row本身是没有时间戳的,记录中的每个column中有其自己的时间戳)

如果想要创建一组相关的columns,用于加到另一个其上的维度,cassandra叫这种为super column family,下图是一个super column family:

在column family中的一条记录包含了一批name/value对,super column family包含的是subcolumns,其中一组有特定关系的columns被称作subcolumn。所以,在一个常规column family中,一个value的地址是一行关键字,指向一个column name指向的value值。但在super类的column family中,一个value的地址是一行关键字,指向一个column name,其指向一个subcolumn name的值。说的更简单些,一个super column family的记录仍包含了columns,每个columns包含了subcolumns。

以上就是自底向上的看cassandra数据模型。对此我们现在有一个基本的理解。现在来转换角度,缩小范围到更高的层次,来从上到下的讲解。这个话题有相对比较易混淆,需要以不同的方式进行阐述,从而更好的理解。

2 clusters

如果只将cassandra以单点模式运行,可能不是一个很好的选择。正如之前提到的,cassandra数据库是特别设计用于在多个机器上分布式操作而对于终端用户,看似只有一个实例的。所以在cassandra最外面的结构是cluster,有时也称作ring(环),因为cassandra是将各节点排序在一个ring中,从而分配数据节点的。

一个节点保存了不同范围的数据的一个副本。如果第一个节点down,另一个副本可用于响应发来的请求。点对点的协议允许数据在各节点间进行复制,在某种意义上讲,这对user是透明的。复制因子是在clusters中接收相同复制数据信息的机器数量,具体见后续介绍。

3 keyspaces

一个cluster是一个keyspaces的容器——典型只有一个keyspace。一个keyspace是cassandra中数据的最外层容器,非常类似于关系型的database。就像一个关系database,一个keyspace有一个name和一组参数,这些参数定义了keyspace范围的行为。虽然人们多次建议对于每个应用建立一个单独的keyspace是一种很好的做法,但在实际中这并不是非常普遍的。在事实上可以接受、也是相对比较完美的就是创建实际应用所需的keyspaces数量。但是,需要注意的是,如果一个应用中分布在数以千计的keyspaces中,可能会遇到麻烦。

从安全性的约束和分区方面的考虑,最好在一个cluster中运行多个keyspaces。例如,如果一个叫twitter的应用,它依附在一个叫twitter-cluster的cluster上,keyspace被称作twitter。据我所知,对于这种items,cassandra当前还没有命名规则。

在cassandra中,可以为每个keyspaces设置的基本属性有:

①replication factor:简而言之,replication factor指的是对于每一行数据将被复制到不同节点作为副本的节点数量。如果replication factor是3,则在ring中的三个节点将会拥有每行数据的副本,而这些副本对clients是透明的。replication factor本质上允许你决定为了获得一致性能够话费的性能。读和写的一致性等级是基于replication factor的。

②replica placement strategy:replica placement指的是副本将如何在ring放置。有多种不同的策略,它决定了哪个节点将获得keys的副本。不同的策略有:SimpleStrategy(之前被称作RackUnawareStrategy),OldNetworkTopologyStrategy(之前被称作RackAwareStrategy),和NetworkTopologyStrategy(之前被称作DatacenterShardStrategy)。

③column families:类似于关系型数据库中database是table的容器,一个keyspace是一系列column families的容器。一个column family可以近似于一个粗糙了table,是一系列rows集合的容器。每行记录包含了有序的字段。column families代表了你的数据的结构。每个keyspace中至少包含一个、一般会很多column families。

一般不推荐将一个应用建立在多个keyspaces上(虽然实际并非如此),唯一需要考虑将你的应用分解成多个keyspaces的情况是,如果你想要对某些column families使用不同的replication factor或replica placement strategy。

4 column families

一个column family是一个有序的记录集合,其中每条记录自身又是一个有序columns的集合。在关系世界中,当你物理上建立一个database,指定database(keyspace)的名字、table的名字(类似于column families,但又不全是),此外还定义了每个table中columns的名字。

column family类似于关系table,但去不同。首先,cassandra认为是schema-free的,因为即使column families被定义,但columns没有。可以自由的在任何column family中添加任何column,只要你需要。另外,一个column family有两个属性:name和comparator。comparator值指明在一个访问请求发出并返回后多少个columns被存储——根据long、byte、UTF8或其他顺序。还有一个原因就是,一个column family分别存储在各自的磁盘文件中,这对于定义在相同的column family中将相关的columns放在一起很重要。

另外一个column families不同于关系table的地方是关系table之定义columns,用户提供记录中的values。但cassandra中,一个table可以包含columns,或者它可以被定义为一个super column family。使用super column family的好处是它允许嵌套。对于标准column families,设置标准类型;对于super column family,设置super类型的。

当向column family中写入数据时,指定一个或更多columns具体的值。以一个唯一的标识确认的一组values的集合被称作是row(记录)。row有唯一的key,被称作row key,它就好像是primary key一样唯一的标识这一row。所以有些人把cassandra column families称作四维的hash:

[keyspace][columnfamily][key][column]

下面用JSON符号表示了hotel column family。(这里省略了时间戳的属性,但每个column都有自己的时间戳)

Hotel {
      key: AZC_043 { name: Cambria Suites Hayden, phone: 480-444-4444,
          address: 400 N. Hayden Rd., city: Scottsdale, state: AZ, zip: 85255}
     key: AZS_011 { name: Clarion Scottsdale Peak, phone: 480-333-3333,
          address: 3000 N. Scottsdale Rd, city: Scottsdale, state: AZ, zip: 85255}
     key: CAS_021 { name: W Hotel, phone: 415-222-2222,
          address: 181 3rd Street, city: San Francisco, state: CA, zip: 94103}
     key: NYN_042 { name: Waldorf Hotel, phone: 212-555-5555,
          address: 301 Park Ave, city: New York, state: NY, zip: 10019}
}

可以通过CLI客户端查看column family,如下

cassandra> get Hotelier.Hotel['NYN_042']
=> (column=zip, value=10019, timestamp=3894166157031651)
=> (column=state, value=NY, timestamp=3894166157031651)
=> (column=phone, value=212-555-5555, timestamp=3894166157031651)
=> (column=name, value=The Waldorf=Astoria, timestamp=3894166157031651)
=> (column=city, value=New York, timestamp=3894166157031651)
=> (column=address, value=301 Park Ave, timestamp=3894166157031651)
Returned 6 results.

**  column family options:cassandra中存在几个额外的参数用于定义column family。具体如下:

keys_cached:用于保存每个SSTable的缓存大小。这不是指缓冲name/values,而是指能被缓冲到内存中的key的个数,按照最近最少使用的顺序保存在缓冲中。
row_cached:指明多少条rows的内容(唯一的row key对应的name/value对整体内容)将被缓冲到内存中
comment:这只是普通的注释,用于帮助记忆column family定义相关的信息。
read_repair_chance:该值在0到1之间,代表当查询语句在执行期间没有指明quorum是,从两个或是多个节点副本返回相同row,并且至少有一个副本是过期的情况下,发生read修复操作的可能性。如果相比writes操作有大量的reads操作,可考虑将该值调低。
preload_row_cache:指明是否在server启动时将记录预先缓冲。

5 columns

一个column是cassandra数据模型中最基本的数据单元。一个column是一个三元组,由name、value和时间戳组成。虽然对关系世界中的column概念非常熟悉,但在cassandra中不要混淆。首先,在关系数据库中,会预先设定table中的结构,包含哪些column,随后写入数据,只需提供之前定义结构的相应值。

但在cassandra中,你无须事先定义columns,只需在keyspace中定义你所需要的column families,随后即可在任何没有定义columns的地方写入数据。因为在cassandra中,所以column的name是由客户端提供的。这给应用带来了非常大的灵活性,可使其随着时间逐渐演变。

name和value的数据类型是java的字节阵列,频繁提供字符串。因为name和value是二进制类型,他们可以是任意长度。时间戳的数据类型是org.apache.cassandra.db.IClock的,但对于0.7版本的,该时间戳仍然保持后相兼容。

在服务器端,columns是不可变的,从而避免多线程的问题。在cassandra中由org.apache.cassandra.db.IColumn接口定义的column,它允许各种操作,包括获得column的值,在super column的情况下,获得subcolumns,并获得最近经常变动的时间。

在关系数据库中,rows是存储在一起的。这在早期的cassandra版本中不是这样的,但到了0.6版本时,对于相同column family的rows是一起存储在磁盘上的。

在cassandra中是无法实现joins操作的。如果设计一个数据模型,发现需要某些类似join的操作,则要么需要在客户端做这种操作或创建一个非普通的第二个column family,代表join结果。多会采用后者。

** wide rows, skinny rows

当在传统的关系数据库中设计table,典型用“entities(实体)”处理,或一系列描述性的属性。对于row自身的长度无需考虑过错,因为一旦定义了你的table代表什么名词,row的长度是不可协商的。但对于cassandra,可决定rows的长度:可以是wide或skinny,依赖于row中包含的columns数量。

一个wide row意味着一条记录有很多columns(甚至可以是数以百万的)。相反,有些row可能类似于关系模型,定义了少量的columns。

wide rows一般包含自动生成的names(如UUID或时间戳),并用于存储事务的lists。如一个监控应用:可以用一条row来代表一个小时内的时间切片,将变化的timestamp作为row的key,并存储间隔时间内访问应用的IP地址到columns中。可在一小时后创建新的row key。

skinny rows相对更像传统的RDBMS rows,这种情况下,每条row将包含类似的column names,有所不同的是所有的columns都是可选的。此外,wide和skinny row不同的是只有wide rows才会与column name的排序更关注。

** column 排序

columns对于其定义还有另外一个方面。在cassandra中,当结果返回给client时,你指明了column names将如何排序比较。columns将按照定义时所属的column family指定的compara with类型来进行存储,可选的比较种类有:AsciiType,BytesType,LexicalUUIDType,IntegerType,LongType,TimeUUIDType或UTF8Type。此外还有custom类型,可创建自己的column排序机制。

column names被按照compare_with指定的值进行排序存储。

在cassandra中,是不能对value进行排序的。这看似是一个非常奇怪的限制,但cassandra必须按照column name进行排序,从而允许从非常庞大的rows中获得某些columns,而无需将整个row都读入内存。性能是cassandra的一个重要卖点,在读取过程中的排序将是对性能的巨大伤害。

6 super columns

super column是一种特殊的column。两类columns都是name/value对,但普通的column存储了一个字节序列值,但super column值是subcolumns(subcolumns存储了字节序列值)的映射。它们存储的只是columns的映射,不能定义存储super columns映射关系的super columns。所以super column概念只有一层深度,但其宽度可是无限的。

super column的基础结构是其自己的name(同普通column一样,是字节序列值)和其存储的columns。

每个column family都是存储在磁盘上一个独立的文件中。所以,为了优化性能,将可能一起查询的数据存放在相同的column family中是非常有效的,super column对此是比较有效的。

supercolumn类使用了IColumn和IColumnContainer类,都是来自org.apache.cassandra.db包中。

对于super column,看起来像是一个五维hash:[Keyspace][ColumnFamily][Key][SuperColumn][SubColumn]

** composite keys

当模型中有super columns,需要考虑一下因素:cassandra不会对subcolumns进行索引,所以当load一个super column到内存中时,其中所有的columns都会被载入到内存。

对此可考虑用composite key(符合主键)来帮助查询。一个composite key可能类似这样:<userid:lastupdate>

composite key的方法意味着使用普通column family的常规columns来代替使用super column family,同时需要在columns的key中定义一个界定符,在客户端进行分离。

7 关系型数据库和cassandra之间设计的不同

* 没有查询语言:SQL是标准的查询语言用于关系型数据库。cassandra没有查询语言,但有一组API,用于通过其RPC序列化机制来访问。

* 没有参照完整性:cassandra中没有参照完整性的概念,因此没有joins的概念。在关系数据库中,可在table中指定外键,参照其他表中记录的主键。但cassandra不支持该种功能。它可以要求在你的表中存放关于其他实体的ID,但类似于级联删除的功能是没有的。

* 二级索引:简单描述一下二级索引的作用:假设你想根据唯一hotel的某个属性找到其唯一ID,在关系型数据库中,可使用如下的查询语句:select hotelID from Hotel where name=’clarion midtown’。如果在name字段上添加一个索引,即使hotel表很大,也会在很短时间内获得结果,无需进行全表扫描。因为hotelID是唯一索引作为第一索引,所以对于name这类字段上建立的索引被称作二级索引。而在cassandra中,这是不支持的。为了在cassandra中获得相同的功能,可创建第二个column family,它保存了查询的数据。比如上面的例子,可创建一个column family来存放hotel names,并将其映射到其ID上。这个二级column family扮演了关系型数据库中二级索引的角色。

*排序是一种设计方案:在关系型数据库中,可通过order by简单的改变查询记录输出的结果。默认的排列顺序是不可配置的,默认下记录返回是按照写入的顺序给出的。如果需要排列输出,只需要改变查询语句,可依据任何columns进行排序。在cassandra中,对待排序时不同的;它是一种设计方案。column family的定义包括compareWith元素,它指明了记录被存储的顺序,在读取时也将按该顺序显示,但它不是每个查询都可配置的。在关系型数据库中,趋势你基于column中的数据类型进行排序。cassandra只存储字节阵列,所以那种方法是讲不通的。但是如果需要对一个column,类型是ASCII, Longinteger, TimestampUUID, lexicographically中的一个,则可使用自己的可插入式比较器用于比较。cassandra中有一种slicerange的查询,类似于order by查询,将在后面介绍。

* Denormalization(反规范化)在学习关系数据库时,我们常会学习重要的规范化。但在cassandra中,当数据模型是反规范化时性能会更好。重要的一点是,在cassandra中,不是先设计数据模型再写相关的查询语句,而是你先设计你的查询模型,随后让你的数据围绕着这些查询来组织。先想好应用中最常用的查询路径,再创建支持这些查询的所需的column families。

8 设计模式

* 物化视图(Materialized view):创建二级索引用于代表附加的查询是非常普遍的。因为在cassandra中没有where子句的sql,最有效的方法是创建二级的column family,用于满足某些具体查询。例如:如果有一个user column family,如果希望查找在特定城市的user,可建立一个名为UserCity的二级column family,用于存放user 数据用city作为key,其包含居住在该城市的user名的columns。这是一种非规范化技术,将加速查询,是典型的围绕查询需求进行特殊设计的例子。这在cassandra中是非常普遍的用法。当想要查询某个city中的user是,只需查询UserCity column family就可以了。

注意关于这点,”materialized”意思是存储一个原始数据的完整的副本,以便你所需要的所有数据都保存这个column family中,无需再查找原始数据。如果因为只存储了column names,就像外键似的,则需要执行二次查询。在0.7版本中,cassandra将支持二级索引。

* valueless column:以刚才的user、UserCity为例。因为我们在User column family中存放了参照数据,两种情况出现:一、需要有唯一的贴切的key可以被实施参照完整性;二,在UserCity column family中的columns不必须要有values。如果有一条row的key是Boise,则column names可以是Boise中users的names。因为参照数据保存在user column family中,所以这些UserCity column family中的user names没有任何真正的意义,它只是作为预加工list,但也可能需要从参照的column family中获得额外的数据。

* Aggregate Key

当使用valueless column 模式时,可能同时会需要aggregate key模式。这种模式将两个标量值用分隔符融合在一起,从而创建一个aggregate。为了进一步扩展我们的例子,city name不是唯一的,在US的很多州都有叫做Springfield的城市,等等。所以为了更好的将州名和城市名融合在一起,可创建一个aggregate key用于刚才的materialized view。该key可以类似于这种,TX:Paris或TN:Paris。按照惯例,很多cassandra user都会使用冒号作为分隔符,但它应该是一个管道字符,或其他任何在key中不使用的字符。