安装

1、下载

1
打开官网,选择下载频道 https://cassandra.apache.org/download/

2、新建数据存储目录,data目录

1
2
3
# 如果不配置数据目录默认为$CASSANDRA_HOME/data/data;
data_file_directories:
- D:\coding-software\apache-cassandra-3.11.11\data

3、新建日志目录,commitlog目录

1
2
3
# 如果不配置日志目录,默认为:$CASSANDRA_HOME/data/commitlog;
commitlog_directory:
- D:\coding-software\apache-cassandra-3.11.11\commitlog

4、新建缓存目录,saved_caches目录

1
2
# 如果不配置日志目录,默认为:$CASSANDRA_HOME/data/saved_caches;
saved_caches_directory: D:\coding-software\apache-cassandra-3.11.11\saved_caches
  • 注意:- 的后面要跟一个空格,这是yaml的语法;

启动

前提条件:JDK && Python

1、不会创建交互式会话,它只是在前台运行Cassandra,而是作为服务器进程运行。 要与Cassandra进行交互,可以在另一个终端窗口中启动CLI会话。

1
bin % ./cassandra -f

2、进入cqlsh

1
bin % ./cqlsh

3、查看是否启动成功

1
ps -ef|grep cassandra

数据存储结构

Cassandra 的数据模型是基于列族(Column Family)的四维或五维模型。它借鉴了 Amazon 的 Dynamo 和 Google’s Big Table 的数据结构和功能特点,采用Memtable的方式进行存储。在 Cassandra 写入数据之前,需要先记录日志 ( Commitlog),然后数据开始写入到 Column Family 对应的 Memtable 中,Memtable 是一种按照 key 排序数据的内存结构,在满足一定条件时,再把 Memtable 的数据批量的刷新到磁盘上,存储为 SSTable 。

Keyspace

相当于关系数据库中的 Schema 或 database,用于存放 ColumnFamily 的容器,键空间包含一个或多个列族(Column Family)

注意:键空间 (KeySpace) 创建的时候可以指定一些属性:副本因子,副本策略,Durable_writes(是否启用 CommitLog 机制)

ColumnFamily

  1. 列族,一个简单的结构用来分组Column和SuperColumn。

  2. 用于存放 Column 的容器,类似关系数据库中的 table 的概念 。

  3. ColumnFamily是一种可以包含无限数量的行(row)的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 这是一个ColumnFamily
UserProfile = {
// 这是ColumnFamily中这一行的key,以及一个包含多个Column的map,map中的value应该是整个Column。
phatduckk: {
// 这一行中无限数量的Column,map中的key是Column的name,value是Column本身
username: "lauy",
email: "coderlauy@example.com",
phone: "456-6666"
}, // 这一行结束
ieure: {
username: "blue",
email: "coderblue@example.com",
phone: "123-1212"
age: "66",
gender: "male"
},
}

注意:ColumnFamily没有固定模式(schema)。并没有预先定义行包含了哪些Column。这就是Cassandra无模式的表现( This is the schemaless aspect of Cassandra)。

Column

Cassandra 的最基本单位。由name , value , timestamp组成。另外name和value都是二进制的(技术上指byte[]),并且可以是任意长度。

SuperColumn

它是一个特列殊的 Column, 它的 Value 值可以包函多个Column,如下图所示:

同Coulmn区别:

Column和SuperColumn都是拥有name、value的元组。

  1. 最大的不同在于标准Column的value是一个“字符串”,而 SuperColumn的value是一个包含多个Column的map。因此它们的value包含的是不同类型的数据。
  2. SuperColumn没有时间戳。

rowkey

ColumnFamily 中的每一行都用Row Key(行键)来标识,这个相当于关系数据库表中的主键,并且总是被索引的。

举个栗子,假如我有这么一个 table,叫做 ks.potato,该 table 总共有四个 field, 分别是 pk, ck, field1, field2。其中 pk,ck 是 key

ks.potato
pk -> key 1
ck -> key 2
field1
field2

CQL 插入这个表

