我们到Apache Cassandra的​​官方网站​​下载最新版本的Cassandra,在这里写作时最新版本的Cassandra为3.11.4。ApacheCassandra可以在Linux,Unix,Mac OS以及Windows上进行安装,为了可以起见,此处以CentOS为例进行介绍。

Cassandra入门到实战_数据





为什么会诞生 Apache ​​Cassandra​

2007 年 Facebook 为了解决消息收件箱搜索问题( Inbox Search problem)而开始设计 ​​Cassandra​​ 项目。 当时 Facebook 遇到了传统的方法难以解决的超大数据量存储可扩展性问题。具体来说,项目团队需要处理大量的消息副本、消息的反向索引等不同形式的数据,需要处理很多随机读和并发随机写操作。

该团队由 Jeff Hammerbacher 领导,核心工程师包括 Avinash Lakshman,Karthik Ranganathan 和搜索团队的 Prashant Malik 。2008年7月 Cassandra 的代码被作为开源项目发布到 Google Code。虽然代码在2008年作为 Google Code 的一个项目,但是这段时间基本上只有 Facebook 工程师来更新代码,基本上没有形成社区。所以在2009年3月,Cassandra 被转移到 Apache 孵化器项目,并在2010年2月17日,它被投票成为一个顶级项目。 在 Apache Cassandra Wiki 上,您可以找到 committers 列表,其中许多人自 2010/2011 年以来就一直参与该项目。 committers 主要来自 Twitter,LinkedIn,Apple,其中还包括了许多独立开发者。

Cassandra 的设计很大程度受 Amazon Dynamo 的影响,具体可参见​​《Dynamo: Amazon's Highly Available Key-value Store》​​​。2010年由 Facebook 的 Lakshman 和 Malik 在 ACM 首次发表了 Cassandra 的论文 ​​《Cassandra: a decentralized structured storage system》​​,向全世界介绍了 Cassandra。

随着商界对 Cassandra 的兴趣增加,对 Cassandra 的生产支持变得越来越明显。2010年4月 Cassandra 的 Apache 项目主席 Jonathan Ellis 和其同事 Matt Pfeil 成立了一家名为 DataStax 公司(最初名为 Riptano)。DataStax 雇佣了多名 Cassandra Committer,为 Cassandra 项目提供了相关支持,并引领其发展。

Cassandra 的名字由来
在希腊神话里,Cassandra 是特洛伊国王 Priam 和 Hecuba 王后的女儿。Cassandra 非常美丽,以至于阿波罗给了她预见未来的能力。但当她拒绝阿波罗的爱慕的时候,遭到他的诅咒。从此,她依然可以精确地预知未来,但是不会有任何人相信她。Cassandra 预知了她的特洛伊城终将覆灭,但却无力阻止这一悲剧。Cassandra 分布式数据库就据此命名。

Apache Cassandra 特性

分布式和去中心化(Distributed and Decentralized)

Cassandra 是分布式的,这意味着它可以运行在多台机器上,并呈现给用户一个一致的整体。事实上,在一个节点上运行 Cassandra 是没啥用的,虽然我们可以这么做,并且这可以帮助我们了解它的工作机制,但是你很快就会意识到,需要多个节点才能真正了解 Cassandra 的强大之处。它的很多设计和实现让系统不仅可以在多个节点上运行,更为多机架部署进行了优化,甚至一个 Cassandra 集群可以运行在分散于世界各地的数据中心上。你可以放心地将数据写到集群的任意一台机器上,Cassandra 都会收到数据。

对于很多存储系统(比如 MySQL, Bigtable),一旦你开始扩展它,就需要把某些节点设为主节点,其他则作为从节点。但 Cassandra 是无中心的,也就是说每个节点都是一样的。与主从结构相反,Cassandra 的协议是 P2P 的,并使用 gossip 来维护存活或死亡节点的列表。关于 gossip 可以参见​​《分布式原理:一文了解 Gossip 协议》​​。

去中心化这一事实意味着 Cassandra 不会存在单点失效。Cassandra 集群中的所有节点的功能都完全一样, 所以不存在一个特殊的主机作为主节点来承担协调任务。有时这被叫做服务器对称(server symmetry)。

综上所述,Cassandra 是分布式、无中心的,它不会有单点失效,所以支持高可用性。

弹性可扩展(Elastic Scalability)

可扩展性是指系统架构可以让系统提供更多的服务而不降低使用性能的特性。仅仅通过给现有的机器增加硬件的容量、内存进行垂直扩展,是最简单的达到可扩展性的手段。而水平扩展则需要增加更多机器,每台机器提供全部或部分数据,这样所有主机都不必负担全部业务请求。但软件自己需要有内部机制来保证集群中节点间的数据同步。

弹性可扩展是指水平扩展的特性,意即你的集群可以不间断的情况下,方便扩展或缩减服务的规模。这样,你就不需要重新启动进程,不必修改应用的查询,也无需自己手工重新均衡数据分布。在 Cassandra 里,你只要加入新的计算机,Cassandra 就会自动地发现它并让它开始工作。

高可用和容错(High Availability and Fault Tolerance)

从一般架构的角度来看,系统的可用性是由满足请求的能力来量度的。但计算机可能会有各种各样的故障,从硬件器件故障到网络中断都有可能。如何计算机都可能发生这些情况,所以它们一般都有硬件冗余,并在发生故障事件的情况下会自动响应并进行热切换。对一个需要高可用的系统,它必须由多台联网的计算机构成,并且运行于其上的软件也必须能够在集群条件下工作,有设备能够识别节点故障,并将发生故障的中端的功能在剩余系统上进行恢复。

Cassandra 就是高可用的。你可以在不中断系统的情况下替换故障节点,还可以把数据分布到多个数据中心里,从而提供更好的本地访问性能,并且在某一数据中心发生火灾、洪水等不可抗灾难的时候防止系统彻底瘫痪。

可调节的一致性(Tuneable Consistency)

2000年,加州大学伯克利分校的 Eric Brewer 在 ACM 分布式计算原理会议提出了著名的 CAP 定律。CAP 定律表明,对于任意给定的系统,只能在一致性(Consistency)、可用性(Availability)以及分区容错性(Partition Tolerance)之间选择两个。关于 CAP 定律的详细介绍可参见​​《分布式系统一致性问题、CAP定律以及 BASE 理论》​​​以及​​《一篇文章搞清楚什么是分布式系统 CAP 定理》​​。所以 Cassandra 在设计的时候也不得不考虑这些问题,因为分区容错性这个是每个分布式系统必须考虑的,所以只能在一致性和可用性之间做选择,而 Cassandra 的应用场景更多的是为了满足可用性,所以我们只能牺牲一致性了。但是根据 BASE 理论,我们其实可以通过牺牲强一致性获得可用性。

Cassandra 提供了可调节的一致性,允许我们选定需要的一致性水平与可用性水平,在二者间找到平衡点。因为客户端可以控制在更新到达多少个副本之前,必须阻塞系统。这是通过设置副本因子(replication factor)来调节与之相对的一致性级别。

通过副本因子(replication factor),你可以决定准备牺牲多少性能来换取一致性。 副本因子是你要求更新在集群中传播到的节点数(注意,更新包括所有增加、删除和更新操作)。

