Cassandra概览

564 阅读17分钟

简介

GitHub地址: github.com/apache/cass…

关系数据库NoSQL数据库
支持强大的查询语言支持非常简单的查询语言
它具有固定的模式无固定模式
遵循ACID(原子性,一致性,隔离性和持久性)只有“最终一致”
支持事务不支持事务

Apache Cassandra是一套开源分布式NoSQL数据库系统。它最初由Facebook开发,用于改善电子邮件系统的搜索性能的简单格式数据,集Google BigTable的数据模型与Amazon Dynamo的完全分布式架构于一身。

  • Apache的HBase - HBase 是一个开放源代码,非关系型,分布式数据库,以Google的BigTable为模型,用Java编写。它作为Apache Hadoop项目的一部分开发,在HDFS之上运行,为Hadoop提供类似于BigTable的功能。

  • MongoDB - MongoDB是一个跨平台的面向文档的数据库系统,避免使用传统的基于表的关系数据库结构,使用动态模式的类似JSON的文档,使得在某些类型的应用程序中的数据集成更容易和更快。

在《Cassandra The Definitive Guide》这本书里,有一段概括性的描述,即用 50 个 word 描述 Cassandra。它归纳了 Cassandra 的几大特性,依次为:开源、分布式、去中心化、可扩展性、高可用、容错性、可配置的一致性、行存储

Cassandra一般被认为是满足AP的系统,也就是说Cassandra认为可用性和分区容忍性比一致性更重要。不过Cassandra可以通过调节replication factor和consistency level来满足C。

特点

Cassandra 因其卓越的技术特性而变得如此受欢迎。下面给出了 Cassandra 的一些特性:

  • 弹性可扩展性 - Cassandra 是高度可扩展的;它允许添加更多的硬件以适应更多的客户和更多的数据根据要求。
  • 始终基于架构 - Cassandra 没有单点(peer-to-peer)故障,它可以连续用于不能承担故障的关键业务应用程序。
  • 快速线性性能 - Cassandra 是线性可扩展性的,即它为你增加集群中的节点数量增加你的吞吐量。因此,保持一个快速的响应时间。
  • 灵活的数据存储 - Cassandra 适应所有可能的数据格式,包括:结构化,半结构化和非结构化。它可以根据您的需要动态地适应变化的数据结构。
  • 便捷的数据分发 - Cassandra 通过在多个数据中心之间复制数据,可以灵活地在需要时分发数据。
  • 事务支持 - Cassandra 支持属性,如原子性,一致性,隔离和持久性(ACID)。
  • 快速写入 - Cassandra 被设计为在廉价的商品硬件上运行。 它执行快速写入,并可以存储数百 TB 的数据,而不牺牲读取效率。

Cassandra的组件

Cassandra的关键组件如下:

  • 节点 - 它是存储数据的地方。
  • 数据中心 - 它是相关节点的集合。
  • 集群 - 集群是包含一个或多个数据中心的组件。
  • 提交日志 - 提交日志是Cassandra中的崩溃恢复机制。每个写操作都写入提交日志。
  • Mem-表 - mem-表是存储器驻留的数据结构。提交日志后,数据将被写入mem表。有时,对于单列族,将有多个mem表。
  • SSTable - 它是一个磁盘文件,当其内容达到阈值时,数据从mem表中刷新。

布隆过滤器 - 这些只是快速,非确定性的算法,用于测试元素是否是集合的成员。它是一种特殊的缓存。 每次查询后访问Bloom过滤器。

LSM-Tree

LSM-Tree全称是 Log Structured Merge Tree,是一种分层,有序,面向磁盘的数据结构,其核心思想是充分了利用了磁盘批量的顺序写要远比随机写性能高出很多。很多数据库都使用了 LSM Tree 的存储模型,包括 LevelDB,HBase,Google BigTable,Cassandra,InfluxDB 等。

WAL log: Write-Ahead logging,称预写log。

Immutable Memtable 就是在内存中只读的 MemTable,由于内存是有限的,通常我们会设置一个阀值,当 MemTable 占用的内存达到阀值后就自动转换为 Immutable Memtable

LSM-Tree里面如何写数据的?

当收到一个写请求时,会先把该条数据记录在WAL Log里面,用作故障恢复。

当写完WAL Log后,会把该条数据写入内存的SSTable里面(删除是墓碑标记,更新是新记录一条的数据),也称Memtable。注意为了维持有序性在内存里面可以采用红黑树或者跳跃表相关的数据结构。

当Memtable超过一定的大小后,会在内存里面冻结,变成不可变的Memtable,同时为了不阻塞写操作需要新生成一个Memtable继续提供服务。

把内存里面不可变的Memtable给dump到到硬盘上的SSTable层中,此步骤也称为Minor Compaction,这里需要注意在L0层的SSTable是没有进行合并的,所以这里的key range在多个SSTable中可能会出现重叠,在层数大于0层之后的SSTable,不存在重叠key。