1
2
3
4
5
6
7
CREATE TABLE ks.potato (
pk text,
ck text,
field1 text,
field2 text,
PRIMARY KEY (pk,ck)
);

向这张表里面写入一行数据

1
2
3
4
INSERT INTO ks.potato 
(pk, ck, field1, field2)
VALUES
('key1','key2','value1','value2');

那这行数据在 sstable 是怎么是储存的呢 ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"partition" : {
"key" : [ "key1" ],
"position" : 0
},
"rows" : [
{
"type" : "row",
"position" : 18,
"clustering" : [ "key2" ],
"liveness_info" : { "tstamp" : "2021-06-24T07:09:37.972290Z" },
"cells" : [
{ "name" : "field1", "value" : "value1" },
{ "name" : "field2", "value" : "value2" }
]
}
]
}
]

可以看到,

  • key1 决定了这行数据在哪个 partition,所以是 partition key。
  • key2 决定数据在这个partition的位置,是 clustering key,同时会让一个分区内的数据进行排序。
  • (key1, key2) 一起唯一标识了这一行,叫做 primary key,也叫做 row key。

Primary Key

Cassandra可以使用PRIMARY KEY 关键字创建主键,主键分为2种:

  1. Single column Primary Key
    如果 Primary Key 由一列组成,那么称为 Single column Primary Key

  2. Composite Primary Key
    如果 Primary Key 由多列组成,那么这种情况称为 Compound Primary Key 或 Composite Primary Key。比如例子中的 primary key 包括了两个 column (pk, ck), 那它就是一个 composite key。

列族具有以下属性 :

  • keys_cached - 它表示每个SSTable保持缓存的位置数。
  • rows_cached - 它表示其整个内容将在内存中缓存的行数。
  • preload_row_cache -它指定是否要预先填充行缓存。

column key

column name

数据存放规则

data:存储真正的数据文件,既后面的SStable文件,可以指定多个目录。

commitlog:存储未写入SSTable中的数据(在每次写入之前先放入日志文件)。

cache:存储系统中的缓存数据(在服务重启的时候从这个目录中加载缓存数据)。

命令操作

help 帮助

输入命令,可以查看cqlsh 支持的命令

1
cqlsh> help

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

DESCRIBE

此命令配合 一些内容可以输入信息

1
cqlsh> DESCRIBE cluster;

输出:

1
2
Cluster: Test Cluster
Partitioner: Murmur3Partitioner

DESCRIBE Keyspaces 列出集群中的所有Keyspaces(键空间)

1
2
3
cqlsh> DESCRIBE keyspaces;

system_traces system_schema system_auth system system_distributed

DESCRIBE tables 列出键空间的所有表

效果,当前没有创建任何的键空间,这里显示的默认内置的表

1
2
---------------------------
repair_history view_build_s

指定键空间 ,这里使用 system_traces

1
cqlsh> USE system_traces;

列出system_traces 下的 sessions信息

1
cqlsh:system_traces> DESCRIBE sessions;

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE system_traces.sessions (
session_id uuid PRIMARY KEY,
client inet,
command text,
coordinator inet,
coordinator_port int,
duration int,
parameters map<text, text>,
request text,
started_at timestamp
) WITH bloom_filter_fp_chance = 0.01
AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
AND comment = 'tracing sessions'
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.0
AND default_time_to_live = 0
AND gc_grace_seconds = 0
AND max_index_interval = 2048
AND memtable_flush_period_in_ms = 3600000
AND min_index_interval = 128
AND read_repair_chance = 0.0
AND speculative_retry = '99PERCENTILE';

Keyspace

1、创建Keyspace

1
CREATE KEYSPACE <identifier> WITH <properties>;

更具体的语法:

1
Create keyspace KeyspaceName with replicaton={'class':strategy name,'replication_factor': No of replications on different nodes};

要填写的内容:

  • KeyspaceName 代表键空间的名字
  • strategy name 代表副本放置策略,内容包括:简单策略、网络拓扑策略,选择其中的一个。
  • No of replications on different nodes 代表 复制因子,放置在不同节点上的数据的副本数。

