锻造车间 - Cassandra
在过去的几个月里,我介绍了几种不同的非关系型 (NoSQL) 数据库。这类数据库正变得越来越流行,因为它们通常比关系型数据库提供更简单(有时也更强大)的速度和可扩展性。在大多数情况下,它们也是“无模式”的,这意味着您不需要预先定义或声明您正在存储的数据的名称、类型和大小。这也意味着您可以像使用哈希表一样轻松灵活地存储持久性信息。
我仍然怀疑这些非关系型数据库是否应该始终用来代替它们的关系型对应物。关系型数据库背后有着多年的思考、开发和调试。但是,关系型数据库是为可靠性和任意数据组合而设计的。相比之下,NoSQL 数据库是为速度和可扩展性而设计的,没有“连接”和作为关系查询核心支柱的其他项。
因此,我开始相信关系型数据库在计算机世界中,甚至在高功率 Web 应用程序的世界中,仍然发挥着重要作用。然而,正如内置字符串、数组、哈希表和其他复杂数据结构的引入使无数程序员的生活更加轻松一样,我认为非关系型数据库也发挥着重要作用,为开发人员提供了存储和检索数据的新颖有趣的有用方法。
到目前为止,我在本专栏中探索了几个非关系型系统。CouchDB 和 MongoDB 都是“文档”数据库,这意味着它们基本上允许您存储名称-值对(如果您喜欢哈希)的集合,然后使用各种类型的查询从这些集合中检索元素。CouchDB 和 MongoDB 在存储和检索数据的方式上截然不同,它们处理复制的方式也不同。
CouchDB 和 MongoDB 在风格和精神上都比我上个月介绍的系统 Redis 更接近——Redis 是一种键值存储,速度极快,但限制您只能查询特定键,并且数据类型有限。此外,Redis 假设您只有一个服务器。虽然您可以复制到辅助服务器,但数据或负载不会在多个节点之间进行分区。
Cassandra 有点像所有这些系统,但又与它们中的任何一个都截然不同。Cassandra 将数据存储在可以被认为是多级(或多维)哈希表中的结构中。您可以根据键检索信息,使其类似于键值存储,如 Redis 或 Memcached。但是,Cassandra 允许您请求一系列键,从而提供了一定的额外灵活性。此外,Cassandra 的多维特性、它使用“超级列”来存储多个相似类型的项目以及它在底层存储名称-值对都提供了相当大的灵活性。
在可扩展性的许多方面,Cassandra 真正大放异彩。您可以添加节点,Cassandra 会将它们无缝集成到存储系统中。节点可能会死机或被删除,系统会适当地处理这种情况。所有节点最终都包含所有数据,这意味着即使您在 Cassandra 存储集群中只保留一个节点,系统也应该继续无缝运行。由于写入分布在不同的节点上,因此将新数据写入 Cassandra 只需很短的时间。
很明显,Cassandra 引起了大量开发人员的共鸣。该项目最初在 Facebook 启动,目的是解决搜索用户收件箱的问题。Facebook 将代码捐赠给了 Apache 项目,该项目此后对其进行了推广,并使其成为一流项目。Facebook 不再参与 Cassandra 的开源版本,但显然 Facebook 仍在自己的系统上使用它。与此同时,包括 Rackspace、Twitter 和 Digg 在内的公司都已成为活跃且重要的 Cassandra 用户,贡献代码并为 Cassandra 提供的总体发展势头做出贡献。
也许我在使用 Cassandra 时必须克服的两个最大障碍是不寻常的术语以及必要的配置和管理。术语之所以困难,部分原因是它以不同于我习惯的关系型数据库的方式使用现有术语(例如“列”和“行”)。这并不难,但确实需要一些时间来适应。(尽管开发人员可以通过避免使用“列族”和“超级列”等术语来帮大家一个忙。)配置方面并不十分繁琐,但也许表明人们在使用非关系型数据库时变得多么娇生惯养。我必须在配置文件中命名我的键空间和列族,然后重新启动 Cassandra 才能使其定义生效,这似乎是对较旧、更僵化系统的倒退。但是,关系型数据库迫使我们在使用表、列和数据类型之前定义它们,这似乎从来都不是一个可怕的负担。而且,Cassandra 的速度和可靠性的秘密似乎部分在于其数据结构是严格定义的。
本月,我将初步了解如何启动并运行 Cassandra,并解释如何在简单的 Cassandra 实例中存储和检索数据。
Cassandra 的主页是 cassandra.apache.org。从那里,您可以下载 Cassandra 并将其安装在您的计算机上。由于 Cassandra 是用 Java 编写的,因此只有一个发行版二进制文件,它应该可以在任何装有当前 JVM 的计算机上运行。
在我的运行 Ubuntu 的计算机上,我首先使用以下命令安装了最新的 Java JDK:
apt-get install openjdk-6-jdk
在此之后,我可以下载最新的 Cassandra 版本并安装它。但相反,我决定使用 apt-get 来检索最新版本,并确保将来我会收到更新。为了做到这一点,我首先需要按照 Cassandra Wiki 上的说明,将相应的 GPG 密钥添加到我的密钥链中
gpg --keyserver wwwkeys.eu.pgp.net --recv-keys F758CE318D77295D gpg --export --armor F758CE318D77295D | sudo apt-key add -
之后,我将以下两行添加到 /etc/apt/sources.list 中
deb https://apache.ac.cn/dist/cassandra/debian unstable main deb-src https://apache.ac.cn/dist/cassandra/debian unstable main
接下来,我运行了apt-get update以检索所有软件包的最新版本信息,然后我运行了apt-get install cassandra以将其安装在服务器上。大约一分钟后,Cassandra 就安装完成并准备在我的机器上运行了。
我使用以下命令启动了它
/etc/init.d/cassandra start
果然,快速查看一下ps向我表明 Cassandra 确实正在运行。
有许多来自各种编程语言的 Cassandra 接口。但是,连接到 Cassandra 最简单的方法通常是通过其内置的命令行界面 (CLI),该界面随程序一起提供。只需输入cassandra-cli在您的 shell 中,您将看到如下所示的提示符
Welcome to cassandra CLI. Type 'help' or '?' for help. Type 'quit' or 'exit' to quit. cassandra>
您的首要任务应该是连接到您的本地 Cassandra 服务器
cassandra> connect localhost/9160 Connected to: "Test Cluster" on localhost/9160
如果您忘记了刚刚打印的内容,您可以使用以下命令获取当前集群名称
cassandra> show cluster name Test Cluster
您还可以获取此集群中键空间的列表
cassandra> show keyspaces Keyspace1 system
这个system键空间,正如您可以想象的那样,用于 Cassandra 系统任务。探索它可能很有趣,但除非您真的知道自己在做什么,否则您不想搞乱它。
如果您想创建一个新的键空间怎么办?那么,您需要进入并更改系统配置并重新启动 Cassandra。您需要修改的配置文件名为 storage-conf.xml。在我的 Ubuntu 系统上安装 Cassandra 后,它被放置在 /etc/cassandra/storage-conf.xml 中。(文件名始终为 storage-conf.xml,但位置可能因您的机器而异,具体取决于您的安装方式。)您可以使用 Cassandra CLI 中的以下命令查看此配置文件的内容
cassandra> show config file
但是,此命令仅显示文件的内容,而不显示其位置,因此您可能需要四处寻找一下才能找到它。
要向您的 Cassandra 集群添加新的键空间,首先您必须考虑您要存储的内容,以及如何在 Cassandra 中表示它。例如,让我们存储一个用户列表。您现在无需考虑更多;您只需要定义您的列族的名称。单个列和值可以并且将在运行时定义。
为此,请定义一个新的键空间和一个新的列族。每个列族都类似于关系型数据库中的表;它包含零个或多个列。每个列又是一个名称-值对。因此,通过如下定义您的键空间,您基本上是在说您想要存储有关用户的信息
<Keyspace Name="People"> <ColumnFamily Name="Users" CompareWith="BytesType"/> </Keyspace> </Keyspaces>
与关系型数据库一样,您将能够存储有关这些用户的许多信息字段。与关系型数据库不同,您无需从一开始就定义它们。同样与关系型数据库不同的是,您只能通过您用于此列族的键来检索有关用户的信息。因此,如果您使用电子邮件地址作为“Users”列族的键,您将需要一个地址才能执行某些操作;拥有人员的姓名对您没有太大帮助。
Cassandra 将信息存储为字节集;没有内部类型。但是,您可以(并且应该)向 Cassandra 指示数据应如何排序。指定“比较器”允许您模拟不同类型的存储。更重要的是,它决定了您接收结果的顺序。这是因为在 Cassandra 中检索数据时没有等效的 ORDER BY;您需要在配置文件中确定顺序并指定它。有点令人惊讶的是,排序是在写入数据时完成的,而不是在读取数据时完成的。在示例“Users”列族的情况下,您只需按字节顺序检索它们。
如果您将上面的 <Keyspace> 部分放在 storage-conf.xml 文件中的 <Keyspaces> 标记内并重新启动 Cassandra,您会发现它无法启动。(错误日志位于 /var/log/cassandra 中,至少在我的 Ubuntu 安装中是这样。)这是因为您还需要包含其他三个定义:ReplicaPlacementStrategy、ReplicationFactor 和 EndPointSnitch。当您只有一个 Cassandra 节点时,这些定义都不会让您担心,因此我建议您只需从包含的 Keyspace1 键空间中复制它们即可。最后,您的键空间定义的这一部分将如下所示
<Keyspace Name="People"> <ColumnFamily Name="Users" CompareWith="BytesType"/> <ReplicaPlacementStrategy>org.apache.cassandra.locator. ↪RackUnawareStrategy</ReplicaPlacementStrategy> <ReplicationFactor>1</ReplicationFactor> <EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch ↪</EndPointSnitch> </Keyspace>
重新启动 Cassandra,并通过 CLI 重新连接。然后,输入
cassandra> show keyspaces
您的新键空间“People”现在应该出现在列表中
cassandra> show keyspaces Keyspace1 system People
您可以请求描述您的键空间
cassandra> describe keyspace People People.Users Column Family Type: Standard Columns Sorted By: org.apache.cassandra.db.marshal.BytesType@1b22920 Column Family Type: Standard Column Sorted By: org.apache.cassandra.db.marshal.BytesType flush period: null minutes ------
您现在可以看到您的 People 键空间包含一个“Users”列族。有了这个,您就可以开始设置和检索数据了
cassandra> get People.Users['1'] Returned 0 results. cassandra> set People.Users['1']['email'] = 'reuven@lerner.co.il' cassandra> set People.Users['1']['first_name'] = 'Reuven' cassandra> set People.Users['1']['last_name'] = 'Lerner'
在 Cassandra 术语中,您会说您现在已经为一个键('1')、在一个列族(“Users”)和一个键空间(“People”)下设置了三个列值('email'、'first_name' 和 'last_name')。如果您习惯使用 Ruby 或 Python 等语言,您可能会感到有点失望——毕竟,这看起来就像您刚刚设置了一个多级哈希。但这很有道理,因为 Cassandra 是键值存储的超级版本,对吧?
现在,让我们尝试检索数据。您可以使用键来做到这一点
cassandra> get People.Users['1'] => (column=6c6173745f6e616d65, value=Lerner, ↪timestamp=1279024194314000) => (column=66697273745f6e616d65, value=Reuven, ↪timestamp=1279024183326000) => (column=656d61696c, value=reuven@lerner.co.il, timestamp=1279024170585000) Returned 3 results.
请注意,每一列都有其唯一的 ID,并且数据是使用时间戳存储的。当您运行多个 Cassandra 节点时,这些时间戳至关重要,它们会在您不知情的情况下相互更新以实现完全一致性。
您也可以添加其他信息
cassandra> set People.Users['2']['first_name'] = 'Atara' cassandra> set People.Users['2']['last_name'] = 'Lerner-Friedman' cassandra> set People.Users['2']['school'] = 'Yachad' cassandra> set People.Users['3']['first_name'] = 'Shikma' cassandra> set People.Users['3']['last_name'] = 'Lerner-Friedman' cassandra> set People.Users['3']['school'] = 'Yachad'
现在您有了关于三个用户的信息,并且如您所见,您在“Users”列族中使用的列不是由配置文件确定的,而是可以当场添加的。此外,没有规则规定您必须为“email”列设置值;Cassandra 中不存在这种强制执行。但对于关系型数据库老手来说,最令人惊讶的也许是,没有任何方法可以检索所有 last_name 为 'Lerner-Friedman' 或学校名为 'Yachad' 的值。一切都基于键(在这种情况下,我已将其设置为整数);您可以向下钻取,但不能横向钻取,就像这样。
您可以询问 Cassandra 为给定键设置了多少列,但您不会知道这些列是什么
cassandra> count People.Users['1'] 3 columns cassandra> count People.Users['2'] 3 columns
但是,如果您尝试存储有关许多用户的信息,并且这些用户将定期更新其信息,那么 Cassandra 会非常有帮助。
现在您已经掌握了列的要领,我将提及 Cassandra 数据模型中一个特别有趣的部分。您可以定义“超级列”而不是定义列。每个超级列都像一个普通列,只是它可以包含多个列(而不是名称-值对)。为了定义超级列,请将 storage-conf.xml 文件中的 ColumnType 属性设置为“Super”。例如
<ColumnFamily Name="Users" CompareWith="BytesType" ↪ColumnType="Super" />
请注意,如果您使用此更改后的定义重新启动 Cassandra,然后尝试检索People.Users['1'],您可能会收到错误。那是因为您实际上是在没有更改数据的情况下更改了模式,这始终是一个坏主意。现在您可以存储和检索更细粒度的信息
cassandra> set People.Users['1']['address']['city'] = 'Modiin' cassandra> get People.Users['1']['address']['city'] => (column=63697479, value=Modiin, timestamp=1279026442675000)
Cassandra 提供了一种非关系型存储和检索机制(NoSQL 数据库),它具有极大的可扩展性、速度和灵活性。包含超级列(以及我在此处未讨论的超级列族)为您提供了足够的灵活性来存储大量关于许多用户的信息。只要您永远不必在数据库级别搜索主键以外的任何内容或连接来自不同用户的信息,Cassandra 都是一个不错的选择。
话虽如此,与其他非关系型数据库相比,Cassandra 更难理解和管理。我认为时间和精力的投入是值得的,但您不应期望能够像使用 CouchDB 或 MongoDB 那样快速轻松地使用 Cassandra。这个问题的另一方面是,管理允许您微调 Cassandra 网络和一致性的许多方面,直到您达到您感到舒适的水平。
下个月,我将继续探索和讨论 Cassandra,研究将多个 Cassandra 框连接到集群的方法,以及这样做会发生什么。
资源
Cassandra 的主页位于 cassandra.apache.org。您可能会找到对另一个 Cassandra 页面的引用;它最近才“毕业”成为一个成熟的 Apache 项目,而不是一个“孵化器”项目;因此,某些参考文献将会过时。此页面包含下载链接、文档、积极维护的 wiki 以及指向多种语言的论文、教程和驱动程序的链接。
Cassandra 基于 Amazon 的 Dynamo,其原始论文有助于理解一些设计决策。您可以在 www.allthingsdistributed.com/2007/10/amazons_dynamo.html 阅读本文。
两个补充视频讲座描述了 Cassandra,但更侧重于网络存储方面(而不是实际的日常使用),网址为 www.parleys.com/#sl=1&st=5&id=1866 和 vimeo.com/5185526。
最后,尽管我仍然觉得 Cassandra 文档有点不足,但越来越多的博客、教程和用户评价已经出现在 Web 上。我特别喜欢的三个是 Arin Sarkissian 的“什么是 SuperColumn?Cassandra 数据模型简介”(arin.me/blog/wtf-is-a-supercolumn-cassandra-data-model)、Evan Weaver 的“启动并运行 Cassandra”(blog.evanweaver.com/articles/2009/07/06/up-and-running-with-cassandra)和 Dominic Williams 的“HBase vs Cassandra:我们为什么要迁移”(ria101.wordpress.com/2010/02/24/hbase-vs-cassandra-why-we-moved)。
Reuven M. Lerner 是一位资深的 Web 开发人员、架构师和培训师。他是西北大学学习科学专业的博士候选人,研究协作在线社区的设计和分析。Reuven 与他的妻子和三个孩子住在以色列的莫迪因。