当每层的磁盘上的SSTable的体积超过一定的大小或者个数,也会周期的进行合并。此步骤也称为Major Compaction,这个阶段会真正 的清除掉被标记删除掉的数据以及多版本数据的合并,避免浪费空间,注意由于SSTable都是有序的,我们可以直接采用merge sort进行高效合并

 在 LSM Tree 中,写入操作是相当快速的,只需要在 WAL 文件中顺序写入当次操作的内容,成功之后将该 k-v 数据写入 MemTable 中即可。尽管做了一次磁盘 IO,但是由于是顺序追加写入操作,效率相对来说很高,并不会导致写入速度的降低。数据写入 MemTable 中其实就是往 SkipList 中插入一条数据,过程也相当简单快速。

LSM-Tree里面如何读数据的?

当收到一个读请求的时候,会直接先在内存里面查询,如果查询到就返回。 如果没有查询到就会依次下沉,知道把所有的Level层查询一遍得到最终结果。 优化由于SSTable分层多导致多次扫描的方法(参考Bigtable论文): 

  • Compression → 压缩
  • Caching for read performance → 缓存
  • Bloom filters → 索引
  • 合并

Bigtable: A Distributed Storage System for Structured Data

先在内存中的 MemTable 和 Immutable MemTable 中查找,如果没有找到,则继续从 Level 0 层开始,找不到就从更高层的 SSTable 文件中查找,如果查找失败,说明整个 LSM Tree 中都不存在这个 key 的数据。如果中间在任何一个地方找到这个 key 的数据,那么按照这个路径找到的数据都是最新的。

优化读取

因为这样的读取效率非常差,通常会进行一些优化,例如 LevelDB 中的 Mainfest 文件,这个文件记录了 SSTable 文件的一些关键信息,例如 Level 层数,文件名,最小 key 值,最大 key 值等,这个文件通常不会太大,可以放入内存中,可以帮助快速定位到要查询的 SSTable 文件,避免频繁读取。

另外一个经常使用的方法是布隆解析器(Bloom filter),布隆解析器是一个使用内存判断文件是否包含一个关键字的有效方法。

LSM-Tree的优点是支持高吞吐的写(可认为是O(1)),这个特点在分布式系统上更为看重,当然针对读取普通的LSM-Tree结构,读取是O(N)的复杂度,在使用索引或者缓存优化后的也可以达到O(logN)的复杂度。

B+tree的优点是支持高效的读(稳定的OlogN),但是在大规模的写请求下(复杂度O(LogN)),效率会变得比较低,因为随着insert的操作,为了维护B+树结构,节点会不断的分裂和合并。操作磁盘的随机读写概率会变大,故导致性能降低。(MySQL)

Cassandra 的写入

  1. replica node 接收write请求,并立即写入commit log
  2. replica node 将数据写入memtable
  3. 如果启用row cache 并且此行在缓存中,此行无效
  4. 如果memtable或者commit log达到最大阈值,flush早操将会被触发(memetable数据写入SSTable,commit log数据被清除)。flush操作完成还会做compact检查,如果需要,进行compact,整合SSTable。

Commit log

commitlog主要记录客户端提交过来的数据以及操作。这个数据将被持久化到磁盘中,以便数据没有被持久化到磁盘时可以用来恢复。

Commitlog是server级别的,不是Column Family级别。 每个Commitlog文件的大小是固定的,称之为一个Commitlog Segment,当一个Commitlog文件写满以后,会新建一个的文件。当旧的Commitlog文件不再需要时,会自动清除。

每个Commitlog文件(Segment)都有一个固定大小(大小根据Column Family的数目而定)的CommitlogHeader 结 构,其中有两个重要的数组,每一个Column Family在这两个数组中都存在一个对应的元素。其中一个是位图数组(BitSet dirty ),如果Column Family对应的Memtable中有脏数据,则置为1,否则为0,这在恢复的时候可以指出哪些Column Family是需要利用Commitlog进行恢复的。

另外一个是整数数组(int[] lastFlushedAt ), 保存的是Column Family在上一次Flush时日志的偏移位置,恢复时则可以从这个位置读取Commitlog记录。通过这两个数组结构,Cassandra可以在异 常重启服务的时候根据持久化的SSTable和Commitlog重构内存中Memtable的内容,也就是类似Oracle等关系型数据库的实例恢复。

当Memtable flush到磁盘的SStable时,会将所有Commitlog文件的dirty数组对应的位清零,而在Commitlog达到大小限制创建新的文件 时,dirty数组会从上一个文件中继承过来。如果一个Commitlog文件的dirty数组全部被清零,则表示这个Commitlog在恢复的时候不 再需要,可以被清除。因此,在恢复的时候,所有的磁盘上存在的Commitlog文件都是需要的。