客户端每次操作还必须设置一个一致性级别(consistency level)参数,这个参数决定了多少个副本写入成功才可以认定写操作是成功的,或者读取过程中读到多少个副本正确就可以认定是读成功的。这里 Cassandra 把决定一致性程度的权利留给了客户自己。

所以,如果需要的话,你可以设定一致性级别和副本因子相等,从而达到一个较高的一致性水平,不过这样就必须付出同步阻塞操作的代价,只有所有节点都被更新完成才能成功返回一次更新。而实际上,Cassandra 一般都不会这么来用,原因显而易见(这样就丧失了可用性目标,影响性能,而且这不是你选择 Cassandra 的初衷)。而如果一个客户端设置一致性级别低于副本因子的话,即使有节点宕机了,仍然可以写成功。

总体来说,Cassandra 更倾向于 CP,虽然它也可以通过调节一致性水平达到 AP;但是不推荐你这么设置。

面向行(Row-Oriented)

Cassandra 经常被看做是一种面向列(Column-Oriented)的数据库,这也并不算错。它的数据结构不是关系型的,而是一个多维稀疏哈希表。稀疏(Sparse)意味着任何一行都可能会有一列或者几列,但每行都不一定(像关系模型那样)和其他行有一样的列。每行都有一个唯一的键值,用于进行数据访问。所以,更确切地说,应该把 Cassandra 看做是一个有索引的、面向行的存储系统。

Cassandra 的数据存储结构基本可以看做是一个多维哈希表。这意味着你不必事先精确地决定你的具体数据结构或是你的记录应该包含哪些具体字段。这特别适合处于草创阶段,还在不断增加或修改服务特性的应用。而且也特别适合应用在敏捷开发项目中,不必进行长达数月的预先分析。对于使用 Cassandra 的应用,如果业务发生变化了,只需要在运行中增加或删除某些字段就行了,不会造成服务中断。

当然, 这不是说你不需要考虑数据。相反,Cassandra 需要你换个角度看数据。在 RDBMS 里, 你得首先设计一个完整的数据模型, 然后考虑查询方式, 而在 Cassandra 里,你可以首先思考如何查询数据,然后提供这些数据就可以了。

灵活的模式(Flexible Schema)

Cassandra 的早期版本支持无模式(schema-free)数据模型,可以动态定义新的列。 无模式数据库(如 Bigtable 和 MongoDB)在访问大量数据时具有高度可扩展性和高性能的优势。 无模式数据库的主要缺点是难以确定数据的含义和格式,这限制了执行复杂查询的能力。

为了解决这些问题,Cassandra 引入了 Cassandra Query Language(CQL),它提供了一种通过类似于结构化查询语言(SQL)的语法来定义模式。 最初,CQL 是作为 Cassandra 的另一个接口,并且基于 Apache Thrift 项目提供无模式的接口。 在这个过渡阶段,术语“模式可选”(Schema-optional)用于描述数据模型,我们可以使用 CQL 的模式来定义。并且可以通过 Thrift API 实现动态扩展以此添加新的列。 在此期间,基础数据存储模型是基于 Bigtable 的。

从 3.0 版本开始,不推荐使用基于 Thrift API 的动态列创建的 API,并且 Cassandra 底层存储已经重新实现了,以更紧密地与 CQL 保持一致。 Cassandra 并没有完全限制动态扩展架构的能力,但它的工作方式却截然不同。 CQL 集合(比如 list、set、尤其是 map)提供了在无结构化的格式里面添加内容的能力,从而能扩展现有的模式。CQL 还提供了改变列的类型的能力,以支持 JSON 格式的文本的存储。

因此,描述 Cassandra 当前状态的最佳方式可能是它支持灵活的模式。

高性能(High Performance)

Cassandra 在设计之初就特别考虑了要充分利用多处理器和多核计算机的性能,并考虑在分布于多个数据中心的大量这类服务器上运行。它可以一致而且无缝地扩展到数百台机器,存储数 TB 的数据。Cassandra 已经显示出了高负载下的良好表现,在一个非常普通的工作站上,Cassandra 也可以提供非常高的写吞吐量。而如果你增加更多的服务器,你还可以继续保持 Cassandra 所有的特性而无需牺牲性能。

Cassandra 的应用场景

我们已经介绍了 Cassandra 的主要特点,对 Cassandra 的长处有了一定的理解。尽管 Cassandra 设计精巧,功能出色,但也不能胜任所有的工作。所以我们来介绍一下 Cassandra 最适合的场景。

大规模部署

你可能不会开着一辆轻型的小卡车去取干洗的衣服,小卡车显然不适合这种工作。Cassandra 的很多精巧设计都专注于高可用、可调一致性、P2P 协议、无缝扩展等,这些都是 Cassandra 的卖点。这些特性在单节点工作时都是没有意义的,更无法实现它的全部能力。

但是,单节点关系数据库在很多情况下可能正是我们需要的。所以你需要做一些评估。考虑你的期望的流量、吞吐需求以及 SAL 等。关于评估没有什么硬性的指标和要求。但如果你认为有几种关系型数据库可以很好地应付你的流量,提供不错的性能,那可能选关系型数据库更好。简单地说,这是因为 RDBMS 更易于在单机上运行,对你来说也更熟悉。

但是,如果你认为需要至少几个节点才能支撑你的业务,那 Cassandra 就是个不错的选择。如果你的应用可能需要数十个节点,那 Cassandra 可能就是个很棒的选择了。

写密集、统计和分析型工作

考虑一下你的应用的读写比例,Cassandra 是为优异的写吞吐量而特别优化的。

许多早期使用 Cassandra 的产品都用于存储用户状态更新、社交网络、建议/评价以及应用统计等。这些都是 Cassandra 很好的应用场景,因为这些应用大都是写多于读的,并且更新可能随时发生并伴有突发的峰值。事实上,支撑应用负载需要很高的多客户线程并发写性能,这正是 Cassandra 的主要特性。

根据项目的 wiki,Cassandra 已经被用于开发了多种不同的应用,包括窗口化的时间序列数据库,用于文档搜索的反向索引,以及分布式任务优先级队列。

地区分布

Cassandra 直接支持多地分布的数据存储,Cassandra 可以很容易配置成将数据分布到多个数据中心的存储方式。如果你有一个全球部署的应用,那么让数据贴近用户会获得不错的性能收益,Cassandra 正适合这种应用场合。

变化的应用

如果你正在“初创阶段”,业务会不断改进,Cassandra 这种灵活的模式的数据模型可能更适合你。这让你的数据库能更快地跟上业务改进的步伐。

谁在使用 Cassandra

Cassandra 在全世界有多达 1500 家公司使用:


    • 苹果的 Cassandra 集群达到 75,000 节点,存储了 10PB 的数据;
    • Netflix 的 Cassandra 集群达到 2,500 个节点,存储了多达 420TB 的数据;
    • 宜搜的 Cassandra 集群达到 270 个节点,存储多达 300TB 的数据;
    • eBay 的 Cassandra 集群达到 100 个节点,存储多达 250TB 的数据;
    • 360 的 Cassandra 集群达到 1500 个节点;
    • 饿了么的 Cassandra 集群达到 100 个节点。


下载,安装并启动

因为这里只是简单介绍Apache Cassandra的使用,所以这里仅安装单机版的Cassandra,在生产环境下应该部署成分布式模式。可以使用下面的命令下载和解压相关的压缩文件:


​$ wget http:​​​​//mirror​​​​.bit.edu.cn​​​​/apache/cassandra/3​​​​.11.4​​​​/apache-cassandra-3​​​​.11.4-bin.​​​​tar​​​​.gz​​​​$ ​​​​tar​​​​-zxf apache-cassandra-3.11.4-bin.​​​​tar​​​​.gz ​​​​$ ​​​​cd​​​​apache-cassandra-3.11.4 ​


在apache-cassandra-3.11.4目录下有很多文件:


​[iteblog@www.iteblog.com apache-cassandra-3.11.4]​​​​# ll​​​​total 528​​​​drwxr-xr-x 2 iteblog iteblog 4096 Apr 2 21:12 bin​​​​-rw-r--r-- 1 iteblog iteblog 4832 Feb 3 06:09 CASSANDRA-14092.txt​​​​-rw-r--r-- 1 iteblog iteblog 366951 Feb 3 06:09 CHANGES.txt​​​​drwxr-xr-x 3 iteblog iteblog 4096 Apr 2 21:12 conf​​​​drwxr-xr-x 4 iteblog iteblog 4096 Apr 2 21:12 doc​​​​drwxr-xr-x 2 iteblog iteblog 4096 Apr 2 21:12 interface​​​​drwxr-xr-x 3 iteblog iteblog 4096 Apr 2 21:12 javadoc​​​​drwxr-xr-x 4 iteblog iteblog 4096 Apr 2 21:12 lib​​​​-rw-r--r-- 1 iteblog iteblog 11609 Feb 3 06:09 LICENSE.txt​​​​-rw-r--r-- 1 iteblog iteblog 112586 Feb 3 06:09 NEWS.txt​​​​-rw-r--r-- 1 iteblog iteblog 2811 Feb 3 06:09 NOTICE.txt​​​​drwxr-xr-x 3 iteblog iteblog 4096 Apr 2 21:12 pylib​​​​drwxr-xr-x 4 iteblog iteblog 4096 Apr 2 21:12 tools​


各个文件或目录介绍如下:


  • bin:这个目录下包含启动Cassandra以及客户端相关操作的初始化文件,包括查询语言shell(cqlsh)以及命令行界面(CLI)等客户端。同时还包含运行nodetool的相关脚本,操作SSTables的工具等等。
  • conf:这个目录下面包含了Cassandra的配置文件。必须包含的配置文件包括:assandra.yaml以及logback.xml,这两个文件分别是运行Cassandra必须包含的配置文件和日志相关配置文件。同时还包含Cassandra网络拓扑配置文件等。
  • doc:这个目录包含CQL相关的html文档。
  • 接口:这个文件夹下面只包含一个称为cassandra.thrift的文件。这个文件定义了基于Thrift语法的RPC API,这个Thrift主要用于Java,C ++,PHP,Ruby,Python,Perl,以及C#等语言中创建相关客户端,但是在CQL出现之后,Thrift API在Cassandra 3.2版本开始标记为已弃用,并且会在Cassandra 4.0版本删除。
  • javadoc:这个文件夹包含使用JavaDoc工具生成的html文档。
  • lib:这个目录包含Cassandra运行时需要的所有外部库。
  • pylib:这个目录包含cqlsh运行时需要使用的Python库。
  • 工具:这个目录包含用于维护Cassandra先前的相关工具。
  • NEWS.txt:这个文件包含当前及之前版本的发行说明相关信息。
  • CHANGES.txt:这个文件主要包含一些bug修复信息。

启动Cassandra

上面已经简单介绍了Cassandra发行包里面的一些文件和目录用途。因为我们主要简单地介绍了Cassandra的使用,所以我们使用了替代的配置。下面我们来启动Cassandra服务,具体如下:


​[iteblog@www.iteblog.com apache-cassandra-3.11.4]​​​​# bin/cassandra​


运行上面命令会在命令行里面输出一堆的日志,但是我们如何判断cassandra服务已经启动了呢?答案是使用nodetool工具,如下:


​[iteblog@www.iteblog.com apache-cassandra-3.11.4]​​​​# bin/nodetool status​​​​Datacenter: datacenter1​​​​=======================​​​​Status=Up​​​​/Down​​​​|/ State=Normal​​​​/Leaving/Joining/Moving​​​​-- Address Load Tokens Owns (effective) Host ID Rack​​​​UN 127.0.0.1 160.88 KiB 256 100.0% 49f4470f-396b-4d50-bcd6-3da2d9370167 rack1​


并且会在apache-cassandra-3.11.4目录下生成数据和日志两个目录。如果我们看到上面的信息,那么我们的测试服务已经正常启动了。

使用CQL Shell

上面我们已经启动了Cassandra服务,我们可以使用CQL Shell来进行一些操作。从名字就可以切削,CQL(Cassandra查询语言)其实和我们熟悉的SQL很类似,我们可以通过它使用类似SQL的语言来和Cassandra进行交互。需要注意的是,CQL和SQL是不兼容的,CQL表示SQL的一些关键功能,例如JOIN等,这个在Cassandra下不能实现;同时,CQL也不是SQL的子集。为了使用CQL ,可以使用以下命令:


​[iteblog@www.iteblog.com apache-cassandra-3.11.4]​​​​# bin/cqlsh​​​​Connected to Test Cluster at 127.0.0.1:9042.​​​​[cqlsh 5.0.1 | Cassandra 3.11.4 | CQL spec 3.4.4 | Native protocol v4]​​​​Use HELP ​​​​for​​​​help.​​​​cqlsh>​


在启动cqlsh的时候我们并没有指定需要连接的串口和端口,这种情况下cqlsh会自动检测本机及相关端口,因为我们在前面已经启动了Cassandra服务,所以cqlsh可以正确连接到这个运用。从顶部的命令可以修剪cqlsh连接到称为测试集群的副本,这是由​​conf/cassandra.yaml​​​文件里面的​​cluster_name​​参数决定的,替换为测试集群。

当然,我们也可以在启动cqlsh的时候指定计数器和相应的端口,如下:


​[iteblog@www.iteblog.com apache-cassandra-3.11.4]​​​​# bin/cqlsh localhost 9042​


上面的命令执行效果和不指定一样。我们也可以将节点和端口相关的信息保存到环境变量 ​​$CQLSH_HOST​​​ 和 ​​$CQLSH_PORT​​​ 里面,这个在我们需要经常连接到特定节点的情况下非常有用。更多关于 cqlsh 命令支持的参数可以使用 ​​bin/cqlsh -help​​。

基本的 cqlsh 命令

cqlsh 支持很多操作 Cassandra 的基本命令,我们可以在 cqlsh 里面使用 ​​HELP​​​ 或 ​​?​​ 命令查看所有支持的命令:


​cqlsh> HELP​​​​Documented shell commands:​​​​===========================​​​​CAPTURE CLS COPY DESCRIBE EXPAND LOGIN SERIAL SOURCE UNICODE​​​​CLEAR CONSISTENCY DESC EXIT HELP PAGING SHOW TRACING​​​​CQL help topics:​​​​================​​​​AGGREGATES CREATE_KEYSPACE DROP_TRIGGER TEXT ​​​​ALTER_KEYSPACE CREATE_MATERIALIZED_VIEW DROP_TYPE TIME ​​​​ALTER_MATERIALIZED_VIEW CREATE_ROLE DROP_USER TIMESTAMP​​​​ALTER_TABLE CREATE_TABLE FUNCTIONS TRUNCATE ​​​​ALTER_TYPE CREATE_TRIGGER GRANT TYPES ​​​​ALTER_USER CREATE_TYPE INSERT UPDATE ​​​​APPLY CREATE_USER INSERT_JSON USE ​​​​ASCII DATE INT UUID ​​​​BATCH DELETE JSON ​​​​BEGIN DROP_AGGREGATE KEYWORDS ​​​​BLOB DROP_COLUMNFAMILY LIST_PERMISSIONS​​​​BOOLEAN DROP_FUNCTION LIST_ROLES ​​​​COUNTER DROP_INDEX LIST_USERS ​​​​CREATE_AGGREGATE DROP_KEYSPACE PERMISSIONS ​​​​CREATE_COLUMNFAMILY DROP_MATERIALIZED_VIEW REVOKE ​​​​CREATE_FUNCTION DROP_ROLE SELECT ​​​​CREATE_INDEX DROP_TABLE SELECT_JSON​


如果需要查看特定命令的帮助,可以使用 ​​HELP <command>​​​。需要注意的是,很多 cqlsh 命令并不接收相关的参数,当我们使用这些命令时,其输出为当前的设置,比如 ​​CONSISTENCY​​​, ​​EXPAND​​​ 和 ​​PAGING​​ 命令,如下:


​cqlsh> CONSISTENCY​​​​Current​​​​consistency ​​​​level​​​​is​​​​ONE.​​​​cqlsh> EXPAND​​​​Expanded ​​​​output​​​​is​​​​currently disabled. Use EXPAND ​​​​ON​​​​to​​​​enable.​​​​cqlsh> PAGING​​​​Query paging ​​​​is​​​​currently enabled. Use PAGING ​​​​OFF​​​​to​​​​disable​​​​Page ​​​​size​​​​: 100​


在 cqlsh 里面查看环境变量

我们可以使用 ​​DESCRIBE​​ 命令,来查看一些集群的一些环境变量的值。下面命令查看当前集群的情况


​cqlsh> DESCRIBE CLUSTER;​​​​Cluster: Test Cluster​​​​Partitioner: Murmur3Partitioner​


​DESCRIBE CLUSTER​​ 显示了集群的名字以及采用的 Partitioner ,Cassandra 1.2 版本开始默认为 Murmur3Partitioner,其他可选的 Partitioner 有 RandomPartitioner(Cassandra 1.2 版本之前默认的 Partitioner)、OrderPreservingPartitioner 以及 ByteOrderedPartitioner 等。

如果我们需要查看集群里面可用的 keyspaces,可以使用下面命令:


​cqlsh> DESCRIBE KEYSPACES;​​​​system_traces system_schema system_auth system system_distributed​


上面命令将系统自带的 keyspaces 都显示出来了,如果我们自己创建了 keyspaces,也会在这里面显示。

可以使用下面命令查看 cqlsh、Cassandra 以及 protocol 的版本:


​cqlsh> SHOW VERSION;​​​​[cqlsh 5.0.1 | Cassandra 3.11.4 | CQL spec 3.4.4 | Native protocol v4]​


事实上,这些版本在启动 cqlsh 的时候就显示了。

通过 cqlsh 创建 keyspace

Cassandra 里面的 keyspace 和关系型数据库里面的 database 概念类似的,一个 keyspace 可以包含一个或多个 tables 或 column families。当我们启动 cqlsh 时没有指定 keyspace,那么命令提示符为 ​​cqlsh>​​​,我们可以使用 ​​CREATE KEYSPACE​​ 命令来创建 keyspace,具体如下:


​cqlsh> CREATE KEYSPACE iteblog_keyspace WITH replication = {​​​​'class'​​​​: ​​​​'SimpleStrategy'​​​​, ​​​​'replication_factor'​​​​: 1};​​​​cqlsh>​


上面命令创建了名为 iteblog_keyspace 的 keyspace;并且采用 SimpleStrategy 进行副本复制,因为我们这个测试集群只有单个节点,所以这里设置的副本因子(replication factor)为 1。如果是生产环境,千万别把副本因子设置为 1,比较常见的副本因子为 3。其他可选的副本复制策略出了 SimpleStrategy 还有 NetworkTopologyStrategy 和 OldNetworkTopologyStrategy,具体什么含义这里还不深入介绍,后面会起单独一篇文章进行详细介绍。