编写完成的创建语句 创建一个键空间名字为:school,副本策略选择:简单策略 SimpleStrategy,副本因子:3

1
CREATE KEYSPACE school WITH replication = {'class':'SimpleStrategy', 'replication_factor' : 3};

如图所示:

输入 DESCRIBE school 查看键空间的创建语句,代码:

2、连接Keyspace

1
USE <identifier>;

编写完整的连接Keyspace语句,连接school 键空间

1
use school;

3、修改键空间

1
ALTER KEYSPACE <identifier> WITH <properties>

编写完整的修改键空间语句,修改school键空间,把副本引子 从3 改为1

1
ALTER KEYSPACE school WITH replication = {'class':'SimpleStrategy', 'replication_factor' : 1};

4、删除键空间

1
DROP KEYSPACE <identifier>

完整删除键空间语句,删除school键空间

1
DROP KEYSPACE school

表操作

1、查看键空间下所有表

创建school键空间,再查看:

1
DESCRIBE TABLES;

如果当前键空间下没有任何表,则返回空;

2、创建表

1
2
CREATE (TABLE | COLUMNFAMILY) <tablename> ('<column-definition>' , '<column-definition>')
(WITH <option> AND <option>)

完整创建表语句,创建student 表,student包含属性如下: 学生编号(id), 姓名(name),年龄(age),性别(gender),家庭地址(address),interest(兴趣),phone(电话号码),education(教育经历) id 为主键,并且为每个Column选择对应的数据类型。 注意:interest 的数据类型是set ,phone的数据类型是list,education 的数据类型是map

1
2
3
4
5
6
7
8
9
10
CREATE TABLE student(
id int PRIMARY KEY,
name text,
age int,
gender tinyint,
address text ,
interest set<text>,
phone list<text>,
education map<text, text>
);

如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
cqlsh:school> CREATE TABLE student(
... id int PRIMARY KEY,
... name text,
... age int,
... gender tinyint,
... address text ,
... interest set<text>,
... phone list<text>,
... education map<text, text>
... );
cqlsh:school> describe student;