Memtable

Memtable中的内容是按照key排序好的。Memtable是一种内存结构,满足一定条件后批量刷新到磁盘上,存储为SSTable。这种机制,相当于缓存写回机制(Write-back Cache),优势在于将随机IO写变成顺序IO写,降低大量的写操作对于存储系统的压力。

SSTable

磁盘存储数据的文件。每一个column family都会对应一个SSTable文件。这又分为 Data、Index 和 Filter 三种数据格式。其中Data.db文件是SSTable数据文件,SSTable是Sorted Strings Table的缩写,按照key排序后存储key/value键值字符串。index.db是索引文件,保存的是每个key在数据文件中的偏移位置,而Filter.db则是Bloom Filter算法生产的映射文件。SSTable一旦完成写入,就不可变更,只能读取,即为immutable

因为SSTable数据不可更新,可能导致同一个Column Family的数据存储在多个SSTable中,这时查询数据时,需要去合并读取Column Family所有的SSTable和Memtable,这样到一个Column Family的数量很大的时候,可能导致查询效率严重下降。因此需要有一种机制能快速定位查询的Key落在哪些SSTable中,而不需要去读取合并所有的SSTable。

Cassandra采用的是Bloom Filter算法,通过多个hash函数将key映射到一个位图中,来快速判断这个key属于哪个SSTable。为了避免大量SSTable带来的性能影响,Cassandra也提供一种定期将多个SSTable合并成一个新的SSTable的机制(Compaction),因为每个SSTable中的key都是已经排序好的,因此只需要做一次合并排序就可以完成该任务,代价还是可以接受的。

《Cassandra The Definitive Guide》

First, writing data is very fast in Cassandra, because its design does not require performing disk reads or seeks. The memtables and SSTables save Cassandra from having to perform these operations on writes, which slow down many databases. All writes in Cassandra are append-only.

Cassandra为什么可以在Memtable上纯粹的做追加写入,这个Cassandra记录的Timestamp概念是分不开的,即无论你写入多少次,数据库只会以最新Timestamp的记录为准。这样就不需要去对记录资源上锁。这样的设计,不要说没有锁冲突了,就连去把需要上锁的记录找出来的开销都省了,快就快在这个地方。

传统的数据库的写入(包括INSERT、UPDATE、DELETE),通常是一个先读后写的过程。而Cassandra的写入,是没有先读这个动作的,这也是它快的根本原因。一旦使用了IF NOT EXIST之类的语法,那么它的写入性能也就会要受损。

Cassandra 的读取

Cassandra的读取,它的读取是多节点、多副本的读取

  1. replica node 接收到读请求后,首先检查行缓存(row cache),如果包含,立即返回。Row cache提升了读的性能。

  2. 如果数据不在 row cache,replica node 会在memtable和SSTable中搜数据(一个表,对应一个memtable,但是可能对应多个SSTable,Cassandra提供了key caching, Bloom filters, SSTable indexes, and summary indexes来提升SSTable的检索效率)

  3. 搜索磁盘上的SSTable的第一步是使用一个Bloom过滤器来确定请求的分区是否在给定的SSTable中。

  4. 如果SSTable通过了布隆滤波器,Cassandra将会检查key cache是否包含partition key的offset。key cache是一个map结构,键是SSTable文件描述符和分区键的组合,值是SSTable文件中的偏移位置。有助于消除在SSTable文件中寻找频繁访问的数据,因为数据可以直接读取。

  5. 如果key cache中没有找到offset,Cassandra会使用存储在磁盘上的two-level index来确定offset的位置。第一级索引是分区摘要(partition summary),它用于获得在第二级索引中搜索分区键的offset,即分区索引(partition index)。分区索引是存储分区键到SSTable的offset的地方。

  6. 如果找到分区键的偏移量,Cassandra将访问指定偏移量处的SSTable并开始读取数据。

  7. 一旦从所有SSTable中获得了数据,Cassandra就会通过为每个请求的列选择具有最新时间戳(Timestamps)的值来合并SSTable数据和memtable数据。任何遇到的Tombstones数据都将被忽略。

  8. 最后,merge后的数据会被加到row cache(如果启用的话),并且返回给客户端或者coordinator 节点。

设置Row Cache: docs.datastax.com/en/archived…

Row Caches放的是一整行的数据,如前面提到了,适合于存放热点读取的数据

Cassandra 的修改与删除

一句话简述之,Cassandra的删除都是修改,Cassandra的修改都是写入,所以Cassandra只有写入和查询

Cassandra一直写入数据,岂不是会导致存储爆炸?

写入

在Cassandra上,UPDATE命令会变成一条INSERT语句。

Cassandra的每次查询,都会把所有重的KEY读出来,但是永远会以最新的Timestamps为准。

删除

