理解cassandra架构

本文是我在看cassandra官方文档时写的一篇翻译,由于能力有限,很多地方都翻译的不到位或者和原意有出入,希望大家可以给予指正,由于翻译的较早,而且没有全部完成,有些观点可能已经在最新的cassandra中被更新或者改变,我也会在之后的时间将这些内容补全和修复

Cassandra是一种无性能损耗的高拓展性和可用性的分布式数据库。线性的可伸缩性以及被商品级硬件设施和云基础架构验证的容错技术使它成为任务关键型数据存储的良好平台。Cassandra支持跨多个数据中心的副本备份是非常棒的,为使用者提供低延迟和即使某个区域停电也能正常运转的放心。

 

节点间通信(gossip)

Gossip是一种点对点的通信协议,按照这个协议节点间定期的交换自己和其他他们知道的节点的状态信息。Gossip进程每秒运行一次来与集群上的最多其他3个节点交换状态信息。 这些节点交换自己的信息和其他的与这些节点通信的节点信息。所以所有的节点可以快速的了解到集群上的其他节点。

为了防止gossip通信出现问题,为集群中所有的节点使用了相同列表的种子节点。这种情况在第一个节点启动时非常重要。默认情况下,一个节点在随后的重启中会记住与它通信的其他节点。种子节点的选定除了为新节点加入集群时引导gossiped过程外再无其他目的。种子节点不会有单点故障,除了引导节点外再无其他特定的目的。

在多个数据中心集群,从每个数据中心的种子列表中选取一个节点是一个好主意。建议每个数据中心指定多余一个种子节点来提高容错能力。否则,当引导一个节点时gossip不得不与另一个数据中心通信。

不建议为每个节点都创建种子节点,因为这增了加维护量同时降低了性能。Gossip的优化不是关键,但是建议使用一个有少量种子节点的列表(大约每个数据中心三个节点)

 

失败检测和恢复