注意,cqlsh 自带了命令提示功能,当我们输入 CREATE KEYSPACE iteblog_keyspace 时,按上键盘上的 ​Tab​ 键,cqlsh 会自动给我们不全到 ​CREATE KEYSPACE iteblog_keyspace WITH replication = {'class': '​,这时候我们再按 ​Tab​ 键,会显示出支持的所有副本复制策略。具体大家可以去试试。

创建完 keyspace 之后,我们可以使用 ​​DESCRIBE KEYSPACE​​ 命令来查看这个 keyspace:


​cqlsh> DESCRIBE KEYSPACE iteblog_keyspace;​​​​CREATE​​​​KEYSPACE iteblog_keyspace ​​​​WITH​​​​replication = {​​​​'class'​​​​: ​​​​'SimpleStrategy'​​​​, ​​​​'replication_factor'​​​​: ​​​​'1'​​​​} ​​​​AND​​​​durable_writes = ​​​​true​​​​;​


现在我们可以使用 ​​USE​​ 命令来切换到这个 keyspace :


​cqlsh> USE iteblog_keyspace;​​​​cqlsh:iteblog_keyspace>​


从上面的输出可以看出,keyspace 已经切换到 iteblog_keyspace 了。

通过 cqlsh 创建表

接下来,我们通过 cqlsh 来创建一张表:


​cqlsh> use iteblog_keyspace;​​​​cqlsh:iteblog_keyspace> ​​​​CREATE​​​​TABLE​​​​iteblog_user (first_name text , last_name text, ​​​​PRIMARY​​​​KEY​​​​(first_name)) ;​


通过上面的命令,我们在 iteblog_keyspace 下面创建了一张名为 iteblog_user 的表。其中包含了 first_name 和 last_name 两个字段,类型都是 text,并且 first_name 是这张表的 PRIMARY KEY。当然,我们也可以通过下面命令在 iteblog_keyspace 里面建表:


​cqlsh> ​​​​CREATE​​​​TABLE​​​​iteblog_keyspace.iteblog_user(first_name text , last_name text, ​​​​PRIMARY​​​​KEY​​​​(first_name)) ;​


效果和上面一样。我们可以使用 ​​DESCRIBE TABLE​​ 命令查看建表语句:


​cqlsh:iteblog_keyspace> DESCRIBE ​​​​TABLE​​​​iteblog_user;​​​​CREATE​​​​TABLE​​​​iteblog_keyspace.iteblog_user (​​​​​​​​first_name text ​​​​PRIMARY​​​​KEY​​​​,​​​​​​​​last_name text​​​​) ​​​​WITH​​​​bloom_filter_fp_chance = 0.01​​​​​​​​AND​​​​caching = {​​​​'keys'​​​​: ​​​​'ALL'​​​​, ​​​​'rows_per_partition'​​​​: ​​​​'NONE'​​​​}​​​​​​​​AND​​​​comment = ​​​​''​​​​​​​​AND​​​​compaction = {​​​​'class'​​​​: ​​​​'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'​​​​, ​​​​'max_threshold'​​​​: ​​​​'32'​​​​, ​​​​'min_threshold'​​​​: ​​​​'4'​​​​}​​​​​​​​AND​​​​compression = {​​​​'chunk_length_in_kb'​​​​: ​​​​'64'​​​​, ​​​​'class'​​​​: ​​​​'org.apache.cassandra.io.compress.LZ4Compressor'​​​​}​​​​​​​​AND​​​​crc_check_chance = 1.0​​​​​​​​AND​​​​dclocal_read_repair_chance = 0.1​​​​​​​​AND​​​​default_time_to_live = 0​​​​​​​​AND​​​​gc_grace_seconds = 864000​​​​​​​​AND​​​​max_index_interval = 2048​​​​​​​​AND​​​​memtable_flush_period_in_ms = 0​​​​​​​​AND​​​​min_index_interval = 128​​​​​​​​AND​​​​read_repair_chance = 0.0​​​​​​​​AND​​​​speculative_retry = ​​​​'99PERCENTILE'​​​​;​​​​cqlsh:iteblog_keyspace>​


​DESCRIBE TABLE​​ 命令将建表语句以格式化的形式显示出来。除了我们制定的设置,还包含了许多默认的设置,这里我们先不纠结这些设置的含义。

通过 cqlsh 往表里面读写数据

到现在,我们已经创建好 keyspace 和 table 了,我们可以往表里面插入一些数据,看下一切是不是正常。


​cqlsh:iteblog_keyspace> ​​​​INSERT​​​​INTO​​​​iteblog_user (first_name, last_name) ​​​​VALUES​​​​(​​​​'iteblog'​​​​, ​​​​'Hadoop'​​​​);​​​​cqlsh:iteblog_keyspace> ​​​​INSERT​​​​INTO​​​​iteblog_user (first_name, last_name) ​​​​VALUES​​​​(​​​​'Zhang'​​​​, ​​​​'San'​​​​);​​​​cqlsh:iteblog_keyspace> ​​​​INSERT​​​​INTO​​​​iteblog_user (first_name) ​​​​VALUES​​​​(​​​​'Wu'​​​​);​


上面语句我们往 iteblog_user 表里面插入三条数据,其中最后一条数据只指定了 key,last_name 没有值。现在我们可以使用 ​​SELECT COUNT​​ 语句查看上面的数据是否插入成功


​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​COUNT​​​​(*) ​​​​FROM​​​​iteblog_user;​​​​​​​​count​​​​-------​​​​​​​​3​​​​(1 ​​​​rows​​​​)​​​​Warnings :​​​​Aggregation query used without partition ​​​​key​


可以看出 iteblog_user 表里面已经有3条数据了。我们可以使用下面命令将这条数据查询出来:


​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user;​​​​​​​​first_name | last_name​​​​------------+-----------​​​​​​​​iteblog | Hadoop​​​​​​​​Wu | ​​​​null​​​​​​​​Zhang | San​​​​(3 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name=​​​​'iteblog'​​​​;​​​​​​​​first_name | last_name​​​​------------+-----------​​​​​​​​iteblog | Hadoop​​​​(1 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace>​


可以看出,first_name 为 Wu 对应的 last_name 没数据直接显示 null 了,在 Cassandra 里面的这个代表对应的列没有数据,在底层存储是不占用空间的,而在常见的关系型数据库里面是占一定空间的。

注意,在 cqlsh 里面查询数据如果超过 10,000 行,那么只会显示 10,000,这是 cqlsh 的限制。

我们可以使用 ​​DELETE​​ 命令删除一些列,比如我们删除 last_name 列,


​cqlsh:iteblog_keyspace> ​​​​DELETE​​​​last_name ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name=​​​​'iteblog'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name=​​​​'iteblog'​​​​;​​​​​​​​first_name | last_name​​​​------------+-----------​​​​​​​​iteblog | ​​​​null​​​​(1 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace>​


可以看出 last_name 列已经成功被删除了。

我们也可以删除一整行数据,如下:


​cqlsh:iteblog_keyspace> ​​​​DELETE​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name=​​​​'iteblog'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name=​​​​'iteblog'​​​​;​​​​​​​​first_name | last_name​​​​------------+-----------​​​​(0 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace>​


可以看到 key 为 iteblog 的数据已经成功被删除了。

insert/update 相当于 upsert

如果我们插入数据对应的 key 在 Cassandra 已经存在了,这时候 Cassandra 并不会在原来数据位置上修改数据,而是会新写入一份数据,旧的数据会被 Cassandra 删除。


​cqlsh:iteblog_keyspace> ​​​​INSERT​​​​INTO​​​​iteblog_user (first_name, last_name) ​​​​VALUES​​​​(​​​​'Wu'​​​​, ​​​​'Shi'​​​​);​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user;​​​​​​​​first_name | last_name​​​​------------+-----------​​​​​​​​Wu | Shi​​​​​​​​Zhang | San​​​​(2 ​​​​rows​​​​)​


可以看见,key 为 Wu 的数据对应的 last_name 已经有值了。

如果我们使用 UPDATE 命令往表里面更新不存在的数据会发生什么呢?答案是会插入新的数据。


​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user;​​​​​​​​first_name | last_name​​​​------------+-----------​​​​​​​​Wu | Shi​​​​​​​​Zhang | San​​​​(2 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​last_name = ​​​​'Si'​​​​WHERE​​​​first_name = ​​​​'Li'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user;​​​​​​​​first_name | last_name​​​​------------+-----------​​​​​​​​Wu | Shi​​​​​​​​Zhang | San​​​​​​​​Li | Si​​​​(3 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace>​


可见,key 为 Li 的数据被插入到表中了,更新之前不存在。

清空或删除表

如果我们确实想清空一张表,我们也可以使用 ​​TRUNCATE​​​ 命令;使用 ​​DROP TABLE​​ 命令可以删除一张表。


​cqlsh:iteblog_keyspace> ​​​​TRUNCATE​​​​iteblog_user;​​​​cqlsh:iteblog_keyspace> ​​​​DROP​​​​TABLE​​​​iteblog_user;​


到目前为止,我们已经学会了 cqlsh 的一些简单的命令。后面我们将介绍 Cassandra 底层的数据模型,敬请关注。

细心的同学可能已经发现我们在 cqlsh 里面移动键盘里面的上下键可以看到过去敲过的命令。这是因为 Cassandra 会在用户的 home 目录下生成名为 .cassandra 的文件夹,里面有个 cqlsh_history 文件,里面以文本形式记录了我们执行的命令,这个和 Linux 的 .bash_history 文件类似。

定义 static column

在表中将某个列定义为 STATIC 很简单,只需要在列的最后面加上 STATIC 关键字,具体如下:


​CREATE​​​​TABLE​​​​"iteblog_users_with_status_updates"​​​​(​​​​​​​​"username"​​​​text,​​​​​​​​"id"​​​​timeuuid,​​​​​​​​"email"​​​​text ​​​​STATIC​​​​,​​​​​​​​"encrypted_password"​​​​blob ​​​​STATIC​​​​,​​​​​​​​"body"​​​​text,​​​​​​​​PRIMARY​​​​KEY​​​​(​​​​"username"​​​​, ​​​​"id"​​​​)​​​​);​


iteblog_users_with_status_updates 表中我们将 email 和 encrypted_password 两个字段设置为 STATIC 了,这意味着同一个 username 只会有一个 email 和 encrypted_password 。

注意,不是任何表都支持给列加上 STATIC 关键字的,静态列有以下限制。


  • 如果表没有定义 Clustering columns(又称 Clustering key),这种情况是不能添加静态列的。如下:

​cqlsh:iteblog_keyspace> ​​​​CREATE​​​​TABLE​​​​"iteblog_users_with_status_updates_invalid"​​​​(​​​​                    ​​​​...   ​​​​"username"​​​​text,​​​​                    ​​​​...   ​​​​"id"​​​​timeuuid,​​​​                    ​​​​...   ​​​​"email"​​​​text ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"encrypted_password"​​​​blob ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"body"​​​​text,​​​​                    ​​​​...   ​​​​PRIMARY​​​​KEY​​​​(​​​​"username"​​​​)​​​​                    ​​​​... );​​​​InvalidRequest: Error ​​​​from​​​​server: code=2200 [Invalid query] message=​​​​"Static columns are only useful (and thus allowed) if the table has at least one clustering column"​

iteblog_users_with_status_updates_invalid 表只有 PRIMARY KEY,没有定义 clustering column,不支持创建 Static columns。这是因为静态列在同一个 partition key 存在多行的情况下才能达到最优情况,而且行数越多效果也好。但是如果没有定义 clustering column,相同 PRIMARY KEY 的数据在同一个分区里面只存在一行数据,本质上就是静态的,所以没必要支持静态列。
如果建表的时候指定了 COMPACT STORAGE,这时候也不允许存在静态列:

​cqlsh:iteblog_keyspace> ​​​​CREATE​​​​TABLE​​​​"iteblog_users_with_status_updates_invalid"​​​​(​​​​                    ​​​​...   ​​​​"username"​​​​text,​​​​                    ​​​​...   ​​​​"id"​​​​timeuuid,​​​​                    ​​​​...   ​​​​"email"​​​​text ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"encrypted_password"​​​​blob ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"body"​​​​text,​​​​                    ​​​​...   ​​​​PRIMARY​​​​KEY​​​​(​​​​"username"​​​​, ​​​​"id"​​​​)​​​​                    ​​​​... )​​​​WITH​​​​COMPACT STORAGE;​​​​InvalidRequest: Error ​​​​from​​​​server: code=2200 [Invalid query] message=​​​​"Static columns are not supported in COMPACT STORAGE tables"​


如果列是 partition key/Clustering columns 的一部分,那么这个列不能说明为静态列:

​cqlsh:iteblog_keyspace> ​​​​CREATE​​​​TABLE​​​​"iteblog_users_with_status_updates_invalid"​​​​(​​​​                    ​​​​...   ​​​​"username"​​​​text,​​​​                    ​​​​...   ​​​​"id"​​​​timeuuid ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"email"​​​​text ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"encrypted_password"​​​​blob ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"body"​​​​text,​​​​                    ​​​​...   ​​​​PRIMARY​​​​KEY​​​​(​​​​"username"​​​​, ​​​​"id"​​​​)​​​​                    ​​​​... );​​​​InvalidRequest: Error ​​​​from​​​​server: code=2200 [Invalid query] message=​​​​"Static column id cannot be part of the PRIMARY KEY"​​​​cqlsh:iteblog_keyspace> ​​​​CREATE​​​​TABLE​​​​"iteblog_users_with_status_updates_invalid"​​​​(​​​​                    ​​​​...   ​​​​"username"​​​​text,​​​​                    ​​​​...   ​​​​"id"​​​​timeuuid,​​​​                    ​​​​...   ​​​​"email"​​​​text ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"encrypted_password"​​​​blob ​​​​STATIC​​​​,​​​​                    ​​​​...   ​​​​"body"​​​​text,​​​​                    ​​​​...   ​​​​PRIMARY​​​​KEY​​​​((​​​​"username"​​​​, ​​​​"id"​​​​), email)​​​​                    ​​​​... );​​​​InvalidRequest: Error ​​​​from​​​​server: code=2200 [Invalid query] message=​​​​"Static column email cannot be part of the PRIMARY KEY"​



给静态列的表插入数据

含有静态列的表插入数据和正常表类似,比如我们现在往 iteblog_users_with_status_updates 导入数据:


​cqlsh:iteblog_keyspace> ​​​​INSERT​​​​INTO​​​​"iteblog_users_with_status_updates"​​​​​​​​... (​​​​"username"​​​​, ​​​​"id"​​​​, ​​​​"email"​​​​, ​​​​"encrypted_password"​​​​, ​​​​"body"​​​​)​​​​​​​​... ​​​​VALUES​​​​(​​​​​​​​... ​​​​'iteblog'​​​​,​​​​​​​​... NOW(),​​​​​​​​... ​​​​'iteblog_hadoop@iteblog.com'​​​​,​​​​​​​​... 0x877E8C36EFA827DBD4CAFBC92DD90D76,​​​​​​​​... ​​​​'Learning Cassandra!'​​​​​​​​... );​​​​cqlsh:iteblog_keyspace> ​​​​select​​​​username, email, encrypted_password, body ​​​​from​​​​iteblog_users_with_status_updates;​​​​​​​​username | email | encrypted_password | body​​​​----------+----------------------------+------------------------------------+---------------------​​​​​​​​iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra!​​​​(1 ​​​​rows​​​​)​


我们成功的插入一条数据了。但是上面的插入语句做了两件事:


  • 所有 username 为 iteblog 数据中的 email 和 encrypted_password 都被设置为 iteblog_hadoop@iteblog.com 和 0x877e8c36efa827dbd4cafbc92dd90d76 了。
  • 在 iteblog 所在的分区中新增了 body 内容为 Learning ​​Cassandra​​! 的记录。

现在我们再往表中插入一条数据,如下:


​cqlsh:iteblog_keyspace> ​​​​INSERT​​​​INTO​​​​"iteblog_users_with_status_updates"​​​​​​​​... (​​​​"username"​​​​, ​​​​"id"​​​​, ​​​​"body"​​​​)​​​​​​​​... ​​​​VALUES​​​​(​​​​'iteblog'​​​​, NOW(), ​​​​'I love Cassandra!'​​​​);​​​​cqlsh:iteblog_keyspace> ​​​​select​​​​username, email, encrypted_password, body ​​​​from​​​​iteblog_users_with_status_updates;​​​​​​​​username | email | encrypted_password | body​​​​----------+----------------------------+------------------------------------+---------------------​​​​​​​​iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra!​​​​​​​​iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | I love Cassandra!​​​​(2 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace>​


可以看到,这次插入数据的时候,我们并没有指定 email 和 encrypted_password,但是从查询结果可以看出,新增加的行 email 和 encrypted_password 的值和之前是一样的!

现在由于某些原因,用户修改了自己的 email,我们来看看会发生什么事:


​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_users_with_status_updates ​​​​SET​​​​email = ​​​​'iteblog@iteblog.com'​​​​​​​​... ​​​​WHERE​​​​username = ​​​​'iteblog'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​select​​​​username, email, encrypted_password, body ​​​​from​​​​iteblog_users_with_status_updates;​​​​​​​​username | email | encrypted_password | body​​​​----------+---------------------+------------------------------------+---------------------​​​​​​​​iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra!​​​​​​​​iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | I love Cassandra!​​​​(2 ​​​​rows​​​​)​


从上面查询这输出的结果可以看出, username 为 iteblog 的 email 全部修改成一样的了!这就是静态列的强大之处。

现在表中存在了用户的邮箱和密码等信息,如果我们前端做了个页面支持用户修改自己的邮箱和密码,这时候我们的后台系统需要获取到现有的邮箱和密码,具体如下:


​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​"username"​​​​, ​​​​"email"​​​​, ​​​​"encrypted_password"​​​​​​​​... ​​​​FROM​​​​"iteblog_users_with_status_updates"​​​​​​​​... ​​​​WHERE​​​​"username"​​​​= ​​​​'iteblog'​​​​;​​​​​​​​username | email | encrypted_password​​​​----------+---------------------+------------------------------------​​​​​​​​iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76​​​​​​​​iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76​​​​(2 ​​​​rows​​​​)​


可以看出,表中有多少行 username 为 iteblog 的数据将会输出多少行邮箱和密码,这肯定不是我们想要的。这时候我们可以在查询的时候加上 DISTINCT 关键字,如下:


​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​DISTINCT​​​​"username"​​​​, ​​​​"email"​​​​, ​​​​"encrypted_password"​​​​​​​​... ​​​​FROM​​​​"iteblog_users_with_status_updates"​​​​​​​​... ​​​​WHERE​​​​"username"​​​​= ​​​​'iteblog'​​​​;​​​​​​​​username | email | encrypted_password​​​​----------+---------------------+------------------------------------​​​​​​​​iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76​​​​(1 ​​​​rows​​​​)​


这样不管表中有多少行 username 为 iteblog 的数据,最终都会显示一行数据。注意,虽然我们加了 DISTINCT 关键字,但是 Cassandra 并不是将 username 为 iteblog 的数据全部拿出来,然后再去重的,因为静态列本来在底层就存储了一份,所以没必要去重。

静态列的意义

到这里,我们已经了解了 Cassandra 中静态列的创建、使用等。那静态列有什么意义呢?因为 Cassandra 中是不支持 join 的,静态列相当于把两张表进行了 join 操作。

那什么时候建议使用静态列呢?如果两张表关联度很大,而且我们经常需要同时查询这两张表,那这时候就可以考虑使用静态列了。

数字数据类型(Numeric Data Types)

CQL 支持的数字数据类型包括整型和浮点型,这些数据类型和 Java 的标准数据类型类似。包括以下几种:


  • int:32位有符号整型,和 Java 中的 int 类似;
  • bigint:64位长整型,和 Java 中的 long 类似;
  • smallint:16位有符号整型,和 Java 中的 short 类似,Apache ​​Cassandra​​ 2.2 开始引入;
  • tinyint:8位有符号整型,和 Java 中的 tinyint 类似,Apache ​​Cassandra​​ 2.2 开始引入;
  • varint:可变精度有符号整数,和 Java 中的 java.math.BigInteger 类似;
  • float:32位 IEEE-754 浮点型,和 Java 中的 float 类似;
  • double:64位 IEEE-754 浮点型,和 Java 中的 double 类似;
  • decimal:可变精度的 decimal,和 Java 中的 java.math.BigDecimal 类似。

注意,枚举类型(enumerated types)虽然在很多语言中都提供,但是在 CQL 是不提供的。在 CQL 中存储枚举常见的方法是使用 String,使用 Enum.name() 将枚举转换成 text,然后使用 Enum.valueOf() 将 text 转换成对应的枚举类型。

文本数据类型(Textual Data Types)

CQL 中提供了两种数据类型用于存放文本类型,其中一种我们在前面的几篇文章里面已经使用过了,也就是 text。


  • text, varchar:UTF-8编码的字符串,这个在 CQL 中使用的比较普遍;
  • ascii:ASCII字符串。

时间和标识符数据类型(Time and Identity Data Types)


  • timestamp:时间可以使用64位有符号的整数表示,但是一般为了可读性,我们会选择支持 ISO 8601 标准的时间戳表示。比如下面的几种数据表示都是可以的:

​2019-04-15 20:05-0700​​​​2019-04-15 20:05:07-0700​​​​2019-04-15 20:05:07.013-0700​​​​2019-04-15T20:05-0700​​​​2019-04-15T20:05:07-0700​​​​2019-04-15T20:05:07.013+-0700​

建议在使用时间戳的时候都指定时区,而不是依赖系统的时区。
date, time:在 Apache Cassandra 2.1 版本之前只支持 timestamp 类型,里面包含了日期和时间;从 Cassandra 2.2 版本开始引入了 date 和 time 时间类型,分别表示日期和时间。和 timestamp 一样,这个也是支持 ISO 8601 标准的。 uuid:通用唯一识别码(universally unique identifier,UUID)是128位数据类型,其实现包含了很多种类型,其中最有名的为 Type 1 和 Type 4。CQL 中的 uuid 实现是 Type 4 UUID,其实现完全是基于随机数的。UUID 的数据类似于 ab7c46ac-c194-4c71-bb03-0f64986f3daa,uuid 类型通常用作代理键,可以单独使用,也可以与其他值组合使用。由于 UUID 的长度有限,因此并不能绝对保证它们是唯一的。我们可以在 CQL 中使用 ​​uuid()​​ 获取 Type 4 UUID。 timeuuid:这个是 Type 1 UUID,它的实现基于计算机的 MAC 地址,系统时间和用于防止重复的序列号。CQL 中提供了 ​​now()​​, ​​dateOf()​​ 以及 ​​unixTimestampOf()​​ 等函数来操作 timeuuid 数据类型。由于这些简便的函数,timeuuid 的使用频率比 uuid 数据类型多。

集合数据类型

set

这种数据类型可以存储集合数据类型,set 里面的元素存储是无序的,但是 cql 返回的数据是有序的。set 里面可以存储前面介绍的数据类型,也可以是用户自定义数据类型,甚至是其他集合类型。

为了介绍这个类型的使用,我们使用 ​​《Apache Cassandra 快速入门指南(Quick Start)》​​ 文章中的 iteblog_user 表进行说明。假设我们需要在这张表里面添加 email 信息,如下:


​cqlsh:iteblog_keyspace> ​​​​ALTER​​​​TABLE​​​​iteblog_user ​​​​ADD​​​​emails ​​​​set​​​​<text>;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name​​​​------------+--------+-----------​​​​​​​​Wu | ​​​​null​​​​| Shi​​​​(1 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​emails = {​​​​'iteblog@iteblog.com'​​​​} ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name​​​​------------+-------------------------+-----------​​​​​​​​Wu | {​​​​'iteblog@iteblog.com'​​​​} | Shi​​​​(1 ​​​​rows​​​​)​


上面我们给 first_name 为 Wu 的数据添加了 email 信息。如果我们还需要往里面加一些 email 信息,可以使用下面语法进行:


​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​emails = emails + {​​​​'cassandra@iteblog.com'​​​​} ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name​​​​------------+--------------------------------------------------+-----------​​​​​​​​Wu | {​​​​'cassandra@iteblog.com'​​​​, ​​​​'iteblog@iteblog.com'​​​​} | Shi​​​​(1 ​​​​rows​​​​)​


可见 first_name 为 Wu 的记录已经添加了两条 email 信息了。当然,如果我们需要删除 email,可以使用下面语法进行:


​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​emails = emails - {​​​​'cassandra@iteblog.com'​​​​} ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name​​​​------------+-------------------------+-----------​​​​​​​​Wu | {​​​​'iteblog@iteblog.com'​​​​} | Shi​​​​(1 ​​​​rows​​​​)​​​​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​emails ={} ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name​​​​------------+--------+-----------​​​​​​​​Wu | ​​​​null​​​​| Shi​​​​(1 ​​​​rows​​​​)​


上面我们使用 ​​SET emails = emails - {'cassandra@iteblog.com'}​​​ 从用户 email 列表里面删除 email,使用 ​​SET emails ={}​​ 清空用户的 email。

list

list 包含了有序的列表数据,默认情况下,数据是按照插入顺序保存的。我们还是使用 iteblog_user 进行说明,比如我们想往这张表里面添加电话等信息,操作如下:


​cqlsh:iteblog_keyspace> ​​​​ALTER​​​​TABLE​​​​iteblog_user ​​​​ADD​​​​phone list<text>;​​​​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​phone = [​​​​'13112345678'​​​​] ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name | phone​​​​------------+--------+-----------+-----------------​​​​​​​​Wu | ​​​​null​​​​| Shi | [​​​​'13112345678'​​​​]​​​​(1 ​​​​rows​​​​)​


上面我们给 first_name 为 Wu 的记录添加了电话信息,如果需要再添加电话信息,其操作和 set 添加信息类似,如下:


​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​phone = phone + [​​​​'15511112222'​​​​] ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name | phone​​​​------------+--------+-----------+--------------------------------​​​​​​​​Wu | ​​​​null​​​​| Shi | [​​​​'13112345678'​​​​, ​​​​'15511112222'​​​​]​​​​(1 ​​​​rows​​​​)​


可见,新加入的电话号码被放在 list 的后面了。当然,如果我们使用下面的语句,可以往电话号码的前面添加信息:


​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​phone = [​​​​'13344448888'​​​​] + phone ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name | phone​​​​------------+--------+-----------+-----------------------------------------------​​​​​​​​Wu | ​​​​null​​​​| Shi | [​​​​'13344448888'​​​​, ​​​​'13112345678'​​​​, ​​​​'15511112222'​​​​]​​​​(1 ​​​​rows​​​​)​


我们可以使用下标从 list 数据类型里面修改数据:


​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​phone[1] = ​​​​'18888888888'​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name | phone​​​​------------+--------+-----------+-----------------------------------------------​​​​​​​​Wu | ​​​​null​​​​| Shi | [​​​​'13344448888'​​​​, ​​​​'18888888888'​​​​, ​​​​'15511112222'​​​​]​​​​(1 ​​​​rows​​​​)​


下标为 1 的元素被修改了。也可以使用下标删除数据:


​cqlsh:iteblog_keyspace> ​​​​DELETE​​​​phone[2] ​​​​from​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​* ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | emails | last_name | phone​​​​------------+--------+-----------+--------------------------------​​​​​​​​Wu | ​​​​null​​​​| Shi | [​​​​'13344448888'​​​​, ​​​​'18888888888'​​​​]​​​​(1 ​​​​rows​​​​)​


当然,删除元素也可以使用 ​​SET phone_numbers = phone_numbers - [ '13344448888' ]​​,这里就不演示了。

map

map 数据类型包含了 key/value 键值对。key 和 value 可以是任何类型,除了 counter 类型。使用如下:


​cqlsh:iteblog_keyspace> ​​​​ALTER​​​​TABLE​​​​iteblog_user ​​​​ADD​​​​login_sessions map<timeuuid, ​​​​int​​​​>;​​​​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​login_sessions = {now(): 13, now(): 18} ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​first_name, login_sessions ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | login_sessions​​​​------------+--------------------------------------------------------------------------------------​​​​​​​​Wu | {1cc61ff0-5f8b-11e9-ac3a-5336cd8118f6: 13, 1cc61ff1-5f8b-11e9-ac3a-5336cd8118f6: 18}​​​​(1 ​​​​rows​​​​)​


其他简单数据类型


  • boolean:值只能为 true/false,在 cql 中输入的这两个值无论大小如何写法,其输出都是 True/False;
  • blob:二进制大对象(binary large object)是任意字节数组的术语简称。这个类型在存储媒体或者其他二进制数据类型时很有用,Cassandra 并不会检查其中存储的二进制数据是否有效。Cassandra 中二进制大对象是以十六进制存储的,如果我们想将任意的文本数据类型使用 blob 存储,可以使用 textAsBlob() 函数实现。
  • inet:这个数据类型可以表示 IPv4 或 IPv6 网络地址。cqlsh 接受用于定义 IPv4 地址的任何合法格式,包括包含十进制,八进制或十六进制值的点或非点式表示。CQL 统一输出为 1.1.1.1 这种 ip 地址形式。
  • counter:计数器数据类型是64位有符号整数,其值不能直接设置,而只能递增或递减。计数器类型的使用有一些特殊限制,它不能用作主键的一部分;如果使用计数器,则除primary key 列之外的所有列都必须是计数器。

用户自定义数据类型(User-Defined Types)

Cassandra 中如果内置的数据类型无法满足我们的需求,我们可以使用自定义数据类型的功能。比如我们想用一个字段存储用户的地址信息,然后我们需要获取地址的邮编、街道等信息,如果使用 text 来存储是不能满足我们的需求的。这时候就可以自己定义数据类型,如下:


​cqlsh:iteblog_keyspace> ​​​​CREATE​​​​TYPE address (​​​​​​​​... street text,​​​​​​​​... city text,​​​​​​​​... state text,​​​​​​​​... zip_code ​​​​int​​​​);​


上面我们定义了 address 数据类型。需要注意的是,Cassandra 中数据类型的定义是 keyspace 范围的,也就是说, address 数据类型只能在 iteblog_keyspace 里面使用。如果我们使用 DESCRIBE KEYSPACE iteblog_keyspace,可以看到 address 数据类型属于 iteblog_keyspace 的一部分。现在我们定义好了 address 数据类型,我们可以使用它了,如下:


​cqlsh:iteblog_keyspace> ​​​​ALTER​​​​TABLE​​​​iteblog_user ​​​​ADD​​​​addresses map<text, frozen<address>>;​​​​cqlsh:iteblog_keyspace> ​​​​UPDATE​​​​iteblog_user ​​​​SET​​​​addresses = addresses + {​​​​'home'​​​​: { street: ​​​​'shangdi 9'​​​​, city: ​​​​'Beijing'​​​​, state: ​​​​'Beijing'​​​​, zip_code: 100080} } ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​cqlsh:iteblog_keyspace> ​​​​SELECT​​​​first_name, addresses ​​​​FROM​​​​iteblog_user ​​​​WHERE​​​​first_name = ​​​​'Wu'​​​​;​​​​​​​​first_name | addresses​​​​------------+--------------------------------------------------------------------------------------​​​​​​​​Wu | {​​​​'home'​​​​: {street: ​​​​'shangdi 9'​​​​, city: ​​​​'Beijing'​​​​, state: ​​​​'Beijing'​​​​, zip_code: 100080}}​​​​(1 ​​​​rows​​​​)​


可见 我们已经成功的使用了自定义数据类型了。