Apache Cassandra 数据库.docx
《Apache Cassandra 数据库.docx》由会员分享,可在线阅读,更多相关《Apache Cassandra 数据库.docx(15页珍藏版)》请在冰点文库上搜索。
![Apache Cassandra 数据库.docx](https://file1.bingdoc.com/fileroot1/2023-5/1/df1635f2-83c3-42c0-98e2-df05e4146a6b/df1635f2-83c3-42c0-98e2-df05e4146a6b1.gif)
ApacheCassandra数据库
ApacheCassandra数据库
NoSQL存储是关系数据库的一个替换物,在众多诸如此类的NoSQL存储中,Cassandra是广受欢迎的选择之一。
本文将超越众所周知的一些细节,探讨与Cassandra相关的不太明显的细节。
(一)前言
【SQL】在数据库历史文章“WhatGoesAroundComesAround”(参阅 参考资料)中,MichalStonebraker详细描述了存储技术是如何随着时间的推移而发展的。
实现关系模型之前,开发人员曾尝试过其他模型,比如层次图和有向图。
值得注意的是,基于SQL的关系模型(即使到现在也仍然是事实上的标准)已经盛行了大约30年。
当前云计算和多核系统的用户群之类的开发已经导致产生越来越多的大型系统。
Google和Amazon之类的高科技公司都是首批触及规模问题的公司。
他们很快就发现关系数据库并不足以支持大型系统。
【NoSQL】为了避免这些挑战,Google和Amazon提出了两个可供选择的解决方案:
BigTable和Dynamo,他们可以由此放松关系数据模型提供的保证,从而实现更高的可扩展性。
EricBrewer的“CAPTheorem”后来官方化了这些观察结果。
它宣称,对于可扩展性系统,一致性、可用性和分区容错性都是权衡因素,因为根本不可能构建包含所有这些属性的系统。
不久之后,根据Google和Amazon早期的工作,以及所获得的对可扩展性系统的理解,计划创建一种新的存储系统。
这些系统被命名为“NoSQL”系统。
该名称最初的意思是“如果想缩放就不要使用SQL”,后来被重新定义为“不只是SQL”,意思是说,除了基于SQL的解决方案外,还有其他的解决方案。
【NoSQL】有许多NoSQL系统,而且每一个系统都缓和或改变了关系模型的某些方面。
值得注意的是,没有一个NoSQL解决方案适用于所有的场景。
每一个解决方案都优于关系模型,且针对一些用例子集进行了缩放。
我的早期文章“在DataStorageHaystack中为您的应用程序寻找正确的数据解决方案”讨论了如何使应用程序需求和NoSQL解决方案相匹配。
ApacheCassandra是其中一个最早也是最广泛使用的NoSQL解决方案。
本文详细介绍了Cassandra,并指出了一些首次使用Cassandra时不容易发现的细节和复杂之处。
(二)ApacheCassandra概述
Cassandra是一个NoSQL列族(columnfamily)实现,使用由AmazonDynamo引入的架构方面的特性来支持BigTable数据模型。
Cassandra的一些优势如下所示:
●高度可扩展性和高度可用性,没有单点故障
●NoSQL列族实现
●非常高的写入吞吐量和良好的读取吞吐量
●类似SQL的查询语言(从0.8起),并通过二级索引支持搜索
●可调节的一致性和对复制的支持
●灵活的模式
这些优点很容易让人们推荐使用Cassandra,但是,对于开发人员来说,至关重要的一点是要深入探究Cassandra的细节和复杂之处,从而掌握该程序的复杂性。
(三)Cassandra数据模型
Cassandra数据模型包括列、行、列族和密钥空间(KeySpace)
3.1列
什么是列?
有点用词不当,使用“名称单元格”可能更容易理解一些。
Cassandra数据模型中最基本的单元,每一个列包括一个名称、一个值和一个时间戳。
在本文的讨论中,我们忽略了时间戳,您可以将一个列表示为一个名称值对(例如author="Asimov")。
3.2行
用一个名称标记的列的集合。
例如,清单1显示了如何表示一个行:
清单1.行的示例
"SecondFoundation"->{author="Asimov",publishedDate="..",tag1="sci-fi",
tag2="Asimov"}
Cassandra包括许多存储节点,并且在单个存储节点内存储每一个行。
在每一行内,Cassandra总是存储按照列名称排序的列。
使用这种排序顺序,Cassandra支持切片查询,在该查询中,给定了一个行,用户可以检索属于给定的列名称范围内的列的子集。
例如,范围tag0到tag9999内的切片查询会获得所有名称范围在tag0和tag9999内的列。
3.3列族
用一个名称标记的行的集合。
清单2.列族示例
Books->{"Foundation"->{author="Asimov",publishedDate=".."},"SecondFoundation"->{author="Asimov",publishedDate=".."},}
人们常说列族就像是关系模型中的一个表格
3.4密钥空间
许多列族共同形成的一个组。
它只是列族的一个逻辑组合,并为名称提供独立的范围。
最后,超级列位于一个列族中,该列族对一个密钥下的多个列进行分组。
正如开发人员不赞成使用超级列一样,在此,我对此也不作任何讨论。
(四)Cassandra与关系数据库
数据被放入每一个列族的二维(2D)空间中。
要想在列族中检索数据,用户需要两个密钥:
行名称和列名称。
从这个意义上来说,尽管还存在多处至关重要的差异,关系模型和Cassandra仍然非常相似。
关系列均匀分布在表中的所有行之间。
数据项之间通常有明显的纵向关系,但这种情况并不适用于Cassandra列。
这就是Cassandra使用各个数据项(列)来存储列名称的原因。
关系模型内的每一个点至少应当拥有存储在此处的null值。
而Cassandra可以拥有只包括少数项的行,而其他行可以拥有数百万个项。
关系模型可以对模式进行预定义,而且在运行时不可以更改模式,而Cassandra允许用户在运行时更改模式。
Cassandra始终存储数据,这样就可以根据其名称对列进行排序。
这使得使用切片查询在列中搜索数据变得很容易,但在行中搜索数据变得很困难,除非您使用的是保序分区程序。
另一个重要差异是,RDMBS中的列名称表示与数据有关的元数据,但绝不是数据。
而在Cassandra中,列名称可以包括数据。
因此,Cassandra行可以拥有数百万个列,而关系模型通常只有数十个列。
关系模型使用定义良好的不可变模式来支持复杂的查询,这些查询中包括JOIN和聚合等。
使用关系模型,用户无需担心查询就可定义数据模式。
Cassandra不支持JOIN和大多数SQL搜索方法。
因此,模式必须满足应用程序的查询要求。
为了探讨上述差异,可以考虑一个书籍评分站点,用户可以在该站点添加书籍(作者、等级、价格和链接)、评论(文本、时间和名称),对这些添加的内容进行标记。
应用程序需要支持用户的以下操作:
添加书籍、添加书籍评论、添加书籍标记、列出按等级排序的书籍、列出给定一个标记的书籍、列出给定一个书籍ID的评论
使用关系模型实现以上应用程序几乎微不足道。
图2 展示了数据库设计的实体关系(ER)图。
让我们看看使用Cassandra数据模型如何实现此项操作。
清单3 展示了Cassandra的可能模式,其中第一行表示“Book"列族(拥有多个行),每一行拥有和列相同的书籍属性。
和表示时间戳。
清单3.用于书籍评分的Cassandra模式样例
Books[BookID->(author,rank,price,link,tag,tag..,cmt+=text+"-"+author)…]Tags2BooksIndex[TagID->(=bookID1,=bookID2,..)]Tags2AuthorsIndex[TagID->(=bookID1,=bookID2,..)]RanksIndex["RANK"->(rank=bookID)]
表1 是按照模式表示的样例数据集。
列族名称
样例数据集
Books
"Foundation"->("author"="Asimov","rank"=9,"price"=14,"tag1"="sci-fi",
"tag2"="future","cmt1311031405922"="bestbook-sanjiva","cmt1311031405923"="wellI
disagree-srinath")
"IRobot"->("author"="Asimov","rank"=7,"price"=14,"tag1"="sci-fi""tag2"="robots",
"cmt1311031405924"="Asimov'sbest-srinath","cmt1311031405928"="Ilikefoundation
better-sanjiva")
RanksIndex
"Rank"->(9="Foundation",7="IRobot")
Tags2BooksIndex
"sci-fi"->("1311031405918"="Foundation","1311031405919"="IRobot"
"future"->…
Tags2AuthorsIndex
"sci-fi"->(1311031405920="Asimov")
"future"->…
本示例展示了关系模型和Cassandra模型之间的几个设计差异。
Cassandra模型在一个名为“Book"的单个列族内存储书籍数据,而其他三个列族是构建用来支持查询的索引。
请仔细看一下“Books”列族,该模型使用了一个行来表示书籍名称是行ID的每本书。
有关书籍的细节被表示为存储在行中的列。
再仔细看看,您可能会发现,已存储的数据项(比如评论、与书籍关系为1:
M的标记)也位于单个行中。
为了实现这一点,可以将时间戳附加在列名称上,以便进行标记和评论。
这种方法在同一列中存储所有的数据。
这样的操作避免了必须执行JOIN才可检索数据的问题。
Cassandra弥补了通过此方法支持JOIN的不足。
这也提供了一些优势。
●通过使用单个查询读取完整行的方法,您可以读取书籍的所有数据。
●您可以通过使用切片查询来检索评论和标记,无需执行JOIN,该切片查询的起始范围和终止范围分别为
cmt0-cmt9999和tag0-tag9999。
●由于Cassandra存储按照其列名称排序的列,这就使得切片查询非常快就能完成。
值得注意的是,在单个行中存储所有的数据细节并使用排序顺序是Cassandra数据设计时最重要的理念。
大多数Cassandra数据模型根据这些理念的某些形式进行设计。
用户在存储数据和构建索引时可以使用排序顺序。
例如,给列名称附加时间戳的另一个副作用是:
就像列名称按照排序顺序进行存储一样,评论也有使用时间戳后缀的列名称,并按照创建它们的顺序进行存储,且搜索结果也具有相同的顺序。
Cassandra不支持基础设计的任何搜索方法。
尽管其支持二级索引,这些方法还是通过使用后来构建的索引来提供支持,而且二级索引有一些局限性,不支持范围查询。
因此,要实现Cassandra数据设计最好的结果,需要用户通过构建定制索引并使用列和行排序顺序来实现搜索。
其他三个列族(Tags2BooksIndex、Tags2AuthorsIndex和RankIndex)也这样做。
由于用户需要搜索具有给定标记的书籍,通过将标记名称存储为行ID,并将使用该标记进行标记的所有书籍存储为该行下的列,“Tags2BooksIndex”列族构建了一个索引。
如该例所示,时间戳被添加为列密钥,但是也是将要提供的惟一的列ID。
通过按照标记名称查找行并通过读取存储在该rowID内的所有列来找到匹配项,搜索实现仅读取索引。
表2 讨论了应用程序要求的每个查询是如何使用上述Cassandra索引来实现的。
查询描述
SQL查询
Cassandra实现
列出根据等级存储的书籍
运行查询
"Select*fromBooksorderbyrank",然后在每个结果上执行 "SelecttagfromTagswherebookid=?
"and"SelectcommentfromCommentswherebookid=?
"
在“RankIndex”列族上进行切片查询,接收已排序的书籍列表,并在“Books”对每一个书籍执行切片查询,以便读取书籍的详细信息。
给定一个标记,查找具有给定标记的书籍的作者。
SelectdistinctauthorfromTags,BookswhereTags.bookid=Books.bookidandtag=?
使用切片查询在Tags2Authors中读取给定标记的所有列。
给定一个标记,列出具有给定标记的书籍。
SelectbookidfromTagswheretag=?
使用切片查询在Tags2BooksIndex中读取具有给定标记的所有列。
给定一个书籍,创建评论时,按时间的排序对列出该书籍的评论进行排序。
Selecttext,time,userfromCommentswherebookid=?
Orderbytime
在“Books”列族中,在与给定书籍对应的行中执行切片查询。
它们是按排序顺序的,这是因为将时间戳用作了列名称。
尽管上述设计可以高效支持由书籍评分站点要求的查询,但它只能支持为专用查询设计但不支持专用查询的查询。
例如,如果没有构建新的索引,它就不能支持以下查询。
Select*fromBookswhereprice>50;
Select*fromBookswhereauthor="Asimov"
将设计更改为支持这些和其他查询是有可能的,通过构建适当的索引或编写代码来遍历所有数据即可实现此操作。
但是,需要定制代码来支持新的查询,与关系模型相比,这是一种局限性,因为在关系模型中添加新查询通常不需要更改模式。
在0.8发行版中,Cassandra支持次级索引,用户可以在此根据给定属性指定搜索,而且Cassandra可以自动构建索引来根据该属性进行搜索。
但是,该模型的灵活性不大。
例如,次级索引不支持范围查询,也没有为结果的排序顺序提供保证。
(五)在Java环境中使用Cassandra
Cassandra具有许多用不同语言编写的客户端。
本文将重点介绍Hector客户端,这是最广泛用于Cassandra的Java客户端。
用户可以通过向应用程序类路径添加HectorJAR向应用程序添加Hector客户端节点。
清单4 展示了一个样例Hector客户端。
首先,连接到Cassandra集群。
然后使用CassandraGettingStartedPage中的指令来建立一个Cassandra节点。
除非更改了配置,否则通常在端口9160之上运行该指令。
其次,要定义一个密钥空间,这可以通过客户端或conf/cassandra.yaml配置文件来完成。
清单4.Cassandra的样例Hector客户端节点
Clustercluster=HFactory.createCluster('TestCluster',newCassandraHostConfigurator("localhost:
9160"));//defineakeyspaceKeyspacekeyspace=HFactory.createKeyspace("BooksRating",cluster);//Nowlet'saddanewcolumn.Mutatormutator=HFactory.createMutator(keyspace,user);
StringrowID="Foundation";StringcolumnFamily="Books";mutator.insert(rowID,columnFamily,HFactory.createStringColumn("author","Asimov"));//Nowlet'sreadthecolumnbackColumnQuerycolumnQuery=HFactory.createStringColumnQuery(keyspace);columnQuery.setColumnFamily(columnFamily).setKey(”wso2”).setName("address");QueryResultresult=columnQuery.execute();System.out.println("received"+result.get().getName()+"="+result.get().getValue()+"ts="+result.get().getClock());
(六)Cassandra架构
下图展示了Cassandra集群的架构。
首先观察到得是Cassandra是一个分布式系统。
Cassandra包括多个节点,并跨这些节点来分发数据(用数据库的术语来说就是,将数据分成很多份)。
Cassandra使用一致的散列算法给节点分配数据项。
简言之,Cassandra使用一个散列算法来计算存储在Cassandra(例如,列名称和行ID)中的每个数据项的密钥散列。
散列范围或所有可能的散列值(又称为密钥空间)是在Cassandra集群中的节点之间进行分配的。
然后,Cassandra向该节点分配每一个数据项,而该节点负责存储或管理数据项。
论文“Cassandra-ADecentralizedStructuredStorageSystem”提供了有关Cassandra架构的详细讨论。
由此产生的架构提供了以下属性:
1)Cassandra在其节点之间分发数据,这对用户是透明的。
任何节点可以接收任何请求(读取、写入或删除)并将请求路由至正确的节点,即使数据没有存储在该节点中。
2)用户可以定义所需的副本的数量,而且Cassandra会透明地处理副本的创建和管理。
3)可调节的一致性:
在存储和读取数据时,用户可以选择所期望的每项操作的一致性级别。
例如,如果“quorum”一致性级别是在执行写入或读取操作时使用,那么可以对来自集群中一半以上的节点的数据进行写入和读取操作。
支持可调节的一致性使用户能够选择最适合其用例的一致性级别。
4)Cassandra提供非常快速的写入速度,比每个节点以每秒80-360MB的速度传输数据时的读取速度还要快。
它通过使用两项技术实现这一目的。
5)Cassandra在其负责的节点上保留内存中的大多数数据,而且所有更新都在内存中完成,并以一种懒惰的方式写入永久存储(文件系统)。
但是,为了避免丢失数据,Cassandra将所有的事务写入磁盘中的提交日志。
与在磁盘中更新数据不同,向提交日志写入数据是追加数据,因此在向磁盘写入输入时可以避免旋转延迟。
6)除非要求写入操作是完全一致的,否则Cassandra无需解决任何数据不一致性(只在首次读取时解决不一致性)问题即可将数据写入足够多的节点。
这个过程称作“读取修复”。
由此产生的架构具有高可缩放性。
您可以构建一个具有数十至数百个节点的Cassandra集群,能够处理数TB到数PB字节的数据。
分布式系统有一个权衡,而且缩放几乎从来不会免费提供。
如前所述,用户在从关系数据库迁移到Cassandra时会遇到许多惊喜。
下一部分将讨论这些问题。
(七)Cassandra可能带来的惊喜
7.1不支持事务,就不支持JOIN
Cassandra不支持ACID事务。
尽管其有一个批处理操作,但还是不能保证批处理操作内的子操作是以原子的方式进行的。
此外,Cassandra不支持JOIN。
如果用户需要连接两个列族,就必须以编程方式检索和连接数据。
对于大型数据集来说,这通常代价高昂且非常耗时。
Cassandra通过在同一行中存储尽更多的数据来巧妙地避免这种局限性,如示例中所述。
7.2没有任何外键和键是不可变的
Cassandra不支持外键,所以Cassandra不可能代表用户来管理数据的一致性。
因此,应用程序应当处理数据一致性。
此外,用户不能更改键。
推荐使用具有需要对多个键进行更改的用例的代理键(生成多个键而非一个键,并将键作为属性进行管理)。
7.3键必须是惟一的
每个键(例如行键和列键)在此范围内都必须是惟一的,而且如果同一个键使用过两次,则需要重写数据。
对于这个问题有两种解决方案。
第一个是,您可以使用一个组合键。
也就是说,通过组合多个字段来创建键,而且这个解决方案通常和行键一起使用。
第二个解决方案是,当出现同一个键被使用两次的危险时,使用任意值或时间戳作为该键的后缀。
在索引将某个值存储为列名称并且使用这些索引时,通常会发生这种情况。
例如,在书籍评分应用程序中,等级用作列名称。
为了避免有两个条目因具有相同的等级而具有相同的列名称,时间戳作为后缀添加到等级中。
失败的操作可能导致发生更改
7.4支持幂等操作
不论执行多少次操作,幂等操作都将系统保持为相同的状态。
所有的Cassandra操作都是幂等的。
如果操作失败,您可以果断进行重试。
这就提供了一种从暂时性故障中恢复的机制。
幂等操作并不等同于原子操作。
如果一个操作成功了,一切都很顺利,结果与原子操作是相同的;如果某个操作失败,客户端可以进行重试,如果重试成功了,那么再次一切顺利。
但是,如果在重试后操作仍然失败(与原子操作不同),则会产生副作用。
不幸的是,在使用Cassandra的时候,这是程序员必须亲自处理的一项复杂事物。
7.5批处理操作
Cassandra还支持批处理操作,但也不提供任何原子性保证。
因为操作是幂等的,所以客户端可以一直重试,直到所有批处理操作成功完成为止。
7.6搜索变得复杂
搜索并没有构建为Cassandra架构的核心,而且如前所述,搜索机制使用了排序顺序,划分在分层结构的顶部。
Cassandra支持次级索引,系统可以利用一些受限的功能在此自动构建次级索引。
当次级索引不工作时,用户必须了解数据模型,并使用排序顺序和切片来构建索引。
7.7与构建搜索方法相关的三种类型的复杂领域
在一定程度上,构建定制搜索方法需要程序员了解索引和存储的细节。
因此,Cassandra需要的是更高水平的技能熟练的开发人员,而不是关系模型。
定制索引很大程度上取决于排序顺序,而且被复杂化。
有两种类型的排序顺序:
第一,列始终根据名称进行排序,
第二,行排序顺序只在使用保序分区程序时起作用。
添加一个新查询通常需要新的索引以及与关系模型不同的代码更改。
这就要求开发人员先分析查询,然后再存储数据。
7.8不赞成使用超级列和保序分区程序