CREATE TABLE school.student (
id int PRIMARY KEY,
address text,
age int,
education map<text, text>,
gender tinyint,
interest set<text>,
name text,
phone list<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:school>

3、修改表结构

添加列,语法:

1
ALTER TABLE table name ADD  new column datatype;

给student添加一个列email代码:

1
ALTER TABLE student ADD email text;

删除列,语法

1
ALTER table name DROP columnname;

删除student中列phone:

1
ALTER table student DROP phone;

删除表

1
DROP TABLE <tablename>

清空表

1
TRUNCATE <tablename>

CURD

ALLOW FILTERING

ALLOW FILTERING是一种非常消耗计算机资源的查询方式。 如果表包含例如100万行,并且其中95%具有满足查询条件的值,则查询仍然相对有效,这时应该使用ALLOW FILTERING。

如果表包含100万行,并且只有2行包含满足查询条件值,则查询效率极低。Cassandra将无需加载999,998行。如果经常使用查询,则最好在列上添加索引。

ALLOW FILTERING在表数据量小的时候没有什么问题,但是数据量过大就会使查询变得缓慢。查询所有数据

查询

当前student表有2行数据,全部查询出来,代码:

1
2
-- SELECT FROM <table name> WHERE <condition>;
cqlsh:school> select * from student;

查询时排序

  1. 必须有第一主键的=号查询cassandra的第一主键是决定记录分布在哪台机器上,cassandra只支持单台机器上的记录排序。
  2. 只能根据第二、三、四…主键进行有序的,相同的排序。
  3. 不能有索引查询cassandra的任何查询,最后的结果都是有序的,内部就是这样存储的。

现在使用 testTab表,来测试排序

1
2
select * from testtab where key_one = 12 order by key_two;  --正确
select * from testtab where key_one = 12 and age =19 order key_two; --错误,不能有索引查询

索引列 支持 like

主键支持 group by

分页查询

使用limit 关键字来限制查询结果的条数 进行分页

添加数据

语法

1
INSERT INTO <tablename>(<column1 name>, <column2 name>....) VALUES (<value1>, <value2>....) USING <option>

示例:

1
2
3
INSERT INTO student (id,address,age,gender,name,interest, phone,education) VALUES (1011,'中山路21号',16,1,'Tom',{'游泳', '跑步'},['010-88888888','13888888888'],{'小学' : '城市第一小学', '中学' : '城市第一中学'}) ;

INSERT INTO student (id,address,age,gender,name,interest, phone,education) VALUES (1012,'朝阳路19号',17,2,'Jerry',{'看书', '电影'},['020-66666666','13666666666'],{'小学' :'城市第五小学','中学':'城市第五中学'});

添加带过期时间数据:

1
INSERT INTO student (id,address,age,gender,name,interest, phone,education) VALUES (1030,'朝阳路30号',20,1,'Cary',{'运动', '游戏'},['020-7777888','139876667556'],{'小学' :'第30小学','中学':'第30中学'}) USING TTL 60;

更新列数据

更新表中的数据,可用关键字:

  • Where - 选择要更新的行
  • Set - 设置要更新的值
  • Must - 包括组成主键的所有列

在更新行时,如果给定行不可用,则UPDATE创建一个新行

语法:

1
2
3
4
UPDATE <tablename>
SET <column name> = <new value>
<column name> = <value>....
WHERE <condition>

把student_id = 1012 的数据的gender列 的值改为1,代码:

1
UPDATE student set gender = 1 where id = 1012;

set

添加一个元素

在student中interest列是set类型,添加一个元素,使用UPDATE命令 和 ‘+’ 操作符

代码:

1
UPDATE student SET interest = interest + {'游戏'} WHERE id = 1012;
删除一个元素

使用UPDATE命令 和 ‘-’ 操作符

代码:

1
UPDATE student SET interest = interest - {'电影'} WHERE id = 1012;
删除所有元素

可以使用UPDATA或DELETE命令,效果一样

代码:

1
UPDATE student SET interest = {} WHERE id = 1012;

1
DELETE interest FROM student WHERE id = 1012;

list

更新list类型数据

在student中phone列是list类型

使用UPDATE命令向list插入值

1
UPDATE student SET phone = ['020-66666666', '13666666666'] WHERE id = 1012;

2)在list前面插入值

1
UPDATE student SET phone = [ '030-55555555' ] + phone WHERE id = 1012;

可以看到新数据的位置在旧数据的前面,效果:
![](/Users/lauy/Library/Application Support/typora-user-images/image-20211121222953410.png)

3)在list后面插入值

1
UPDATE student SET phone = phone + [ '040-33333333' ]  WHERE id = 1012;
列表索引

使用列表索引设置值,覆盖已经存在的值

现在把phone中下标为2的数据,也就是 “13666666666”替换,代码:

1
UPDATE student SET phone[2] = '050-22222222' WHERE id = 1012;
删除特定位置值

使用DELETE命令和索引删除某个特定位置的值

1
DELETE phone[2] FROM student WHERE id = 1012;
移除list中所有的特定值

使用UPDATE命令和 '-'移除list中所有的特定值

1
UPDATE student SET phone = phone - ['020-66666666'] WHERE id = 1012;

map

更新map类型数据

1)使用Insert或Update命令

1
UPDATE student SET education = {'中学': '城市第五中学', '小学': '城市第五小学'} WHERE id = 1012;

2)使用UPDATE命令设置指定元素的value

1
UPDATE student SET education['中学'] = '爱民中学' WHERE id = 1012;

3)增加map元素

可以使用如下语法增加map元素。如果key已存在,value会被覆盖,不存在则插入

1
UPDATE student SET education = education + { '幼儿园' : '大海幼儿园', '中学': '科技路中学'} WHERE id = 1012;

覆盖“中学”为“科技路中学”,添加“幼儿园”数据。

4)删除元素

可以用DELETE 和 UPDATE 删除Map类型中的数据