如果另一个节点挂掉了或者正在恢复中,失败检测方法用于本地确定gossip的状态和历史。Cassandra使用这个信息避免客户端请求被路由到不可到达的节点。(Cassandra也可以避免请求被路由到活着的但是效率低下的节点,请参考dynamic snitch

 

Gossip进程使用直接方式(节点的gossiping直接导向他)和间接方式(节点的信息被传达2手,三手,等等)跟踪节点的状态。Cassandra不是使用固定的阈值来标记失败的节点,而是使用一个责权检测机制来计算考虑了账户网路性能,工作量和历史条件的每个节点的阈值。在gossip交换期间,每个节点维护一个与集群中的其他节点的关于gossip信息的上次到达时间的滑动窗口。通过配置phi_convict_threshold 属性来调整错误检测器的灵敏程度。较低的值增加了无反应节点被标记为down的可能性,而较高的值减少了瞬时错误引起的节点故障的可能性。大多数情况使用默认值即可,但是为亚马逊EC2将其增加到10-12(由于经常遇到的网络拥堵)。在不稳定的网络环境(例如有时候EC2就是这样),提高值为10或12有利于防止虚假故障,不建议使用值高于12和低于5。

节点失败可能由各种原因引起,如硬件失败和网络中断。节点中断往往瞬变,但可以持续很长时间。因为节点中断很少意味着它将从集群中永久的离开,这不会自动导致永久从环中移除节点。其他节点将定期与失败节点尝试建立连接查看他们是否备份。要在集群上永久改变节点的资格,管理员必须从cassandra环上使用 nodetool utility.工具精准的添加或者移除节点。当一个节点在宕机后重新联机时,或许它已经遗失了它所维护的对于副本数据的写操作。存在一个修复机制以恢复遗失的数据,例如hinted handoffs和使用nodetool手动修复。宕机时间的长短将决定哪种修复机制被用于保持数据一致性。

 

一致性hash允许数据分布跨越一个集群,以减少增加或减少节点时进行的重组操作。一致性hash基于partition key为数据分区,关于partition key和主见的介绍,请参考

 Data modeling example

 

例如,你有如下数据

 

Cassandra是一种无性能损耗的高拓展性和可用性的分布式数据库。线性的可伸缩性以及被商品级硬件设施和云基础架构验证的容错技术使它成为任务关键型数据存储的良好平台。Cassandra支持跨多个数据中心的副本备份是非常棒的,为使用者提供低延迟和即使某个区域停电也能正常运转的放心。

 

节点间通信(gossip)

Gossip是一种点对点的通信协议,按照这个协议节点间定期的交换自己和其他他们知道的节点的状态信息。Gossip进程每秒运行一次来与集群上的最多其他3个节点交换状态信息。 这些节点交换自己的信息和其他的与这些节点通信的节点信息。所以所有的节点可以快速的了解到集群上的其他节点。

为了防止gossip通信出现问题,为集群中所有的节点使用了相同列表的种子节点。这种情况在第一个节点启动时非常重要。默认情况下,一个节点在随后的重启中会记住与它通信的其他节点。种子节点的选定除了为新节点加入集群时引导gossiped过程外再无其他目的。种子节点不会有单点故障,除了引导节点外再无其他特定的目的。

在多个数据中心集群,从每个数据中心的种子列表中选取一个节点是一个好主意。建议每个数据中心指定多余一个种子节点来提高容错能力。否则,当引导一个节点时gossip不得不与另一个数据中心通信。

不建议为每个节点都创建种子节点,因为这增了加维护量同时降低了性能。Gossip的优化不是关键,但是建议使用一个有少量种子节点的列表(大约每个数据中心三个节点)

 

失败检测和恢复

如果另一个节点挂掉了或者正在恢复中,失败检测方法用于本地确定gossip的状态和历史。Cassandra使用这个信息避免客户端请求被路由到不可到达的节点。(Cassandra也可以避免请求被路由到活着的但是效率低下的节点,请参考dynamic snitch

 

Gossip进程使用直接方式(节点的gossiping直接导向他)和间接方式(节点的信息被传达2手,三手,等等)跟踪节点的状态。Cassandra不是使用固定的阈值来标记失败的节点,而是使用一个责权检测机制来计算考虑了账户网路性能,工作量和历史条件的每个节点的阈值。在gossip交换期间,每个节点维护一个与集群中的其他节点的关于gossip信息的上次到达时间的滑动窗口。通过配置phi_convict_threshold 属性来调整错误检测器的灵敏程度。较低的值增加了无反应节点被标记为down的可能性,而较高的值减少了瞬时错误引起的节点故障的可能性。大多数情况使用默认值即可,但是为亚马逊EC2将其增加到10-12(由于经常遇到的网络拥堵)。在不稳定的网络环境(例如有时候EC2就是这样),提高值为10或12有利于防止虚假故障,不建议使用值高于12和低于5。

节点失败可能由各种原因引起,如硬件失败和网络中断。节点中断往往瞬变,但可以持续很长时间。因为节点中断很少意味着它将从集群中永久的离开,这不会自动导致永久从环中移除节点。其他节点将定期与失败节点尝试建立连接查看他们是否备份。要在集群上永久改变节点的资格,管理员必须从cassandra环上使用 nodetool utility.工具精准的添加或者移除节点。当一个节点在宕机后重新联机时,或许它已经遗失了它所维护的对于副本数据的写操作。存在一个修复机制以恢复遗失的数据,例如hinted handoffs和使用nodetool手动修复。宕机时间的长短将决定哪种修复机制被用于保持数据一致性。

 

一致性hash允许数据分布跨越一个集群,以减少增加或减少节点时进行的重组操作。一致性hash基于partition key为数据分区,关于partition key和主见的介绍,请参考

 Data modeling example

 

例如,你有如下数据

 

name

age

car

gender

jim

36

camaro

M

carol

37

bmw

F

johnny

12

 

M

suzy

10

 

F

Cassandra assigns a hash value to each partition key:

Partition key

Murmur3 hash value

jim

-2245462676723223822

carol

7723358927203680754

johnny

-6723372854036780875

suzy

1168604627387940318

 

集群中的每个节点对基于hash值得一段数据负责。在集群中四个节点的hash值

 

cassandra基于partition key的值和每个节点所负责的值得范围来为每个节点分配数据。例如,在一个4个节点的集群 中,数据如下分布

Node

Start range

End range

Partition key

Hash value

A

-9223372036854775808

-4611686018427387904

johnny

-6723372854036780875

B

-4611686018427387903

-1

jim

-2245462676723223822

C

0

4611686018427387903

suzy

1168604627387940318

D

4611686018427387904

9223372036854775807

carol

7723358927203680754

 

 

Virtual nodes

 

虚拟节点,称为Vnodes,以更新的力度跨节点分布数据,如果使用计算token的话这很容易实现。Vnodes简化了cassadra中的许多任务。

 

 

更多信息,请参考cassandora1.2 中关于虚拟节点的主题。

Virtual nodes in Cassandra 1.2

 

将一个存在的节点转换为虚拟节点,参考

Enabling virtual nodes on an existing production cluster.

 

How data is distributed across a cluster (using virtual nodes)

 

虚拟节点使用一致性hash来分配数据,不需要生成和分配新的token

 

Partitioners

 

一个partitioner决定数据分布式的存储在集群中的节点上(包括副本)。基本上,分区程序使用hash函数从partition key中派生出一个token,该token代表了一行数据。每行的数据之后按token的值跨集群分布。

Murmur3Partitioner 和RandomPartitioner 使用token来为每个节点分配相同比例的数据,表格中的数据均等的分布在整个环上,或是其他组上如秘钥空间。即使表使用不同的分区键如username或者timestamps也是按这种情况来做。而且在此基础上,对于集群的读写操作也被均匀的分布,负载均衡被简化因为hash range的每个部分获取到row的数量是均等的。更多细节参考

Consistent hashing.

 

这两个分区算法的区别是如何生成token的hash值,RandomPartitioner 使用一个加密hash,他生成hash的时间要比Murmur3Partitioner 长,Cassoandra不需要加密hash,所以使用Murmur3Partitioner 在3-5分钟内产生完hash值可以改进效率。

 

Cassandra支持如下的分区程序,可以在cassandra.yaml文件中设置他们。

Murmur3Partitioner (默认)基于MurmurHash  hash值跨集群均匀分布节点。

RandomPartitioner 基于MD5  hash值跨集群均匀分布节点。

ByteOrderedPartition 按照数据的秘钥字节字典顺序有序分布。

Murmur3Partitioner 作为默认的分区策略应用于cassandra1.2以后,在部分情况下作为生成新集群的首选。

然而,分区程序间并不兼容, 使用一种分区程序后很难再转换为另一种分区程序。

如果使用虚拟节点(vnodes),你不必计算tokens。如果不使用vnodes,你必须计算tokens的数量然后为cassandra.yaml文件中的initial_token参数赋值。请参见

Generating tokens  同时使用某种分区程序的对应方法。

 

Murmur3Partitioner是默认分区程序,Murmur3Partitioner比RandomPartitioner更快更有效率,Murmur3Partitioner 可以使用vnodes,然而,如果你不使用vnodes,你必须计算token的数量,正如

Generating tokens中描述的那样。

为新的集群使用Murmur3Partitioner。你无法将已经存在的集群的分区程序改变为另一种分区程序。Murmur3Partitioner使用

MurmurHash 函数。该 哈希函数创建一个64-bithash值作为分区键,可能的hash值得范围从

 -263 to +263-1

当使用Murmur3Partitioner时,

你可以通过使用token功能在一个CQL语句中查询所有行。

 

RandomPartitioner 是cassandra1.2之前的默认分区程序,它包含向后的兼容性。RandomPartitioner 可以和vnodes一起使用。然而,如果你不使用vnodes,你必须计算tokens,如Generating tokens中描述的那样。RandomPartitioner 使用MD5 hash值作为行的键值来跨多个节点均匀的分布数据。可能的hash值范围从0-2127-1

 

casssandra为顺序分区提供ByteOrderedPartition方式。他提供向后兼容性。分区程序使用秘钥字节的词法顺序来排序行。通过查看你的分区秘钥数据的实际值并且使用秘钥中前导字符的十六进制表示形式来计算tokens的值。例如,如果你想按照行的字母顺序分区,你可以使用16进制表示形式41带指定A token.

使用顺序分区允许基于主键 的顺序查找。这意味着你可以进行扫描就好像你通过传统的索引移动光标一样。例如,如果你的应用使用用户名作为分区主键,你可以检索用户名落在jakejoe之间的行,这种检索形式使用RandomPartitioner 关键字是不可能实现的,因为键是以MD5 hash值来存储的(非顺序的)。

虽然有能力在行上做范围查询听起来是顺序分区的一个可取特点,但是通过使用表索引可以实现相同的功能。

以下情况不建议使用顺序分区程序:

Difficult load balancing

对于集群的负载均衡来说需要更多的管理开销。一个顺序的分区程序需要管理员基于分区秘钥分配的预估值手动计算分区的范围。在实践中,一旦分布式的数据被加载,就需要积极的四处移动节点的token来容纳这些数据。

 

Sequential writes can cause hot spots 顺序写可能导致热点发生

如果你的应用试图一次写入或者更新一个顺序块的行的数据,然后这些写入不会垮集群分布式,他们都被提交至一个节点。这个问题通常在应用程序处理时间戳数据时产生

 

 

Uneven load balancing for multiple tables -多个表的不平衡的负载均衡

如果你的应用程序有多个表,这些表有不同的行键值和不同的分布式数据。顺序分区程序使一个表平衡可能导致热点和同一集群下其他表的不均匀分布。

 

 

 

Snitches

 

snitch决定节点属于哪些数据中心和机架。他们告知cassandra使用什么样的网络拓扑结构以便请求被有效的路由同时让cassandra通过将机器分组到数据中心和机架的方式来分发副本。具体来说,驻留在副本上的副本策略基于由新的snitch提供的信息。所有节点都必须返回到同一机架和数据中心。cassandro尽量不在同一机架上存储一个以上副本。如果你改变snitch,你需要执行额外的步骤因为snitche影响了副本的驻留位置。参考

 Switching snitches.

 

默认情况下,所有的snitches还使用了一个动态snitch层来监视读取延迟,并在可能的情况下,将请求从表现不佳的节点路由到别的节点。动态snitch默认情况下是可用的而且是被建议用在大多数部署上的。更多关于动态snitch如何工作,请参考

 Dynamic snitching in Cassandra: past, present, and future.

 

在cassandra.yaml文件中为每个节点配置动态snitch的阈值

 

 cassandra-topology.properties文件中的详细网络信息,使用这个snitch时,你可以自定义你的数据中心名字。确保数据中心的名字和秘钥空间中定义的数据中心名字对应。集群中的每个节点都应该在cassandra-topology.properties文件中定义,同时,这个文件应该和集群上的每个节点完全等价。

 

产品:

如果你两个非均匀的ip和两个物理数据中心,每个数据中心有2个机架,一个分析数据中心用于副本数据分析。那么cassandra-topology.properties 文件可能类似于

 

# Data Center One

175.56.12.105=DC1:RAC1
175.50.13.200=DC1:RAC1
175.54.35.197=DC1:RAC1

120.53.24.101=DC1:RAC2
120.55.16.200=DC1:RAC2
120.57.102.103=DC1:RAC2

# Data Center Two

110.56.12.120=DC2:RAC1
110.50.13.201=DC2:RAC1
110.54.35.184=DC2:RAC1

50.33.23.120=DC2:RAC2
50.45.14.220=DC2:RAC2
50.17.10.203=DC2:RAC2

# Analytics Replication Group

172.106.12.120=DC3:RAC1
172.106.12.121=DC3:RAC1
172.106.12.122=DC3:RAC1

# default for unknown nodes
default =DC3:RAC1

 

 

 

 

 

Determines the location of nodes by rack and data center.

GossipingPropertyFileSnitch 的配置包含在cassandra-rackdc.properties文件中。

为了配置一个节点使用GossipingPropertyFileSnitch ,按照如下编辑cassandra-rackdc.properties

定义数据中心和机架包含这个节点,默认的设置为:

dc=DC1
rack=RAC1

 

数据中心和机架名字是大小写敏感的

 

为了节省带宽,添加prefer_local=true 选项,这个选项告诉cassandra当通信非跨越多个数据中心时使用本地IP地址

 

从propertyfilesnitch迁移到gossipingpropertyfilesnitch

为了允许从propertyfilesnitch迁移,当cassandra-rackdc.properties文件文件存在时,gossipingpropertyfilesnitch会使用它。迁移完成之后删掉这个文件。更过关于迁移的信息,参见

Switching snitches.

 

cassandra-topology.properties文件存在时,GossipingPropertyFileSnitch 总是导入它。在所有集群的每个节点上移除这个文件,或者在任何从PropertyFileSnitch.迁移过来的集群上移除这个文件。

 

 

 

 

 

 

 

 

Related information

Install locations

 

 

 

Storage engine

Cassandra 使用一个类似于Log-Structured Merge Tree的存储结构。不像典型的关系型数据库使用B树。Cassandra避免在写之前读。Read-before-write,特别是在大型分布式系统中,可能导致读性能的大量延迟和其他问题。例如,两个客户端同时读,其中一个使用UpdateA更新了行,另一个使用updateB操作更新航。同时移除UpdateA. 这种竞争将导致模糊的查询结果,哪个update是正确的呢?

要避免在cassandra中使用在写之前读,存储引擎组在内存中插入和更新数据,一段时间之后,顺序的以追加模式将数据写入磁盘。一旦写入磁盘,数据将变成不可变的并且永远不会被覆盖。读取数据涉及结合这个永恒不变的顺序写数据来发现正确的查询结果。你可以使用轻量级的事物(LWT)在写数据之前检查数据的状态。然而,此功能建议有限的使用它。

Log-structured引擎避免覆盖和使用顺序I/O来更新数据对于写入SSD和HDD来说是个必要条件。在HDD上,随机写操作比顺序写操作涉及更多的寻址操作。寻址操作性能损失非常大。因此Cassandra是按顺序永久写入文件的,因此避免了写放大和磁盘失败,数据库容器很便宜,使用SSD性能会更好。对于许多其他类型数据库,在SSD上的写放大问题。

 

Cassandra 如何读写数据

为了管理和访问cassandra中的数据,理解cassandra如何存储数据非常重要。Hinted handoff特性加上cassandra对于数据库ACID(atomic,consistent,isolated,durable)的一致性与非一致性是理解cassandra读写的关键概念。在cassandra中,一致性是指如何在所有副本上更新和同步一行数据。

客户端工具和应用程序接口(APIs)可用于开发数据存储和检索的应用程序。

 

数据如何写入

cassandra分为几个阶段处理数据的写入,从log的写入开始,以数据写入到磁盘结束。

 

log写入和内存存储

当一次写入发生时,cassandra在内存结构中存储的数据称为内存表,同时提供

configurable durability,.同时写操作被追加到磁盘上的commit log中。Commit log接收每个写入cassandra节点的写请求,这些长久的写操作永久存在即使一个节点断电。内存表是cassandra通过键查找的数据分区回写缓存。内存表按顺序存储写操作直到达到设置上限,然后flush他们。

 

从内存表中flush数据

为了flush数据,cassandra以内存中的顺序将数据写入磁盘。一个分区索引同时在磁盘上被创建出来用来将token映射到磁盘上的一个位置。当内存结构表中的内容超过configurable threshold或者commitlog的空间超过了commitlog_total_space_in_mb , 内存表被放置在一个队列中向磁盘flush. 这个队列在cassandra.yaml文件中通过

memtable_heap_space_in_mb or memtable_offheap_space_in_mb属性来设置。如果将要flush的数据超过了memtable_cleanup_threshold的限制,cassandra将阻塞写直到下一次flush成功。你可以使用

 nodetool flushor nodetool drain手动flush一张表。为了减少commit log回溯的时间,建议的最佳做法是在重启节点之前刷新内存表。如果一个节点停止工作,回溯commit log将会使内存表回到节点停止前的状态。

 

Commit log中的数据在对应的内存表中的数据被刷新到磁盘上的SStable中后被清除。

 

在磁盘上的SStable中存储数据。

内存表和SSTable中的每个表都是可维护的。Commit log在表之间共享。SSTable是不可变的,在内存表被刷新之后不会再次被写入。因此,一个分区通常跨多个SSTable文件存储,许多其他的sstable结构文件用于协助读操作。

 

 

 

对于每个sstable,cassandra创建如下结构:

Data(Data.db)

sstable数据

Primary Index(Index.db)

每行键的索引,指向数据文件中他们的位置。

Bloom filter (Filter.db) 

一个存储于内存中的结构用于在访问磁盘上的sstable之前检查行数据是否存在于内存表

Compression Information (CompressionInfo.db) 

一个用于保存未压缩文件长度的信息,数据偏移量块和其他压缩信息

Statistics (Statistics.db) 

关于sstable内容的统计元数据。

Digest (Digest.crc32, Digest.adler32, Digest.sha1) 

一个保存adler32数据文件校验和的文件

CRC (CRC.db) 

一个保存CRC32未压缩文件数据块的文件

SSTable Index Summary (SUMMARY.db)

一个分区索引存储于内存中的例子

SSTable Table of Contents (TOC.txt)

一个用于存储所有sstable TOC组件列表的文件

Secondary Index (SI_.*.db)

内置的二级索引。每个sstable文件可能存在多个sls

 

sstable是存储在磁盘上的文件。Cassandra2.2之后为了缩短文件路径改变了文件的命名约定。数据文件存在于通过安装文件安装的数据目录中,对于每一个秘钥空间,每张表存储在每个数据文件夹内。例如:

/data/data/ks1/cf1-5be396077b811e3a3ab9dc4b9ac088d/la-1-big-Data.db 

代表了一个数据文件。Ks1代表了秘钥空间名字以区别用于以流方式或者批量方式加载数据的秘钥空间。本例中的

5be396077b811e3a3ab9dc4b9ac088d 十六进制字符串,被追加到表的名称中代表表的唯一Id

 

cassandra为每个表创建一个子目录,可以允许你将表连接到一个可选的物理驱动器或者数据卷。这提供了一种能力-将非常活跃的表移动到更快的介质上,例如SSD提供的更好执行效率,同时为了更好的存储层I/O均衡也可以跨所有的附加存储设备来划分表。

 

数据如何维护

Cassandra写过程将数据存储在称为SSTable的结构中。SSTable是不可变得。Cassandra在新的SSTables中为插入和更新的数据写入新的时间戳来代替插入和更新数据时覆盖已经存在的行。同时,cassandra也不会就地删除数据,相反,cassandra使用一个结束标记

tombstone

来标记数据已经被删除。

随着时间的推移,cassandra可能对一行写出很多版本,每一个在不同的sstable中。每个版本可能有一系列唯一的列存储,使用不同的时间戳。这意味 着cassandra必须访问越来越多的sstable以检索整行数据。

要保证数据库健康,cassandra定期合并sstable,丢弃旧数据,这个过程称为压缩。

压缩

压缩通过partition key来合并每个sstable表中的数据,选择最新时间戳的数据版本。合并过程是很有效率的,因为行是按照每个sstable表分区键进行排序的,合并的过程不使用随机I/O. 在移除删除的列和行之后,压缩过程整合sstables 成为一个新的单独的sstable.只要使用旧的文件的被挂起的读操作完成,旧的sstable就将被删除

 

压缩导致了旧的sstable和新的sstable在磁盘空间和I/O上的临时共存,当压缩完成时,压缩释放由旧的SSTable占据的磁盘空间。用压缩后的sstable逐步取代旧的sstable提高了读取的性能。Cassandra能够直接从新的sstable表中读取数据而不需要等待整个压缩过程完成

 

当cassandra处理读写时,他同时在页面缓存中将旧的sstable文件替换为新的sstable文件。当将读操作从旧的sstable一点点导出时,缓存新的sstable的工作在同时进行,这不会引起巨大的缓存遗失。即使在高负载下,cassandra也能提供一个可预知的高性能

 

压缩策略

cassandra支持不同的压缩策略。每个都有自己的特点。为了正确的选取合适你的应用工作负载的策略,了解每个策略如何工作十分重要。虽然以下的每节都以广义的介绍开始,但是如何选取一个压缩策略仍然有很多复杂的因素。有关更多的深入了解每种策略有何优势以及讨论每种策略特定的用例,请参考

Which compaction strategy is best?.

 

 

SizeTieredCompactionStrategy (STCS) 

 

建议写密集型工作负载

当积累一系列(默认4个)相似大小的SSTable,The SizeTieredCompactionStrategy (STCS)启动一次压缩。 这种压缩策略将这些sstable压缩成一个更大的sstable. 然后这些更大的sstable被压缩成更大的sstable.在任何给定的时间,一些不同大小的sstable是共同存在的。

 

 

 

这种策略对于压缩一个写密集型的工作负载来说能够很好的工作,但他使读变得很慢,因为按照大小进行合并的过程

不会按照行来进行分组。由于这个原因,更可能的是对于特定行的数据可能遍布许多sstables。此外,对于删除数据的回收并没有如预期般发生,因为sstable的大小是压缩的触发器,同时sstable不可能增长的足够以合并和回收旧数据。因为最大的sstable的大小不断增长,用于压缩存储新旧sstable的磁盘空间的数量可能超过一个典型节点上的磁盘空间的数量。

 

Pros:写密集型压缩工作负载运行良好

Cons:可以保留陈旧的数据很长时间, 内存需要量随时间而增长。

 

LeveledCompactionStrategy (LCS)

 

建议读密集型工作负载使用

The LeveledCompactionStrategy (LCS)用于减轻STCS的有关读操作发生的一些问题。这种策略工作在一系列的等级之上。首先,内存表在L0上首先被刷新到SSTables.然后,压缩在L1将sstables合并成更大的sstable.

 

 

大于L1等级的sstables将合并成一个大于等于sstable_size_in_mb (默认160M)

大小的sstable. 如果一个L1 等级的sstable存储了大于L2等级的分区数据,LCS将把SStable移动到L2的下一等级上。

在许多插入之后的等级压缩

 

在L0以上的每个等级,LCS创建相同大小的SSTable.每个等级是上一个等级的10倍大小。所以level1是L0的10倍,L2是L0的100倍。如果压缩的结果是L1的等级 上有多于10个sstable,那么多于的sstable将会被移动到L2。

 

LCS压缩过程保证以L1开始的每层的sstable没有重叠数据。对于许多读取操作,这种保证可以使cassandra能够仅从1-2个表中检索需要的数据,事实上,所有读取请求的90%由一张sstable就可以满足。因为LCS不会压缩L0的表,然而,涉及许多L0层sstable的资源密集型的读取仍然可能发生。

 

在L0层之上,LCS需要较少的磁盘空间用于压缩-通常状况下,10倍的SSTable的固定大小。过时的数据更加频繁的被清除,所以,在磁盘上清除sstable中使用较少的那部分数据。然而,LCS压过过程发生的太频繁给节点带来了过多的I/O负担,

对于写密集的工作流程,使用这种策略的回报与I/O操作带来的性能损失相比通常是不值得的。在许多情况下,LCS-configured测试表揭示了写和压缩的I/O饱和程度

 

Cassandra2.2以后使用LCS在集群中引导一个新的节点性能改进绕过了压缩操作,原始的数据直接被移动到了正确的LEVEL因为没有已经存在的数据。所以每层没有分区层叠出现。更多的信息,请参考Apache

Apache Cassandra 2.2 - Bootstrapping Performance Improvements for Leveled Compaction.

 

好处:磁盘要求更容易预测。读操作延迟更容易遇见。陈旧的数据更加频繁的被清楚。

缺点:更高的I/O利用率影响操作的延迟

 

DateTieredCompactionStrategy (DTCS)

 

建议时间序列和到期时间工作流使用

 

DTCS与STCS类似。但是不是基于SSTable的大小而压缩,而是基于SSTable的年龄而压缩。配置time windows确保新数据和旧数据不会在在合并表中被混合。事实上,使用(Time-To-Live)TTL时间戳,DTCS可以将全部的过期数据都清理出去。如果时间序列的提取以一个平稳的速率,这种策略经常会生成类似大小的sstable表。

 

当系统在可配置的时间间隔内达到了确定的最低限度数目的sstable的时候,DTCS将会合并sstable表。就像在STC中的一样,需要的sstable数量在给定的时间内减少时,DTCS将sstable表合并成一个更大的表。然而,DTCS放弃压缩到达了一个按照配置确定的阶段的SSTable。这减少了时间数据的重写次数。

对于最后一个小时的有价值的数据的查询(或者与配置的时间间隔良好协调的其他时间间隔)能在DTCS-compacted sstables上被非常高效率的执行。

 

有一种情况使用这种策略会导致困难-无序写入,例如:一次操作使用了一个过去的时间戳写入了一条时间戳记录。读取修改可能引入一条无需的时间戳,所以,确保当使用DTCS时关闭读取修改功能。

 

好处:专门用于时间序列数据

缺点:无序的时间注入可能引起错误。读取修复必须关闭DTCS,同时时间戳操作不允许使用BATCH, DELETE, INSERT and UPDATE CQL 命令。

 

那种压缩策略最好:

要实现最好的压缩策略:

1 重新审视你的应用的需求

2 配置表以使用最合适的策略

3 针对你的数据测试压缩策略

 

以下的问题基于cassandra开发者和使用者的经验来描述上述策略

 

你的表格需要处理时间序列数据吗?

如果需要,DTCS是你最好的选择。要避免问题发生,重审上述描述。如果你的表格不关注时间序列数据,那么选择可能会变得很复杂,以下关于其他因素的介绍问题可以指导你的选择。

 

你的表是处理读多还是写多?

如果你的表处理读是写的2倍以上那么LCS是个很好的选择,特别是随机读。如果读写比例接近,那么LCS的性能损失带来的弊端将比利益还高。避免LCS被高容量的写入搞得很快不堪重负。

表中的数据经常变吗?

LCS的一个优势是它在一个小的SSTable集合中保存相关数据。如果你的数据不可变或者不受经常的插入删除的影响,STCS可以实现相同类型的分组而不受LCS性能的影响。

 

你需要读写活动的预测值吗?

LCS在可预见的的大小和数量内保持SSTable,例如,如果你的表的读/写  率很小,同时在读上期望符合Service Level agrrementst(SLA),LCS为了在可预见的等级上保证读的效率和延迟而带来的写的性能损失是值得的。同时你可以通过横向扩展增加更多节点来克服写上的损失。

你的表将由批处理程序填充吗?

对于批处理读写,STCS的性能要高于LCS。批处理基本不会产生碎片,而LCS却没有这种好处。同时批处理操作也是压垮LCS-configured 表。

你的系统有磁盘空间限制吗?

LCS处理磁盘空间比STCS更有效率;除了用于数据处理,还需要10%的额外空间。STCS和DTCS大多数情况需要50%以上的剩余空间。

你的系统到达了I/O限制了吗?

LCS比DTCS或者STCS需要更多的I/O操作,切换到LCS可能带来更多的I/P负载以抵消其优势。

 

测试压缩策略

一些建议用来决定哪个压缩策略适合你的系统:

1 使用其中的一个压缩策略创建一个三个节点的集群,使用cassandra-stress进行压力测试,查看结果

2 在已经存在的集群上创建一个节点同时使用cassandra 写测量模式来举例live data。请参考

What’s new in Cassandra 1.1: live traffic sampling.

 

 

配置和运行压缩

在create table 或者 alter table命令中使用参数来为一张表设置压缩策略。具体请参考

Table properties.

 

 

你可以使用nodetool compact 命令手动开始压缩。

 

更多关于压缩的信息

以下一些开发者博客提供了更多关于测试压缩策略的信息

 

 

数据如何更新?

插入一个完全一个样的主键被视为upsert. 如果一条记录在数据库中不存在,一个upsert将在数据库中写入一条记录. 如果那条数据的主键已经存在,一条新的包含一个最新时间戳的记录将被写入。如果这条数据在一次读请求中被检索,只有最新的记录会被检索到,老一点的时间戳数据将被标记为删除。最后的效果类似于使用新数据交换覆盖旧数据,即使cassandra不会覆盖数据。最终,更新使用顺序I/O存储到磁盘存于新的sstable. 在更新期间,cassandra使用

write path

记录时间戳并且在磁盘上写入列。如果用户多次写入相同的数据,只有内存表中存储的最新的数据才会被刷新到磁盘上。

 

数据如何删除?

Cassandra删除数据的过程被设计用来改进性能,同时与cassandra内置的属性一起工作来分布式数据和提高容错。

Cassandra将删除视为插入或者upsert,以delete 命令加入分区的数据其实是一个叫做tombstone

的删除标记。墓碑经过Cassandra的写路径,被写入一个或多个节点的sstable中。墓碑关键的区别特征是:它有一个内置的过期时间,在过期时间结束时,墓碑将作为正常Cassandra压缩过程的一部分被删除。

 

在一个分布式系统中删除

在一个多节点集群中,cassandra在两个或者更多节点上存储同一数据的多个副本,这防止了数据的丢失,但这复杂化了删除过程。如果一个节点收到了一条删除它所存储的本地数据的命令,该节点墓碑指定的记录并试图将墓碑传递到包含该记录的副本的其他节点上。但如果一个副本节点在那个时刻没有响应,它没有马上收到墓碑,所以它仍然包含着预删除记录的版本。如果在该节点恢复前集群中的其他节点已经删除了墓碑,Cassandra把该刚恢复过来的节点上的这条记录视为新数据,然后把他传播到集群中其他的节点上。这种标记为已经删除但是仍然存在的记录被称为

zombie

为了防止僵尸记录的再现,cassandra给予每一个墓碑一个过渡期。这个过渡期的目的是给予响应节点时间以恢复和正常处理墓碑。如果一个客户端在过渡期内向墓碑记录发送了一条更新命令,cassandra将会覆盖该墓碑。如果一个客户端在过渡期向墓碑记录发送了一条读命令,cassandra将忽略这个墓碑同时从其他副本查找可能存在的该条记录。

 

当一个停止响应的节点恢复时,cassandra使用 hinted handoff 来重做当节点挂掉时未作的数据库的插入和删除。在一个墓碑的过渡期,cassandra不会重做插入和删除。如果一个节点在过渡期结束后仍然没有恢复,cassandra可能会错过删除这个节点。

 

Hinted Handoff: repair during write path

有时,当数据被写入时一个节点会变得不可用。不可用的原因可能是硬件原因,网路问题,或者过载节点经历了长时间的垃圾收集后暂停的。按照设计,hinted handoff 本质上允许Cassandra在集群的执行能力下降时仍然可以继续执行相同数量的写。

 

如果hinted handoff 在cassandra.yaml 中被启用,在故障检测器将一个节点标记为down之后,错过的写操作将被协调员保存一段时间。在cassandra3.0及其之后,hint存储于每个节点的本地hint目录以改进重做的性能。对于标记为downed的节点,hint由一个target ID,一个hint ID(即一个时间UUID),一个标示cassandra版本的消息ID和数据块本身组成。Hint每10秒钟被刷新到磁盘一次,减少过时的hint。当gossip发现一个节点已经重新上线时,协调员重新执行每个剩余的hint以便将数据写入新返回的节点,然后将hint 文件删除。如果一个节点关闭超过max_hint_window_in_ms  (默认为3小时),协调员将停止写入新的hints.

 

协调员还会通过gossip每十分钟检查一次由于停电故障造成的hint写入超时,如果一个备份节点被重载或者变得不可用,而且故障检测器还没有将这个节点标记为不可用,那么将会预料到大多数或者全部对于节点的写操作在由write_request_timeout_in_ms, (10 seconds by default).引起的超时之后将会失败。协调员返回一个TimeOutException异常,写入将会失败但是hint将会被存储。

 

如果多个节点同时经理短暂中断,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • When to Use Leveled Compaction
  • Leveled compaction in Apache Cassandra
  • DateTieredCompactionStrategy: Notes from the Field
  • Date-Tiered Compaction in Cassandra
  • DateTieredCompactionStrategy: Compaction for Time Series Data.
  • What delays a tombstone purge when using LCS in Cassandra
  • 在commit log中记录log
  • 在内存中写入数据
  • 从内存中将数据导出
  • 在磁盘上的SSTable中存储数据
  • Ec2Snitch
    Use the Ec2Snitch with Amazon EC2 in a single region.
  • Ec2MultiRegionSnitch
    Use the Ec2MultiRegionSnitch for deployments on Amazon EC2 where the cluster spans multiple regions.
  • GoogleCloudSnitch
    Use the GoogleCloudSnitch for Cassandra deployments on Google Cloud Platform across one or more regions.
  • CloudstackSnitch
    Use the CloudstackSnitch for Apache Cloudstack environments.
  •  
  • 生产环境建议使用这个snitch.它使用在cassandra-rackdc.properties 文件中定义的节点的机架和数据中心信息并把这些信息通过gossip传播到其他节点。
  • GossipingPropertyFileSnitch-建议生产环境使用
  • Automatically updates all nodes using gossip when adding new nodes and is recommended for production.
  • PropertyFileSnitch  自己决定每个数据中心和机架内的节点IP
  • 这种snitch由机架和数据中心决定相邻的节点。它使用在
  • RackInferringSnitch 由数据中心和机架确定相邻的节点,假设分别对应于节点ip地址的第三和第二部分。这种snitch最好用于编写一个客户自定义snitch 类的示例。
  • SimpleSnitch
    The SimpleSnitch is used only for single-data center deployments.
  • Simplesnitch 用于单数据中心的部署。它不识别数据中心或机架信息并且仅能用户单数据中心部署或者公共云上的单个区域。
  • RackInferringSnitch
    Determines the location of nodes by rack and data center corresponding to the IP addresses.
  •  
  • Dynamic snitching
    Monitors the performance of reads from the various replicas and chooses the best replica based on this history.
  • ByteOrderedPartitioner
    Cassandra provides this partitioner for ordered partitioning. It is included for backwards compatibility.
  • RandomPartitioner
    The default partitioner prior to Cassandra 1.2.
  • Murmur3Partitioner
    The Murmur3Partitioner provides fast hashing and good performance.
  • Tokens被自动计算和分配给每个节点
  • 当添加和删除节点时再平衡集群自动完成。当一个节点加入集群时,假设该节点为集群中其他节点的部分数据负责(即其他节点中的部分数据再平衡后应该存储在该节点上),如果一个节点挂掉了,负载均匀的分布到集群中的其他节点上。
  • 重建一个死去的节点很快因为这涉及了集群中每个其他节点
  • 分配给每台机器的虚拟节点的比例可以被指定,所以较小和较大的计算机可以用于构建集群。
   
    
    
    
    

Cassandra assigns a hash value to each partition key:

Partition key

Murmur3 hash value

jim

-2245462676723223822

carol

7723358927203680754

johnny

-6723372854036780875

suzy

1168604627387940318

 

集群中的每个节点对基于hash值得一段数据负责。在集群中四个节点的hash值

 

cassandra基于partition key的值和每个节点所负责的值得范围来为每个节点分配数据。例如,在一个4个节点的集群 中,数据如下分布

Node

Start range

End range

Partition key

Hash value

A

-9223372036854775808

-4611686018427387904

johnny

-6723372854036780875

B

-4611686018427387903

-1

jim

-2245462676723223822

C

0

4611686018427387903

suzy

1168604627387940318

D

4611686018427387904

9223372036854775807

carol

7723358927203680754

 

 

Virtual nodes

 

虚拟节点,称为Vnodes,以更新的力度跨节点分布数据,如果使用计算token的话这很容易实现。Vnodes简化了cassadra中的许多任务。

 

  • Tokens被自动计算和分配给每个节点
  • 当添加和删除节点时再平衡集群自动完成。当一个节点加入集群时,假设该节点为集群中其他节点的部分数据负责(即其他节点中的部分数据再平衡后应该存储在该节点上),如果一个节点挂掉了,负载均匀的分布到集群中的其他节点上。
  • 重建一个死去的节点很快因为这涉及了集群中每个其他节点
  • 分配给每台机器的虚拟节点的比例可以被指定,所以较小和较大的计算机可以用于构建集群。

 

更多信息,请参考cassandora1.2 中关于虚拟节点的主题。

Virtual nodes in Cassandra 1.2

 

将一个存在的节点转换为虚拟节点,参考

Enabling virtual nodes on an existing production cluster.

 

How data is distributed across a cluster (using virtual nodes)

 

虚拟节点使用一致性hash来分配数据,不需要生成和分配新的token

 

Partitioners

 

一个partitioner决定数据分布式的存储在集群中的节点上(包括副本)。基本上,分区程序使用hash函数从partition key中派生出一个token,该token代表了一行数据。每行的数据之后按token的值跨集群分布。

Murmur3Partitioner 和RandomPartitioner 使用token来为每个节点分配相同比例的数据,表格中的数据均等的分布在整个环上,或是其他组上如秘钥空间。即使表使用不同的分区键如username或者timestamps也是按这种情况来做。而且在此基础上,对于集群的读写操作也被均匀的分布,负载均衡被简化因为hash range的每个部分获取到row的数量是均等的。更多细节参考

Consistent hashing.

 

这两个分区算法的区别是如何生成token的hash值,RandomPartitioner 使用一个加密hash,他生成hash的时间要比Murmur3Partitioner 长,Cassoandra不需要加密hash,所以使用Murmur3Partitioner 在3-5分钟内产生完hash值可以改进效率。

 

Cassandra支持如下的分区程序,可以在cassandra.yaml文件中设置他们。

Murmur3Partitioner (默认)基于MurmurHash  hash值跨集群均匀分布节点。

RandomPartitioner 基于MD5  hash值跨集群均匀分布节点。

ByteOrderedPartition 按照数据的秘钥字节字典顺序有序分布。

Murmur3Partitioner 作为默认的分区策略应用于cassandra1.2以后,在部分情况下作为生成新集群的首选。

然而,分区程序间并不兼容, 使用一种分区程序后很难再转换为另一种分区程序。

如果使用虚拟节点(vnodes),你不必计算tokens。如果不使用vnodes,你必须计算tokens的数量然后为cassandra.yaml文件中的initial_token参数赋值。请参见

Generating tokens  同时使用某种分区程序的对应方法。

 

Murmur3Partitioner是默认分区程序,Murmur3Partitioner比RandomPartitioner更快更有效率,Murmur3Partitioner 可以使用vnodes,然而,如果你不使用vnodes,你必须计算token的数量,正如

Generating tokens中描述的那样。

为新的集群使用Murmur3Partitioner。你无法将已经存在的集群的分区程序改变为另一种分区程序。Murmur3Partitioner使用

MurmurHash 函数。该 哈希函数创建一个64-bithash值作为分区键,可能的hash值得范围从

 -263 to +263-1

当使用Murmur3Partitioner时,

你可以通过使用token功能在一个CQL语句中查询所有行。

 

RandomPartitioner 是cassandra1.2之前的默认分区程序,它包含向后的兼容性。RandomPartitioner 可以和vnodes一起使用。然而,如果你不使用vnodes,你必须计算tokens,如Generating tokens中描述的那样。RandomPartitioner 使用MD5 hash值作为行的键值来跨多个节点均匀的分布数据。可能的hash值范围从0-2127-1

 

  • ByteOrderedPartitioner
    Cassandra provides this partitioner for ordered partitioning. It is included for backwards compatibility.

casssandra为顺序分区提供ByteOrderedPartition方式。他提供向后兼容性。分区程序使用秘钥字节的词法顺序来排序行。通过查看你的分区秘钥数据的实际值并且使用秘钥中前导字符的十六进制表示形式来计算tokens的值。例如,如果你想按照行的字母顺序分区,你可以使用16进制表示形式41带指定A token.

使用顺序分区允许基于主键 的顺序查找。这意味着你可以进行扫描就好像你通过传统的索引移动光标一样。例如,如果你的应用使用用户名作为分区主键,你可以检索用户名落在jakejoe之间的行,这种检索形式使用RandomPartitioner 关键字是不可能实现的,因为键是以MD5 hash值来存储的(非顺序的)。

虽然有能力在行上做范围查询听起来是顺序分区的一个可取特点,但是通过使用表索引可以实现相同的功能。

以下情况不建议使用顺序分区程序:

Difficult load balancing

对于集群的负载均衡来说需要更多的管理开销。一个顺序的分区程序需要管理员基于分区秘钥分配的预估值手动计算分区的范围。在实践中,一旦分布式的数据被加载,就需要积极的四处移动节点的token来容纳这些数据。

 

Sequential writes can cause hot spots 顺序写可能导致热点发生

如果你的应用试图一次写入或者更新一个顺序块的行的数据,然后这些写入不会垮集群分布式,他们都被提交至一个节点。这个问题通常在应用程序处理时间戳数据时产生

 

 

Uneven load balancing for multiple tables -多个表的不平衡的负载均衡

如果你的应用程序有多个表,这些表有不同的行键值和不同的分布式数据。顺序分区程序使一个表平衡可能导致热点和同一集群下其他表的不均匀分布。

 

 

 

Snitches

 

snitch决定节点属于哪些数据中心和机架。他们告知cassandra使用什么样的网络拓扑结构以便请求被有效的路由同时让cassandra通过将机器分组到数据中心和机架的方式来分发副本。具体来说,驻留在副本上的副本策略基于由新的snitch提供的信息。所有节点都必须返回到同一机架和数据中心。cassandro尽量不在同一机架上存储一个以上副本。如果你改变snitch,你需要执行额外的步骤因为snitche影响了副本的驻留位置。参考

 Switching snitches.

 

  • Dynamic snitching
    Monitors the performance of reads from the various replicas and chooses the best replica based on this history.

默认情况下,所有的snitches还使用了一个动态snitch层来监视读取延迟,并在可能的情况下,将请求从表现不佳的节点路由到别的节点。动态snitch默认情况下是可用的而且是被建议用在大多数部署上的。更多关于动态snitch如何工作,请参考

 Dynamic snitching in Cassandra: past, present, and future.

 

在cassandra.yaml文件中为每个节点配置动态snitch的阈值

  •  
  • SimpleSnitch
    The SimpleSnitch is used only for single-data center deployments.
  • Simplesnitch 用于单数据中心的部署。它不识别数据中心或机架信息并且仅能用户单数据中心部署或者公共云上的单个区域。
  • RackInferringSnitch
    Determines the location of nodes by rack and data center corresponding to the IP addresses.
  • RackInferringSnitch 由数据中心和机架确定相邻的节点,假设分别对应于节点ip地址的第三和第二部分。这种snitch最好用于编写一个客户自定义snitch 类的示例。

 

  • PropertyFileSnitch  自己决定每个数据中心和机架内的节点IP
  • 这种snitch由机架和数据中心决定相邻的节点。它使用在

 cassandra-topology.properties文件中的详细网络信息,使用这个snitch时,你可以自定义你的数据中心名字。确保数据中心的名字和秘钥空间中定义的数据中心名字对应。集群中的每个节点都应该在cassandra-topology.properties文件中定义,同时,这个文件应该和集群上的每个节点完全等价。

 

产品:

如果你两个非均匀的ip和两个物理数据中心,每个数据中心有2个机架,一个分析数据中心用于副本数据分析。那么cassandra-topology.properties 文件可能类似于

 

# Data Center One

175.56.12.105=DC1:RAC1
175.50.13.200=DC1:RAC1
175.54.35.197=DC1:RAC1

120.53.24.101=DC1:RAC2
120.55.16.200=DC1:RAC2
120.57.102.103=DC1:RAC2

# Data Center Two

110.56.12.120=DC2:RAC1
110.50.13.201=DC2:RAC1
110.54.35.184=DC2:RAC1

50.33.23.120=DC2:RAC2
50.45.14.220=DC2:RAC2
50.17.10.203=DC2:RAC2

# Analytics Replication Group

172.106.12.120=DC3:RAC1
172.106.12.121=DC3:RAC1
172.106.12.122=DC3:RAC1

# default for unknown nodes
default =DC3:RAC1

 

 

 

 

 

Determines the location of nodes by rack and data center.

  • GossipingPropertyFileSnitch-建议生产环境使用
  • Automatically updates all nodes using gossip when adding new nodes and is recommended for production.
  • 生产环境建议使用这个snitch.它使用在cassandra-rackdc.properties 文件中定义的节点的机架和数据中心信息并把这些信息通过gossip传播到其他节点。

GossipingPropertyFileSnitch 的配置包含在cassandra-rackdc.properties文件中。

为了配置一个节点使用GossipingPropertyFileSnitch ,按照如下编辑cassandra-rackdc.properties

定义数据中心和机架包含这个节点,默认的设置为:

dc=DC1
rack=RAC1

 

数据中心和机架名字是大小写敏感的

 

为了节省带宽,添加prefer_local=true 选项,这个选项告诉cassandra当通信非跨越多个数据中心时使用本地IP地址

 

从propertyfilesnitch迁移到gossipingpropertyfilesnitch

为了允许从propertyfilesnitch迁移,当cassandra-rackdc.properties文件文件存在时,gossipingpropertyfilesnitch会使用它。迁移完成之后删掉这个文件。更过关于迁移的信息,参见

Switching snitches.

 

cassandra-topology.properties文件存在时,GossipingPropertyFileSnitch 总是导入它。在所有集群的每个节点上移除这个文件,或者在任何从PropertyFileSnitch.迁移过来的集群上移除这个文件。

 

 

 

 

 

 

  •  
  • Ec2Snitch
    Use the Ec2Snitch with Amazon EC2 in a single region.
  • Ec2MultiRegionSnitch
    Use the Ec2MultiRegionSnitch for deployments on Amazon EC2 where the cluster spans multiple regions.
  • GoogleCloudSnitch
    Use the GoogleCloudSnitch for Cassandra deployments on Google Cloud Platform across one or more regions.
  • CloudstackSnitch
    Use the CloudstackSnitch for Apache Cloudstack environments.

 

 

Related information

Install locations

 

 

 

Storage engine

Cassandra 使用一个类似于Log-Structured Merge Tree的存储结构。不像典型的关系型数据库使用B树。Cassandra避免在写之前读。Read-before-write,特别是在大型分布式系统中,可能导致读性能的大量延迟和其他问题。例如,两个客户端同时读,其中一个使用UpdateA更新了行,另一个使用updateB操作更新航。同时移除UpdateA. 这种竞争将导致模糊的查询结果,哪个update是正确的呢?

要避免在cassandra中使用在写之前读,存储引擎组在内存中插入和更新数据,一段时间之后,顺序的以追加模式将数据写入磁盘。一旦写入磁盘,数据将变成不可变的并且永远不会被覆盖。读取数据涉及结合这个永恒不变的顺序写数据来发现正确的查询结果。你可以使用轻量级的事物(LWT)在写数据之前检查数据的状态。然而,此功能建议有限的使用它。

Log-structured引擎避免覆盖和使用顺序I/O来更新数据对于写入SSD和HDD来说是个必要条件。在HDD上,随机写操作比顺序写操作涉及更多的寻址操作。寻址操作性能损失非常大。因此Cassandra是按顺序永久写入文件的,因此避免了写放大和磁盘失败,数据库容器很便宜,使用SSD性能会更好。对于许多其他类型数据库,在SSD上的写放大问题。

 

Cassandra 如何读写数据

为了管理和访问cassandra中的数据,理解cassandra如何存储数据非常重要。Hinted handoff特性加上cassandra对于数据库ACID(atomic,consistent,isolated,durable)的一致性与非一致性是理解cassandra读写的关键概念。在cassandra中,一致性是指如何在所有副本上更新和同步一行数据。

客户端工具和应用程序接口(APIs)可用于开发数据存储和检索的应用程序。

 

数据如何写入

cassandra分为几个阶段处理数据的写入,从log的写入开始,以数据写入到磁盘结束。

  • 在commit log中记录log
  • 在内存中写入数据
  • 从内存中将数据导出
  • 在磁盘上的SSTable中存储数据

 

log写入和内存存储

当一次写入发生时,cassandra在内存结构中存储的数据称为内存表,同时提供

configurable durability,.同时写操作被追加到磁盘上的commit log中。Commit log接收每个写入cassandra节点的写请求,这些长久的写操作永久存在即使一个节点断电。内存表是cassandra通过键查找的数据分区回写缓存。内存表按顺序存储写操作直到达到设置上限,然后flush他们。

 

从内存表中flush数据

为了flush数据,cassandra以内存中的顺序将数据写入磁盘。一个分区索引同时在磁盘上被创建出来用来将token映射到磁盘上的一个位置。当内存结构表中的内容超过configurable threshold或者commitlog的空间超过了commitlog_total_space_in_mb , 内存表被放置在一个队列中向磁盘flush. 这个队列在cassandra.yaml文件中通过

memtable_heap_space_in_mb or memtable_offheap_space_in_mb属性来设置。如果将要flush的数据超过了memtable_cleanup_threshold的限制,cassandra将阻塞写直到下一次flush成功。你可以使用

 nodetool flushor nodetool drain手动flush一张表。为了减少commit log回溯的时间,建议的最佳做法是在重启节点之前刷新内存表。如果一个节点停止工作,回溯commit log将会使内存表回到节点停止前的状态。

 

Commit log中的数据在对应的内存表中的数据被刷新到磁盘上的SStable中后被清除。

 

在磁盘上的SStable中存储数据。

内存表和SSTable中的每个表都是可维护的。Commit log在表之间共享。SSTable是不可变的,在内存表被刷新之后不会再次被写入。因此,一个分区通常跨多个SSTable文件存储,许多其他的sstable结构文件用于协助读操作。

 

 

 

对于每个sstable,cassandra创建如下结构:

Data(Data.db)

sstable数据

Primary Index(Index.db)

每行键的索引,指向数据文件中他们的位置。

Bloom filter (Filter.db) 

一个存储于内存中的结构用于在访问磁盘上的sstable之前检查行数据是否存在于内存表

Compression Information (CompressionInfo.db) 

一个用于保存未压缩文件长度的信息,数据偏移量块和其他压缩信息

Statistics (Statistics.db) 

关于sstable内容的统计元数据。

Digest (Digest.crc32, Digest.adler32, Digest.sha1) 

一个保存adler32数据文件校验和的文件

CRC (CRC.db) 

一个保存CRC32未压缩文件数据块的文件

SSTable Index Summary (SUMMARY.db)

一个分区索引存储于内存中的例子

SSTable Table of Contents (TOC.txt)

一个用于存储所有sstable TOC组件列表的文件

Secondary Index (SI_.*.db)

内置的二级索引。每个sstable文件可能存在多个sls

 

sstable是存储在磁盘上的文件。Cassandra2.2之后为了缩短文件路径改变了文件的命名约定。数据文件存在于通过安装文件安装的数据目录中,对于每一个秘钥空间,每张表存储在每个数据文件夹内。例如:

/data/data/ks1/cf1-5be396077b811e3a3ab9dc4b9ac088d/la-1-big-Data.db 

代表了一个数据文件。Ks1代表了秘钥空间名字以区别用于以流方式或者批量方式加载数据的秘钥空间。本例中的

5be396077b811e3a3ab9dc4b9ac088d 十六进制字符串,被追加到表的名称中代表表的唯一Id

 

cassandra为每个表创建一个子目录,可以允许你将表连接到一个可选的物理驱动器或者数据卷。这提供了一种能力-将非常活跃的表移动到更快的介质上,例如SSD提供的更好执行效率,同时为了更好的存储层I/O均衡也可以跨所有的附加存储设备来划分表。

 

数据如何维护

Cassandra写过程将数据存储在称为SSTable的结构中。SSTable是不可变得。Cassandra在新的SSTables中为插入和更新的数据写入新的时间戳来代替插入和更新数据时覆盖已经存在的行。同时,cassandra也不会就地删除数据,相反,cassandra使用一个结束标记

tombstone

来标记数据已经被删除。

随着时间的推移,cassandra可能对一行写出很多版本,每一个在不同的sstable中。每个版本可能有一系列唯一的列存储,使用不同的时间戳。这意味 着cassandra必须访问越来越多的sstable以检索整行数据。

要保证数据库健康,cassandra定期合并sstable,丢弃旧数据,这个过程称为压缩。

压缩

压缩通过partition key来合并每个sstable表中的数据,选择最新时间戳的数据版本。合并过程是很有效率的,因为行是按照每个sstable表分区键进行排序的,合并的过程不使用随机I/O. 在移除删除的列和行之后,压缩过程整合sstables 成为一个新的单独的sstable.只要使用旧的文件的被挂起的读操作完成,旧的sstable就将被删除

 

压缩导致了旧的sstable和新的sstable在磁盘空间和I/O上的临时共存,当压缩完成时,压缩释放由旧的SSTable占据的磁盘空间。用压缩后的sstable逐步取代旧的sstable提高了读取的性能。Cassandra能够直接从新的sstable表中读取数据而不需要等待整个压缩过程完成

 

当cassandra处理读写时,他同时在页面缓存中将旧的sstable文件替换为新的sstable文件。当将读操作从旧的sstable一点点导出时,缓存新的sstable的工作在同时进行,这不会引起巨大的缓存遗失。即使在高负载下,cassandra也能提供一个可预知的高性能

 

压缩策略

cassandra支持不同的压缩策略。每个都有自己的特点。为了正确的选取合适你的应用工作负载的策略,了解每个策略如何工作十分重要。虽然以下的每节都以广义的介绍开始,但是如何选取一个压缩策略仍然有很多复杂的因素。有关更多的深入了解每种策略有何优势以及讨论每种策略特定的用例,请参考

Which compaction strategy is best?.

 

 

SizeTieredCompactionStrategy (STCS) 

 

建议写密集型工作负载

当积累一系列(默认4个)相似大小的SSTable,The SizeTieredCompactionStrategy (STCS)启动一次压缩。 这种压缩策略将这些sstable压缩成一个更大的sstable. 然后这些更大的sstable被压缩成更大的sstable.在任何给定的时间,一些不同大小的sstable是共同存在的。

 

 

 

这种策略对于压缩一个写密集型的工作负载来说能够很好的工作,但他使读变得很慢,因为按照大小进行合并的过程

不会按照行来进行分组。由于这个原因,更可能的是对于特定行的数据可能遍布许多sstables。此外,对于删除数据的回收并没有如预期般发生,因为sstable的大小是压缩的触发器,同时sstable不可能增长的足够以合并和回收旧数据。因为最大的sstable的大小不断增长,用于压缩存储新旧sstable的磁盘空间的数量可能超过一个典型节点上的磁盘空间的数量。

 

Pros:写密集型压缩工作负载运行良好

Cons:可以保留陈旧的数据很长时间, 内存需要量随时间而增长。

 

LeveledCompactionStrategy (LCS)

 

建议读密集型工作负载使用

The LeveledCompactionStrategy (LCS)用于减轻STCS的有关读操作发生的一些问题。这种策略工作在一系列的等级之上。首先,内存表在L0上首先被刷新到SSTables.然后,压缩在L1将sstables合并成更大的sstable.

 

 

大于L1等级的sstables将合并成一个大于等于sstable_size_in_mb (默认160M)

大小的sstable. 如果一个L1 等级的sstable存储了大于L2等级的分区数据,LCS将把SStable移动到L2的下一等级上。

在许多插入之后的等级压缩

 

在L0以上的每个等级,LCS创建相同大小的SSTable.每个等级是上一个等级的10倍大小。所以level1是L0的10倍,L2是L0的100倍。如果压缩的结果是L1的等级 上有多于10个sstable,那么多于的sstable将会被移动到L2。

 

LCS压缩过程保证以L1开始的每层的sstable没有重叠数据。对于许多读取操作,这种保证可以使cassandra能够仅从1-2个表中检索需要的数据,事实上,所有读取请求的90%由一张sstable就可以满足。因为LCS不会压缩L0的表,然而,涉及许多L0层sstable的资源密集型的读取仍然可能发生。

 

在L0层之上,LCS需要较少的磁盘空间用于压缩-通常状况下,10倍的SSTable的固定大小。过时的数据更加频繁的被清除,所以,在磁盘上清除sstable中使用较少的那部分数据。然而,LCS压过过程发生的太频繁给节点带来了过多的I/O负担,

对于写密集的工作流程,使用这种策略的回报与I/O操作带来的性能损失相比通常是不值得的。在许多情况下,LCS-configured测试表揭示了写和压缩的I/O饱和程度

 

Cassandra2.2以后使用LCS在集群中引导一个新的节点性能改进绕过了压缩操作,原始的数据直接被移动到了正确的LEVEL因为没有已经存在的数据。所以每层没有分区层叠出现。更多的信息,请参考Apache

Apache Cassandra 2.2 - Bootstrapping Performance Improvements for Leveled Compaction.

 

好处:磁盘要求更容易预测。读操作延迟更容易遇见。陈旧的数据更加频繁的被清楚。

缺点:更高的I/O利用率影响操作的延迟

 

DateTieredCompactionStrategy (DTCS)

 

建议时间序列和到期时间工作流使用

 

DTCS与STCS类似。但是不是基于SSTable的大小而压缩,而是基于SSTable的年龄而压缩。配置time windows确保新数据和旧数据不会在在合并表中被混合。事实上,使用(Time-To-Live)TTL时间戳,DTCS可以将全部的过期数据都清理出去。如果时间序列的提取以一个平稳的速率,这种策略经常会生成类似大小的sstable表。

 

当系统在可配置的时间间隔内达到了确定的最低限度数目的sstable的时候,DTCS将会合并sstable表。就像在STC中的一样,需要的sstable数量在给定的时间内减少时,DTCS将sstable表合并成一个更大的表。然而,DTCS放弃压缩到达了一个按照配置确定的阶段的SSTable。这减少了时间数据的重写次数。

对于最后一个小时的有价值的数据的查询(或者与配置的时间间隔良好协调的其他时间间隔)能在DTCS-compacted sstables上被非常高效率的执行。

 

有一种情况使用这种策略会导致困难-无序写入,例如:一次操作使用了一个过去的时间戳写入了一条时间戳记录。读取修改可能引入一条无需的时间戳,所以,确保当使用DTCS时关闭读取修改功能。

 

好处:专门用于时间序列数据

缺点:无序的时间注入可能引起错误。读取修复必须关闭DTCS,同时时间戳操作不允许使用BATCH, DELETE, INSERT and UPDATE CQL 命令。

 

那种压缩策略最好:

要实现最好的压缩策略:

1 重新审视你的应用的需求

2 配置表以使用最合适的策略

3 针对你的数据测试压缩策略

 

以下的问题基于cassandra开发者和使用者的经验来描述上述策略

 

你的表格需要处理时间序列数据吗?

如果需要,DTCS是你最好的选择。要避免问题发生,重审上述描述。如果你的表格不关注时间序列数据,那么选择可能会变得很复杂,以下关于其他因素的介绍问题可以指导你的选择。

 

你的表是处理读多还是写多?

如果你的表处理读是写的2倍以上那么LCS是个很好的选择,特别是随机读。如果读写比例接近,那么LCS的性能损失带来的弊端将比利益还高。避免LCS被高容量的写入搞得很快不堪重负。

表中的数据经常变吗?

LCS的一个优势是它在一个小的SSTable集合中保存相关数据。如果你的数据不可变或者不受经常的插入删除的影响,STCS可以实现相同类型的分组而不受LCS性能的影响。

 

你需要读写活动的预测值吗?

LCS在可预见的的大小和数量内保持SSTable,例如,如果你的表的读/写  率很小,同时在读上期望符合Service Level agrrementst(SLA),LCS为了在可预见的等级上保证读的效率和延迟而带来的写的性能损失是值得的。同时你可以通过横向扩展增加更多节点来克服写上的损失。

你的表将由批处理程序填充吗?

对于批处理读写,STCS的性能要高于LCS。批处理基本不会产生碎片,而LCS却没有这种好处。同时批处理操作也是压垮LCS-configured 表。

你的系统有磁盘空间限制吗?

LCS处理磁盘空间比STCS更有效率;除了用于数据处理,还需要10%的额外空间。STCS和DTCS大多数情况需要50%以上的剩余空间。

你的系统到达了I/O限制了吗?

LCS比DTCS或者STCS需要更多的I/O操作,切换到LCS可能带来更多的I/P负载以抵消其优势。

 

测试压缩策略

一些建议用来决定哪个压缩策略适合你的系统:

1 使用其中的一个压缩策略创建一个三个节点的集群,使用cassandra-stress进行压力测试,查看结果

2 在已经存在的集群上创建一个节点同时使用cassandra 写测量模式来举例live data。请参考

What’s new in Cassandra 1.1: live traffic sampling.

 

 

配置和运行压缩

在create table 或者 alter table命令中使用参数来为一张表设置压缩策略。具体请参考

Table properties.

 

 

你可以使用nodetool compact 命令手动开始压缩。

 

更多关于压缩的信息

以下一些开发者博客提供了更多关于测试压缩策略的信息

 

 

数据如何更新?

插入一个完全一个样的主键被视为upsert. 如果一条记录在数据库中不存在,一个upsert将在数据库中写入一条记录. 如果那条数据的主键已经存在,一条新的包含一个最新时间戳的记录将被写入。如果这条数据在一次读请求中被检索,只有最新的记录会被检索到,老一点的时间戳数据将被标记为删除。最后的效果类似于使用新数据交换覆盖旧数据,即使cassandra不会覆盖数据。最终,更新使用顺序I/O存储到磁盘存于新的sstable. 在更新期间,cassandra使用

write path

记录时间戳并且在磁盘上写入列。如果用户多次写入相同的数据,只有内存表中存储的最新的数据才会被刷新到磁盘上。

 

数据如何删除?

Cassandra删除数据的过程被设计用来改进性能,同时与cassandra内置的属性一起工作来分布式数据和提高容错。

Cassandra将删除视为插入或者upsert,以delete 命令加入分区的数据其实是一个叫做tombstone

的删除标记。墓碑经过Cassandra的写路径,被写入一个或多个节点的sstable中。墓碑关键的区别特征是:它有一个内置的过期时间,在过期时间结束时,墓碑将作为正常Cassandra压缩过程的一部分被删除。

 

在一个分布式系统中删除

在一个多节点集群中,cassandra在两个或者更多节点上存储同一数据的多个副本,这防止了数据的丢失,但这复杂化了删除过程。如果一个节点收到了一条删除它所存储的本地数据的命令,该节点墓碑指定的记录并试图将墓碑传递到包含该记录的副本的其他节点上。但如果一个副本节点在那个时刻没有响应,它没有马上收到墓碑,所以它仍然包含着预删除记录的版本。如果在该节点恢复前集群中的其他节点已经删除了墓碑,Cassandra把该刚恢复过来的节点上的这条记录视为新数据,然后把他传播到集群中其他的节点上。这种标记为已经删除但是仍然存在的记录被称为

zombie

为了防止僵尸记录的再现,cassandra给予每一个墓碑一个过渡期。这个过渡期的目的是给予响应节点时间以恢复和正常处理墓碑。如果一个客户端在过渡期内向墓碑记录发送了一条更新命令,cassandra将会覆盖该墓碑。如果一个客户端在过渡期向墓碑记录发送了一条读命令,cassandra将忽略这个墓碑同时从其他副本查找可能存在的该条记录。

 

当一个停止响应的节点恢复时,cassandra使用 hinted handoff 来重做当节点挂掉时未作的数据库的插入和删除。在一个墓碑的过渡期,cassandra不会重做插入和删除。如果一个节点在过渡期结束后仍然没有恢复,cassandra可能会错过删除这个节点。

 

Hinted Handoff: repair during write path

有时,当数据被写入时一个节点会变得不可用。不可用的原因可能是硬件原因,网路问题,或者过载节点经历了长时间的垃圾收集后暂停的。按照设计,hinted handoff 本质上允许Cassandra在集群的执行能力下降时仍然可以继续执行相同数量的写。

 

如果hinted handoff 在cassandra.yaml 中被启用,在故障检测器将一个节点标记为down之后,错过的写操作将被协调员保存一段时间。在cassandra3.0及其之后,hint存储于每个节点的本地hint目录以改进重做的性能。对于标记为downed的节点,hint由一个target ID,一个hint ID(即一个时间UUID),一个标示cassandra版本的消息ID和数据块本身组成。Hint每10秒钟被刷新到磁盘一次,减少过时的hint。当gossip发现一个节点已经重新上线时,协调员重新执行每个剩余的hint以便将数据写入新返回的节点,然后将hint 文件删除。如果一个节点关闭超过max_hint_window_in_ms  (默认为3小时),协调员将停止写入新的hints.

 

协调员还会通过gossip每十分钟检查一次由于停电故障造成的hint写入超时,如果一个备份节点被重载或者变得不可用,而且故障检测器还没有将这个节点标记为不可用,那么将会预料到大多数或者全部对于节点的写操作在由write_request_timeout_in_ms, (10 seconds by default).引起的超时之后将会失败。协调员返回一个TimeOutException异常,写入将会失败但是hint将会被存储。