1. 简介
Apache Cassandra是由 Facebook 创建的高度可扩展、高性能的分布式NoSQL数据库,可以灵活的在线扩容,满足业务水平扩展的需求。具有能够处理大量数据的分布式架构。 数据放置在具有多个复本因子的不同机器上,以获得高可用性,而无需担心单点故障。
2. 特性
分布式和去中心化
Cassandra 是分布式的,这意味着它可以运行在多台机器上,并呈现给用户一个一致的整体。你可以放心地将数据写到集群的任意一台机器上,Cassandra 都会收到数据。去中心化意味着 Cassandra 不会存在单点失效。Cassandra 集群中的所有节点的功能都完全一样, 所以不存在一个特殊的主机作为主节点来承担协调任务。
弹性可扩展
节点间使用gossip协议通信,不需要重新启动进程,不必修改应用的查询,也无需自己手工重新均衡数据分布。在 Cassandra 里,你只要加入新的计算机,Cassandra 就会自动地发现它并让它开始工作。
可调节的一致性
CAP 定律表明,对于任意给定的系统,只能在一致性(Consistency)、可用性(Availability)以及分区容错性(Partition Tolerance)之间选择两个。所以 Cassandra 在设计的时候也不得不考虑这些问题,因为分区容错性这个是每个分布式系统必须考虑的,所以只能在一致性和可用性之间做选择,而 Cassandra 的应用场景更多的是为了满足可用性,所以我们只能牺牲一致性了。但是根据 BASE 理论,我们其实可以通过牺牲强一致性获得可用性。
Cassandra 提供了可调节的一致性,允许我们选定需要的一致性水平与可用性水平,在二者间找到平衡点。因为客户端可以控制在更新到达多少个副本之前,必须阻塞系统。这是通过设置副本因子(replication factor)来调节与之相对的一致性级别。
通过副本因子(replication factor),你可以决定准备牺牲多少性能来换取一致性。副本因子是你要求更新在集群中传播到的节点数(注意,更新包括所有增加、删除和更新操作)。
客户端每次操作还必须设置一个一致性级别(consistency level)参数,这个参数决定了多少个副本写入成功才可以认定写操作是成功的,或者读取过程中读到多少个副本正确就可以认定是读成功的。这里 Cassandra 把决定一致性程度的权利留给了客户自己。
所以,如果需要的话,你可以设定一致性级别和副本因子相等,从而达到一个较高的一致性水平,不过这样就必须付出同步阻塞操作的代价,只有所有节点都被更新完成才能成功返回一次更新。而实际上,Cassandra 一般都不会这么来用,原因显而易见(这样就丧失了可用性目标,影响性能,而且这不是你选择 Cassandra 的初衷)。而如果一个客户端设置一致性级别低于副本因子的话,即使有节点宕机了,仍然可以写成功。
总体来说,Cassandra 更倾向于 CP,虽然它也可以通过调节一致性水平达到 AP;但是不推荐你这么设置。
高可用和容错
从一般架构的角度来看,系统的可用性是由满足请求的能力来量度的。但计算机可能会有各种各样的故障,从硬件器件故障到网络中断都有可能。任何计算机都可能发生这些情况,所以它们一般都有硬件冗余,并在发生故障事件的情况下会自动响应并进行热切换。对一个需要高可用的系统,它必须由多台联网的计算机构成,并且运行于其上的软件也必须能够在集群条件下工作,有设备能够识别节点故障,并将发生故障的节点的功能在剩余系统上进行恢复。
Cassandra 就是高可用的。你可以在不中断系统的情况下替换故障节点,还可以把数据分布到多个数据中心里,从而提供更好的本地访问性能,并且在某一数据中心发生火灾、洪水等不可抗灾难的时候防止系统彻底瘫痪。
面向行
Cassandra 经常被看做是一种面向列(Column-Oriented)的数据库,这也并不算错。它的数据结构不是关系型的,而是一个多维稀疏哈希表。稀疏(Sparse)意味着任何一行都可能会有一列或者几列,但每行都不一定(像关系模型那样)和其他行有一样的列。每行都有一个唯一的键值,用于进行数据访问。所以,更确切地说,应该把 Cassandra 看做是一个有索引的、面向行的存储系统。
Cassandra 的数据存储结构基本可以看做是一个多维哈希表。这意味着你不必事先精确地决定你的具体数据结构或是你的记录应该包含哪些具体字段。这特别适合处于草创阶段,还在不断增加或修改服务特性的应用。而且也特别适合应用在敏捷开发项目中,不必进行长达数月的预先分析。对于使用 Cassandra 的应用,如果业务发生变化了,只需要在运行中增加或删除某些字段就行了,不会造成服务中断。
当然, 这不是说你不需要考虑数据。相反,Cassandra 需要你换个角度看数据。在 RDBMS 里, 你得首先设计一个完整的数据模型, 然后考虑查询方式, 而在 Cassandra 里,你可以首先思考如何查询数据,然后提供这些数据就可以了。
灵活的模式
Cassandra 的早期版本支持无模式(schema-free)数据模型,可以动态定义新的列。无模式数据库(如 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 当前状态的最佳方式可能是它支持灵活的模式。
高性能
Cassandra 在设计之初就特别考虑了要充分利用多处理器和多核计算机的性能,并考虑在分布于多个数据中心的大量这类服务器上运行。它可以一致而且无缝地扩展到数百台机器,存储数 TB 的数据。Cassandra 已经显示出了高负载下的良好表现,在一个非常普通的工作站上,Cassandra 也可以提供非常高的写吞吐量。而如果你增加更多的服务器,你还可以继续保持 Cassandra 所有的特性而无需牺牲性能。
3. 应用场景
大规模部署
Cassandra 的很多精巧设计都专注于高可用、可调一致性、P2P 协议、无缝扩展等,这些都是 Cassandra 的卖点。这些特性在单节点工作时都是没有意义的,更无法实现它的全部能力。
但是,单节点关系数据库在很多情况下可能正是我们需要的。所以你需要做一些评估,考虑你的期望的流量、吞吐需求等。关于评估没有什么硬性的指标和要求。但如果你认为有几种关系型数据库可以很好地应付你的流量,提供不错的性能,那可能选关系型数据库更好。简单地说,这是因为 RDBMS 更易于在单机上运行,对你来说也更熟悉。
但是,如果你认为需要至少几个节点才能支撑你的业务,那 Cassandra 就是个不错的选择。如果你的应用可能需要数十个节点,那 Cassandra 可能就是个很棒的选择了。
写密集、统计和分析型工作
考虑一下你的应用的读写比例,Cassandra 是为优异的写吞吐量而特别优化的。
许多早期使用 Cassandra 的产品都用于存储用户状态更新、社交网络、建议/评价以及应用统计等。这些都是 Cassandra 很好的应用场景,因为这些应用大都是写多于读的,并且更新可能随时发生并伴有突发的峰值。事实上,支撑应用负载需要很高的多客户线程并发写性能,这正是 Cassandra 的主要特性。
异地多活
Cassandra 直接支持多地分布的数据存储,Cassandra 可以很容易配置成将数据分布到多个数据中心的存储方式。如果你有一个全球部署的应用,那么让数据贴近用户会获得不错的性能收益,Cassandra 正适合这种应用场合。
变化的应用
如果你正在“初创阶段”,业务会不断改进,Cassandra 这种灵活的模式的数据模型可能更适合你。这让你的数据库能更快地跟上业务改进的步伐。
4. 数据模型
Table & KeySpace
Cassandra 中的 KeySpace 概念和 RDBMS 里面的 DataBase 概念很类似,一个 KeySpace 包含多张表,一般将有关联的数据表放到同一个 KeySpace 下面。KeySpace 创建的时候可以指定副本策略,副本因子以及是否启用 CommitLog 机制(类似 HBase 中的 WAL)。
Cassandra 中表的概念和 RDBMS 很类似。不同的是在 Cassandra 中属于同一张表的数据在物理上是分布在不同节点上存储的,同一张表由多个 Partition 组成。
Partitions
Cassandra 一般是由多台节点组成的,每台节点负责一定范围的,如果使用 Murmur3hash 的时候,每个节点负责的 Token 类似于下面那样:
所以 Token 范围为 -9223372036854775808 ~ -4611686018427387904 的数据存储在 A 节点;同理,Token 范围为 -4611686018427387903 ~ -1 之间的数据存储在 B节点,其他类似;每个 Token 范围由多个 Partition 构成,每个 Partition 由一行或多行数据组成。
在底层存储中,多个 Partition 组成一个 SSTable(Sorted-String Table)文件。那么同一个 SSTable 文件中的数据数据是如何组织的呢?答案是按照 Partition Key 计算得到的 Token 升序排序的。如果 Partition Key 由多个字段构成那么是将这多个字段拼在一起再计算拼成后字符串的哈希值。
Row
Partition 里面包含了零个或多个 Row,这些 Row 对应的 Partition Key 是一样的。
Cassandra 通过将列的信息(包括列的名称、类型、表名、keySpace等信息)保存到对应 SSTable 的 md-X-big-Statistics.db 文件中,相应的行只保存列是否存在的标记信息,这个可以节省存储空间的占用。HBase 存储数据的时候每个 Cell 都需要保存列名称和列族名称的。
那多个 Cell 之间的顺序是如何保证的呢?答案是按照列名称的字典顺序升序排序的。
Cell
Cell 就是每列数据的底层实现,Cell 里面包含了列的定义信息,比如是否被删除、是否过期、是否设置了时间戳等。在 Cassandra 里面,Column 有 Simple 和 Complex之分。non-frozen collection 或 UDT(用户自定义类型)的列是 ComplexColumn(Complex Cell)。
5. 使用注意事项
除了二级索引查询,其他所有查询必须指定
分区键
,即第一主键,用于决定数据存储于哪台机器。Cassandra也支持复合分区键,即多个字段作为分区键,定义时用小括号包起来。
第一主键称为分区键
Partition Key
,是用来分区的;除分区键以外的主键称为集群键Clustering Key
(从第二主键开始到最后),用于排序。硬盘中的数据是按照集群键的排列顺序存储的,所以查询时不能跳字段,必须从第二主键开始排序。
- 第一主键 只能用=号查询;第二主键往后 支持= > < >= <=; 二级索引列 只支持=号。
Cassandra支持创建二级索引,可以创建在除了第一主键(分区键:partition key)之外所有的列上;
如果查询条件里,有一个是根据索引查询,那其它非索引非主键字段,可以通过加一个ALLOW FILTERING来过滤实现;
- Cassandra 分页查询不支持跳页,每次分页必须从第一页开始,只能下一页/上一页的滚动翻页,分页无法取到总数量和总页码。
Cassandra 想要排序功能,在建表时就要指定字段的排序顺序,排序字段从第二主键开始,否则数据查询出来是乱序的。
Cassandra 排序需要从第二主键开始,第一主键无法作为排序字段。在查询时order by 字段排序顺序必须与建表时设置的字段顺序一致或完全相反。
- Cassandra查询功能较弱,适合作为存储结构简单的大数据量宽表,做一些简单查询。
Cassandra不支持join和其他复杂查询。
6. 架构
Cassandra 集群的架构类似于 redis cluster
,也是用一致性哈希算法来确定数据存放在那台机器,且也是使用虚拟节点的概念。集群节点间也是通过Gossip协议来实现无中心架构。
6.1 基本流程
点对点分布式系统,集群中各节点平等,数据分布于集群中各节点,各节点间每秒交换一次信息。
每个节点的commit log提交日志
记录写操作日志来确保数据持久性。
数据先被写入MemTable
(内存中的数据结构),待MemTable满后数据被写入SSTable
(硬盘的数据文件)。
所有的写内容被自动在集群中partition分区
并replicate复制
。
6.2 库表结构
Cassandra数据库面向行。用户可连接至集群的任意节点,通过类似SQL的CQL查询数据。
集群中,一个应用一般包含一个keyspace
,一个keyspace
中包含多个表,keyspace
类比关系型数据库中的database
。
6.3 读写请求
客户端连接到某一节点发起读或写请求时,该节点充当客户端应用
与拥有相应数据的节点
间的 coordinator协调者
以根据集群配置确定环(ring)中的哪个节点
来处理这个请求。
对Cassandra的读操作,会根据primary key
来确定请求被路由到哪个节点上去读取数据,节点上的数据按照cluster key
排列顺序存储。
6.4 关键词说明
关键词 | 名词解释 |
---|---|
Gossip |
点对点通信协议,用以Cassandra集群中节点间交换位置和状态信息。 |
Partitioner |
决定如何在集群中的节点间分发数据,即在哪个节点放置数据的第一个replica。 |
Replica placement strategy |
决定在哪些节点放置每行数据的其他replica。Cassandra在集群中的多个节点存储数据的多份拷贝(replicas)来确保可靠和容错。 |
Snitch |
定义了复制策略用来放置replicas和路由请求所使用的拓扑信息 |
Virtual nodes |
虚拟节点, 指定数据与物理节点的所属关系 |
Token Ring |
令牌环 |
6.5 节点间通信gossip
Cassandra使用点对点通讯协议gossip在集群中的节点间交换位置和状态信息。
gossip进程每秒
运行一次,与至多3个
其他节点交换信息,这样所有节点可以很快了解集群中的其他节点信息。
gossip协议的具体表现形式就是配置文件中的seeds种子节点。
一个注意点是同一个集群的所有节点的种子节点应该一致,否则如果种子节点不一致, 有时候会出现集群分裂,即会出现两个集群,一般先启动种子节点,尽早发现集群中的其他节点。
每个节点都和其他节点交换信息,由于随机和概率,一定会穷举出集群的所有节点,同时每个节点都会保存集群中的所有其他节点。
这样随便连到哪一个节点,都能知道集群中的所有其他节点。比如cql随便连接集群的一个节点,都能获取集群所有节点的状态,也就是说任何一个节点关于集群中的节点信息的状态都应该是一致的!
6.6 一致性哈希
图解:key的范围是0到2^32形成一个环,叫做hash空间环,即hash的值空间。对集群的服务器(比如ip地址)进行hash,都能确定其在环空间上的位置。
定位数据访问到相应服务器的算法:将数据key使用相同的函数H计算出哈希值h,然后根据h确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。
图解:由于一致性哈希算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜问题,所以引入了虚拟节点。
把每台server分成v个虚拟节点,再把所有虚拟节点(n*v)随机分配到一致性哈希的圆环上,这样所有的用户从自己在hash环上的位置顺时针往下取到第一个vnode就是自己所属节点。当此节点存在故障时,再顺时针取下一个作为替代节点。
图解:key经过hash会定位到hash环上的一个位置,找到下一个vnode为数据的第一份存储节点,接下来的两个vnode为另外两个副本。
6.7 hash值空间&token
上面在计算key存在哪个节点上是使用往前游走的方式找到环上的第一个节点,游走是一个计算的过程。
如果能够事先计算好集群中的节点(vnodes)在整个hash环的值空间,这样对key进行hash后,可以看它是落在哪个hash值空间上, 而值空间和节点的关系已经知道了,所以可以直接定位到key落在哪个节点上了。这就是token
的作用。
在Cassandra中,table的每行由唯一的primary key
标识,partitioner
分区器根据hash函数
计算出primary key的token
。Cassandra依据这个token值寻找在集群中放置对应的行。
7. 读写流程与存储机制
7.1 写流程
当写事件发生时,首先由Commit Log
捕获写事件并持久化,保证数据的可靠性。之后数据也会被写入到内存中,叫Memtable
,当内存满了之后写入数据文件,叫SSTable
,它是Log-Structured Storage Table的简称。 如果客户端配置了Consistency Level
是ONE,意味着只要有一个节点写入成功,就由协调者节点(Coordinator)返回给客户端写入完成。当然这中间有可能会出现其它节点写入失败的情况,Cassandra自己会通过Hinted Handoff或Read Repair 或者Anti-entropy Node Repair方式保证数据最终一致性。
Cassandra的存储结构类似LSM树
这种结构,不像传统数据一般都使用B+树,存储引擎以追加的方式顺序写入磁盘连续存储数据,写入是可以并发写入的,不像B+树一样需要加锁,写入速度非常高。
Commit Log
记录每次写请求的完整信息,此时并不会根据主键进行排序,而是顺序写入,这样进行磁盘操作时没有随机写导致的磁盘大量寻道操作,对于提升速度有极大的帮助。Commit Log
会在Memtable
中的数据刷入SSTable
后被清除掉,因此它不会占用太多磁盘空间,Cassandra的配置时也可以单独设置存储区,这为使用高性能但容量小价格昂贵的SSD硬盘存储Commit Log,使用速度慢但容量大价格非常便宜的传统机械硬盘存储数据的混合布局提供了便利。
写入到Memtable
时,Cassandra能够动态地为它分配内存空间,你也可以使用工具自己调整。当达到阀值后,Memtable中的数据和索引会被放到一个队列中,然后flush到磁盘,可以使用memtableflushqueue_size
参数来指定队列的长度。当进行flush时,会停止写请求。也可以使用nodetool flush工具手动刷新 数据到磁盘,重启节点之前最好进行此操作,以减少Commit Log回放的时间。为了刷新数据,会根据partition key对Memtables进行重排序,然后顺序写入磁盘。这个过程是非常快的,因为只包含Commit Log的追加和顺序的磁盘写入。
这里所述的写请求不单指Insert操作,Update操作也是如此,Cassandra对Update操作的处理和传统关系数据库完全不一样,并不立即对原有数据进行更新,而是会增加一条新的记录,后续在进行Compaction
时将数据再进行合并。Delete操作也同样如此,要删除的数据会先标记为Tombstone
,后续进行Compaction时再真正永久删除。
7.2 读流程
读取数据时,首先检查Bloom filter
,每一个SSTable都有一个Bloom filter用来检查partition key
是否在这个SSTable,这一步在访问任何磁盘IO的前面都会做。Bloom filter可能误判不会漏判:判断存在,但实际上可能不存在, 判断不存在,则一定不存在,则流程不会访问这个SSTable。如果存在,再检查partition key cache
,然后再做如下操作:
- 如果在cache中能找到索引,到
compression offset map
中找拥有这个数据的数据块,从磁盘上取得压缩数据并返回结果集。 - 如果在cache中找不到索引,搜索
partition summary
(partition index
的样本集)确定索引在磁盘上的近似位置,然后获取索引入口,在SSTable上执行一次单独的寻道和一个顺序的列读取操作,接下来也是到compression offset map
中找拥有这个数据的数据块,从磁盘上取得压缩数据并返回结果集。读取数据时会合并Memtable
中缓存的数据、多个SSTable
中的数据,才返回最终的结果。比如更新用户email后,用户名、密码等还在老的SSTable中,新的email记录到新的SSTable中,返回结果时需要读取新老数据并进行合并。
2.0之后的Bloom filter
,compression offset map
,partition summary
都不放在Heap中了,只有partition key cache
还放在Heap中。Bloom filter每billion partitions增长大约1到2G。partition summary是partition index
的样本,你可以通过index_interval来配置样本频率。compression offset map每TB增长1到3G。对数据压缩越多,就会有越多个数的压缩块,和越大compression offset table。
读请求(Read Request)分两种,一种是Rirect Read Request,根据客户端配置的Consistency Level读取到数据即可返回客户端结果。一种是Background Read Repair Request,除了直接请求到达的节点外,会被发送到其它复制节点,用于修复之前写入有问题的节点,保证数据最终一致性。客户端读取时,Coordinator首先联系Consistency Level定义的节点,发送请求到最快响应的复制节点上,返回请求的数据。如果有多个节点被联系,会在内存比较每个复制节点传过来的数据行,如果不一致选取最近的数据(根据时间戳)返回给客户端,并在后台更新过期的复制节点,这个过程被称作Read Repair 。
下图是Consistency Level 为ONE的读取过程,Client连接到任意一个节点上,该节点向实际拥有该数据的节点发出请求,响应最快的节点数据回到Coordinator后,就将数据返回给Client。如果其它节点数据有问题,Coordinator会将最新的数据发送有问题的节点上,进行数据的修复。
7.3 数据整理压缩(Compaction)
更新操作不会立即更新,这样会导致随机读写磁盘,效率不高,Cassandra会把数据顺序写入到一个新的SSTable,并打上一个时间戳以标明数据的新旧。它也不会立马做删除操作,而是用Tombstone来标记要删除的数据。Compaction时,将多个SSTable文件中的数据整合到新的SSTable文件中,当旧SSTable上的读请求一完成,会被立即删除,空余出来的空间可以重新利用。虽然Compcation没有随机的IO访问,但还是一个重量级的操作,一般在后台运行,并通过限制它的吞吐量来控制,compactionthroughputmbpersec
参数可以设置,默认是16M/s。另外,如果key cache显示整理后的数据是热点数据,操作系统会把它放入到page cache里,以提升性能。它的合并的策略有以下两种:
- SizeTieredCompactionStrategy:每次更新不会直接更新原来的数据,这样会造成随机访问磁盘,性能不高,而是在插入或更新直接写入下一个sstable,这样是顺序写入速度非常快,适合写敏感的操作。但是,因为数据分布在多个sstable,读取时需要多次磁盘寻道,读取的性能不高。为了避免这样情况,会定期在后台将相似大小的sstable进行合并,这个合并速度也会很快,默认情况是4个sstable会合并一次,合并时如果没有过期的数据要清理掉,会需要一倍的空间,因此最坏情况需要50%的空闲磁盘。
- LeveledCompactionStrategy:创建固定大小默认是5M的sstable,最上面一级为L0下面为L1,下面一层是上面一层的10倍大小。这种整理策略读取非常快,适合读敏感的情况,最坏只需要10%的空闲磁盘空间。
7.4 数据复制和分发
数据分发和复制通常是一起的,数据用表的形式来组织,用主键来识别应该存储到哪些节点上,行的copy称作replica
。当一个集群被创建时,至少要指定如下几个配置:Virtual Nodes,Partitioner,Replication Strategy,Snitch。
数据复制策略有两种,一种是SimpleStrategy
,适合一个数据中心的情况,第一份数据放在Partitioner确定的节点,后面的放在顺时针找到的节点上,它不考虑跨数据中心和机架的复制。另外一种是NetworkTopologyStargegy
,第一份数据和前一种一样,第二份复制的数据放在不同的机架上,每个数据中心可以有不同数据的replicas。
Partitioner策略有三种,默认是Murmur3Partitioner
,使用Murmur Hash。RandomPartitioner,使用Md5 Hash。ByteOrderedPartitioner使用数据的字节进行有顺分区。Cassandra默认使用MurmurHash,这种有更高的性能。
Snitch用来确定从哪个数据中心和哪个机架上写入或读取数据,有如下几种策略:
- DynamicSnitch:监控各节点的执行情况,根据节点执行性能自动调节,大部分情况推荐使用这种配置
- SimpleSnitch:不会考虑数据库和机架的情况,当使用SimpleStategy策略时考虑使用这种情况
- RackInterringSnitch:考虑数据库和机架
- PropertyFileSnitch:用cassandra-topology.properties文件来自定义
- GossipPropertyFileSnitch:定义一个本地的数据中心和机架,然后使用Gossip协议将这个信息传播到其它节点,对应的配置文件是cassandra-rockdc.properties
7.5 失败检测和修复
Cassandra从Gossip信息中确认某个节点是否可用,避免客户端请求路由到一个不可用的节点,或者执行比较慢的节点,这个通过dynamic snitch可以判断出来。Cassandra不是设定一个固定值来标记失败的节点,而是通过连续的计算单个节点的网络性能、工作量、以及其它条件来确定一个节点是否失败。节点失败的原因可能是硬件故障或者网络中断等,节点的中断通常是短暂的但有时也会持续比较久的时间。节点中断并不意味着这个节点永久不可用了,因此不会永久地从网络环中去除,其它节点会定期通过Gossip协议探测该节点是否恢复正常。如果想永久的去除,可以使用nodetool手工删除。
当节点从中断中恢复过来后,它会缺少最近写入的数据,这部分数据由其它复制节点暂为保存,叫做Hinted Handoff
,可以从这里进行自动恢复。但如果节点中断时间超过maxhintwindowinms(默认3小时)设定的值,这部分数据将会被丢弃,此时需要用nodetool repair在所有节点上手工执行数据修复,以保证数据的一致性。
7.6 动态扩展
Cassandra最初版本是通过一致性Hash来实现节点的动态扩展的,这样的好处是每次增加或减少节点只会影响相邻的节点,但这个会带来一个问题就是造成数据不均匀,比如新增时数据都会迁移到这台机的机器上,减少时这台机器上的数据又都会迁移到相邻的机器上,而其它机器都不能分担工作,势必会造成性能问题。从1.2版本开始,Cassandra引入了虚拟节点(Virtual Nodes)的概念,为每个真实节点分配多个虚拟节点(默认是256),这些节点并不是按Hash值顺序排列,而是随机的,这样在新增或减少一个节点时,会有很多真实的节点参与数据的迁移,从而实现了负载匀衡。
8. LSM树
LSM树(Log-Structured-Merge-Tree)的名字往往会给初识者一个错误的印象,事实上,LSM树并不像B+树、红黑树一样是一颗严格的树状数据结构,它其实是一种存储结构,目前HBase,LevelDB,RocksDB这些NoSQL存储都是采用的LSM树。
LSM树的核心特点是利用顺序写来提高写性能,但因为分层(此处分层是指的分为内存和文件两部分)的设计会稍微降低读性能,但是通过牺牲小部分读性能换来高性能写,使得LSM树成为非常流行的存储结构。
8.1 LSM树的核心思想
如上图所示,LSM树有以下三个重要组成部分:
1) MemTable
MemTable是在内存中的数据结构,用于保存最近更新的数据,会按照Key有序地组织这些数据,LSM树对于具体如何组织有序地组织数据并没有明确的数据结构定义。
因为数据暂时保存在内存中,内存并不是可靠存储,如果断电会丢失数据,因此通常会通过WAL(Write-ahead logging,预写式日志)的方式来保证数据的可靠性。
2) Immutable MemTable
当 MemTable达到一定大小后,会转化成Immutable MemTable(不可变的MemTable)。Immutable MemTable是将转MemTable变为SSTable的一种中间状态。写操作由新的MemTable处理,在转存过程中不阻塞数据更新操作。
3) SSTable(Sorted String Table)
有序键值对集合,是LSM树组在磁盘中的数据结构。为了加快SSTable的读取,可以通过建立key的索引以及布隆过滤器来加快key的查找。
这里需要关注一个重点,LSM树(Log-Structured-Merge-Tree)正如它的名字一样,LSM树会将所有的数据插入、修改、删除等操作记录(注意是操作记录)保存在内存之中,当此类操作达到一定的数据量后,再批量地顺序写入到磁盘当中。这与B+树不同,B+树数据的更新会直接在原数据所在处修改对应的值,但是LSM数的数据更新是日志式的,当一条数据更新是直接append一条更新记录完成的。这样设计的目的就是为了顺序写,不断地将Immutable MemTable flush到持久化存储即可,而不用去修改之前的SSTable中的key,保证了顺序写。
因此当MemTable达到一定大小flush到持久化存储变成SSTable后,在不同的SSTable中,可能存在相同Key的记录,当然最新的那条记录才是准确的。这样设计的虽然大大提高了写性能,但同时也会带来一些问题:
1)冗余存储,对于某个key,实际上除了最新的那条记录外,其他的记录都是冗余无用的,但是仍然占用了存储空间。因此需要进行Compact操作(合并多个SSTable)来清除冗余的记录。
2)读取时需要从最新的倒着查询,直到找到某个key的记录。最坏情况需要查询完所有的SSTable,这里可以通过前面提到的索引/布隆过滤器来优化查找速度。