1
DELETE education['幼儿园'] FROM student WHERE id = 1012;

使用UPDATE删除数据

1
UPDATE student SET education=education - {'中学','小学'} WHERE id = 1012;

删除行

语法:

1
DELETE FROM <identifier> WHERE <condition>;

删除student中student_id=1012 的数据,代码:

1
DELETE FROM student WHERE student_id=1012;

批量操作

作用:把多次更新操作合并为一次请求,减少客户端和服务端的网络交互。 batch中同一个partition key的操作具有隔离性

使用BATCH,您可以同时执行多个修改语句(插入,更新,删除)

1
2
3
BEGIN BATCH
<insert-stmt>/ <update-stmt>/ <delete-stmt>
APPLY BATCH

示例:

1
2
3
4
5
BEGIN BATCH
INSERT INTO student (id,address,age,gender,name) VALUES (1015,'上海路',20,1,'Jack') ;
UPDATE student set age = 11 where id= 1012;
DELETE FROM student WHERE id=1011;
APPLY BATCH;

索引

上面创建student的时候,把student_id 设置为primary key 在Cassandra中的primary key是比较宏观概念,用于从表中取出数据。primary key可以由1个或多个column组合而成。

不要在以下情况使用索引:

  • 这列的值很多的情况下,因为你相当于查询了一个很多条记录,得到一个很小的结果。
  • 表中有couter类型的列
  • 频繁更新和删除的列
  • 在一个很大的分区中去查询一条记录的时候(也就是不指定分区主键的查询)

五种类型Key

1、Primary Key
是用来获取某一行的数据, 可以是单一列(Single column Primary Key)或者多列(Composite Primary Key)。

在 Single column Primary Key 决定这一条记录放在哪个节点。

例如:

1
2
3
4
create table testTab (
id int PRIMARY KEY,
name text
);

2、Composite Primary Key
如果 Primary Key 由多列组成,那么这种情况称为 Compound Primary Key 或 Composite Primary Key。

例如:

1
2
3
4
5
6
create table testComposite (
key_one int,
key_two int,
name text,
PRIMARY KEY(key_one, key_two)
);

执行创建表后,查询testTab,会发现key_one和key_two 的颜色与其他列不一样,效果:

3、Partition Key
在组合主键的情况下(上面的例子),第一部分称作Partition Key(key_one就是partition key),第二部分是CLUSTERING KEY(key_two)

Cassandra会对Partition key 做一个hash计算,并自己决定将这一条记录放在哪个节点。

例如:

1
2
3
4
5
6
7
8
9
create table testPartition (
key_part_one int,
key_part_two int,
key_clust_one int,
key_clust_two int,
key_clust_three uuid,
name text,
PRIMARY KEY(key_part_one, key_clust_one, key_clust_two, key_clust_three)
);

4、Composite Partition key

如上3中,如果 Partition key 由多个字段组成,称之为 Composite Partition key

1
2
3
4
5
6
7
8
9
create table testCompositePartition (
key_part_one int,
key_part_two int,
key_clust_one int,
key_clust_two int,
key_clust_three uuid,
name text,
PRIMARY KEY((key_part_one,key_part_two), key_clust_one, key_clust_two, key_clust_three)
);

5、Clustering Key

决定同一个分区内相同 Partition Key 数据的排序,默认为升序,可以在建表语句里面手动设置排序的方式

创建索引

1、语法

1
CREATE INDEX <identifier> ON <tablename>

为student的 name 添加索引,索引的名字为:sname, 代码:

1
CREATE INDEX sname ON student (name);

为student 的age添加索引,不设置索引名字,代码

1
CREATE INDEX ON student (age);

可以发现 对age创建索引,没有指定索引名字,会提供一个默认的索引名:student_age_idx。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cqlsh:school> describe student;