删除即为逻辑删除,通过加标识来表示此记录被删除,Cassandra在收到DELETE命令时,并不会立刻去删除这行记录。而是会给这行记录一个Tombstones,表示它被删除了

Compaction

Compaction是在数据库后台异步做的,接着前面的内容,它的内容至少有如把Tombstones数据真实移除、把时间戳比较老的数据移除、重新整理SSTable的存储文件等。

Cassandra集合

Cassandra集合用于处理任务。 您可以在集合中存储多个元素。 Cassandra支持三种类型的集合:

  • Set
  • List
  • Map

举例

CREATE TABLE example (
    key1 text PRIMARY KEY,
    map1 map<text,text>,
    list1 list<text>,
    set1 set<text>
    );

插入数据

INSERT INTO example (
    key1,
    map1,
    list1,
    set1
    ) VALUES (
    'john',
    {'patricia':'555-4326','doug':'555-1579'},
    ['doug','scott'],
    {'patricia','scott'}
    )

实际存储

RowKey: john
=> (column=, value=, timestamp=1374683971220000)
=> (column=map1:doug, value='555-1579', timestamp=1374683971220000)
=> (column=map1:patricia, value='555-4326', timestamp=1374683971220000)
=> (column=list1:26017c10f48711e2801fdf9895e5d0f8, value='doug', timestamp=1374683971220000)
=> (column=list1:26017c12f48711e2801fdf9895e5d0f8, value='scott', timestamp=1374683971220000)
=> (column=set1:'patricia', value=, timestamp=1374683971220000)
=> (column=set1:'scott', value=, timestamp=1374683971220000)

map,list和set的内部存储各不相同:

  • 对于map,每一个map的元素成为一列,列名是map字段的列名和这个元素的键值的组合,列值就是这个元素的值
  • 对于list,每一个list的元素成为一列,列名是list字段的列名和一个代表元素在list里的index的UUID的组合,列值就是这个元素的值
  • 对于set,每一个set的元素成为一列,列名是set字段的列名和这个元素的值的组合,列值总是空值

cassandra中集合类型不能做主键,不能建索引。 这种集合类型做为查询条件很弱,只有map可以使用元素作为查询条件,而且必须加 ALLOW FILTERING

SELECT * FROM test WHERE a=1 AND d['mapkey1']='mapvale2' ALLOW FILTERING;

具体操作

键空间(Keyspace)是用于保存列族,用户定义类型的对象。 键空间(Keyspace)就像RDBMS中的数据库,其中包含列族,索引,用户定义类型,数据中心意识,键空间(Keyspace)中使用的策略,复制因子等。

创建

CREATE KEYSPACE KeyspaceName WITH REPLICATION = {'class': 'Strategy name', 'replication_factor': 'No.Of  replicas'};

Class : 副本配置策略(总共有三种):

  • Simple Strategy(RackUnaware Strategy):为集群指定简单的复制因子,副本不考虑机架的因素,按照Token放置在连续下几个节点,如果是单机架、单数据中心的模式,保持使用SimpleStrtegy即可;
  • OldNetwork Topology Strategy(RackAware Strategy):考虑机架的因素,除了基本的数据外,先找一个处于不同数据中心的点放置一个副本,其余N-2个副本放置在同一数据中心的不同机架中;
  • Network Topology Strategy(DatacneterShard Strategy):将M个副本放置到其他的数据中心,将N-M-1的副本放置在同一数据中心的不同机架中;

Replication Factor: 复制因数。 表示一份数据在一个DC (Data Center)之中包含几份。常用奇数~ 比如我们项目组设置的replication_factor=3

修改

ALTER KEYSPACE KeyspaceName WITH REPLICATION = {'class': 'Strategy name', 'replication_factor': 'No.Of  replicas'} WITH DURABLE_WRITES=true/false;

DURABLE_WRITES: DURABLE_WRITES值可以通过指定其值 true/false 来更改。 默认情况下为 true。 如果设置为 false ,则不会将更新写入提交日志,反之亦然。

删除

DROP KEYSPACE KeyspaceName;

docker实例启动

# 创建一个新的 network
docker network create [OPTIONS] NETWORK
# 使用网络cassandra-test创建
docker run -dit --network cassandra-test --name cassandra-test-1 cassandra:lastest
# 启动后查看
[root@test~] docker ps                                                                 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                         NAMES
fe8eee746e29        cassandra           "docker-entrypoint.s…"   46 seconds ago      Up 45 seconds       7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp   cassandra-test-1

实例操作

官方提供常见问题

cassandra.apache.org/doc/latest/…

压测工具

cassandra-stress用于基准测试和Cassandra集群负载测试的基于java的压力测试实用程序。

其中常用的命令一般只用到:write、read、mixed、user。其中单纯的write和read只测试读和写,mixed则测试同时读写。

参考

cassandra

cassandra-stress