CREATE TABLE school.student (
id int PRIMARY KEY,
address text,
age int,
education map<text, text>,
gender tinyint,
interest set<text>,
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';
## index
CREATE INDEX student_age_idx ON school.student (age);
CREATE INDEX sname ON school.student (name);

索引原理:

Cassandra之中的索引的实现相对MySQL的索引来说就要简单粗暴很多了。Cassandra自动新创建了一张表格,同时将原始表格之中的索引字段作为新索引表的Primary Key!并且存储的值为原始数据的Primary Key

集合列创建索引

给集合列设置索引

1
2
CREATE INDEX ON student(interest);                 -- set集合添加索引
CREATE INDEX mymap ON student(KEYS(education)); -- map结合添加索引

删除索引

1
DROP INDEX <identifier>

删除student的sname 索引,代码

1
DROP INDEX sname;

查询时使用索引

Cassandra对查询时使用索引有一定的要求,具体如下:

  • Primary Key 只能用 = 号查询
  • 第二主键 支持= > < >= <=
  • 索引列 只支持 = 号
  • 非索引非主键字段过滤可以使用ALLOW FILTERING

创建表

1
2
3
4
5
6
7
8
create table testTab (
key_one int,
key_two int,
name text,
age int,
PRIMARY KEY(key_one, key_two)
);
create INDEX tage ON testTab (age);

第一主键查询

只能用 = 号查询

1
select * from testtab where key_one=4;

使用第二主键查询

注意⚠️:不要单独对key_two 进行 查询,

1
select * from testtab where key_two = 8;

报错:

1
InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot execute this query as it might involve data filtering and thus may have unpredictable performance. If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING"

可知,如果需要单独对key_two查询,则要使用 ALLOW FILTERING(不推荐)

推荐做法:在查询第二主键时,前面先写上第一主键,有点像MySQL索引的最左前缀法则

1
select * from testtab where key_one=12 and key_two = 8 ;

索引列

只支持=号

1
2
3
select * from testtab where age = 19;   -- 正确
select * from testtab where age > 20 ; --会报错
select * from testtab where age >20 allow filtering; --可以查询出结果,但是不建议这么做

普通列

非索引非主键字段,如此表name是普通列,在查询时需要使用ALLOW FILTERING。

1
2
select * from testtab where key_one=12 and name='张小仙'; --报错
select * from testtab where key_one=12 and name='张小仙' allow filtering; --可以查询

集合列

使用student表来测试集合列上的索引使用。

假设已经给集合添加了索引,就可以使用where子句的CONTAINS条件按照给定的值进行过滤。

1
2
3
4
5
6
7
8
-- 查询set集合
select * from student where interest CONTAINS '电影';

--查询map集合的key值
select * from student where education CONTAINS key '小学';

--查询map的value值
select * from student where education CONTAINS '中心第9小学' allow filtering;

Prepared statements

基本原理

1
预编译statement的时候,Cassandra会解析query语句,缓存解析的结果并返回一个唯一的标志。当绑定并且执行预编译statement的时候,驱动只会发送这个标志,那么Cassandra就会跳过解析query语句的过程。 应保证query语句只应该被预编译一次,缓存PreparedStatement 到我们的应用中(PreparedStatement 是线程安全的);如果我们对同一个query语句预编译了多次,那么驱动输出印警告日志; 如果一个query语句只执行一次,那么预编译不会提供性能上的提高,反而会降低性能,因为是两次请求,那么此时可以考虑用 simple statement 来代替。

数据存储

Cassandra的数据包括在内存中的和磁盘中的数据

这些数据主要分为三种: CommitLog:主要记录客户端提交过来的数据以及操作。这种数据被持久化到磁盘中,方便数据没有被持久化到磁盘时可以用来恢复。 Memtable:用户写的数据在内存中的形式,它的对象结构在后面详细介绍。其实还有另外一种形式是BinaryMemtable 这个格式目前 Cassandra 并没有使用,这里不再介绍了。 SSTable:数据被持久化到磁盘,这又分为 Data、Index 和 Filter 三种数据格式。

CommitLog 数据格式

Cassandra在写数据之前,需要先记录日志,保证Cassandra在任何情况下宕机都不会丢失数据,这就是CommitLog日志。要写入的数据按照一定格式组成 byte 组数,写到 IO 缓冲区中定时的被刷到磁盘中持久化。Commitlog是server级别的。每个Commitlog文件的大小是固定的,称之为一个CommitlogSegment。

当一个Commitlog文件写满以后,会新建一个的文件。当旧的Commitlog文件不再需要时,会自动清除。

Memtable 内存中数据结构

数据写入的第二个阶段,MemTable是一种内存结构,当数据量达到块大小时,将批量flush到磁盘上,存储为SSTable。优势在于将随机IO写变成顺序IO写,降低大量的写操作对于存储系统的压力。每一个columnfamily对应一个memtable。也就是每一张表对应一个。用户写的数据在内存中的形式,

SSTable 数据格式

SSTable是Read Only的,且一般情况下,一个ColumnFamily会对应多个SSTable,当用户检索数据时,Cassandra使用了Bloom Filter,即通过多个hash函数将key映射到一个位图中,来快速判断这个key属于哪个SSTable。

为了减少大量SSTable带来的开销,Cassandra会定期进行compaction,简单的说,compaction就是将同一个ColumnFamily的多个SSTable合并成一个SSTable。

在Cassandra中,compaction主要完成的任务是:

  1. 垃圾回收: cassandra并不直接删除数据,因此磁盘空间会消耗得越来越多,compaction 会把标记未删除的数据真正删除;
  2. 合并SSTable:compaction 将多个 SSTable 合并为一个(合并的文件包括索引文件,数据文件,bloom filter文件),以提高读操作的效率;
  3. 生成 MerkleTree:在合并的过程中会生成关于这个ColumnFamily中数据的 MerkleTree,用于与其他存储节点对比以及修复数据。

Cassandra的重要知识点

Cassandra的集群中每一台机器都是对等的,不存在主、从节点的区分,集群中任何一台机器出现故障是,整个集群系统不会受到影响。

一致哈希

一致性哈希是Cassandra搭建集群的基础,一致性哈希可以降低分布式系统中,数据重新分布的影响。

在Cassandra中,每个表有Primary Key外,还有一个叫做Partition Key,Partition Key列的Value会通过Cassandra一致性算法得出一个哈希值,这个哈希值将决定这行数据该放到哪个节点上。

每个节点拥有一段数字区间,这个区间的含义是:如果某行记录的Partition Key的哈希值落在这个区间范围之内,那么该行记录就该被存储到这个节点上。

如果简单的使用哈希值,可能会引起数据分布不均匀的问题,为了解决这个问题,一致性哈希提出虚拟节点的概念,简单的理解就是:将某个节点根据一个映射算法,映射出若干个虚拟子节点出来,再把这些节点分布在哈希环上面,保存数据时,如果通过一致性哈希计算落到某个虚拟子节点上,这条记录就会被存在这个虚拟子节点的母节点上。

Token:在Cassandra,每个节点都对应一个token,相当于hash环中的一个节点地址。在Cassandra的配置文件中有一项配置叫做:num_tokens,这个配置项可以控制一个节点映射出来的虚拟节点的个数。

Range:在Cassandra中,每一个节点负责处理hash环的一段数据,范围是从上一个节点的Token到本节点Token,这就是Range

在健康的集群中,可以通过自带的工具nodetool查看集群的哈希环具体情况,命令为:nodetool ring。

这里我们使用cassandra官方文档中一张图来说明

Gossip内部通信协议

Cassandra使用Gossip的协议维护集群的状态,这是个端对端的通信协议。通过Gossip,每个节点都能知道集群中包含哪些节点,以及这些节点的状态,

Gossip进程每秒运行一次,与最多3个其他节点交换信息,这样所有节点可很快了解集群中的其他节点信息。


参考链接:

  1. [传智官方教育](https://blog.csdn.net/itcast_cn/article/details/107559490

  2. 怎么理解Cassandra中的 rowkey和column key ?

  3. Cassandra的数据存储结构——cassandra总结(一)

  4. DataStax Java 驱动程序 3.0 (早期版本)