Cassandra简介

  在前面的一篇文章《图形数据库Neo4J简介》中,咱们介绍了一种很是流行的图形数据库Neo4J的使用方法。而在本文中,咱们将对另一种类型的NoSQL数据库——Cassandra进行简单地介绍。html

  接触Cassandra的缘由与接触Neo4J的缘由相同:咱们的产品须要可以记录一系列关系型数据库所没法快速处理的大量数据。Cassandra,以及后面将要介绍的MongoDB,都是咱们在技术选型过程当中的一个备选方案。虽说最后咱们并无选择Cassandra,可是在整个技术选型过程当中所接触到的一系列内部机制,思考方式等都是很是有趣的。并且在整个选型过程当中也借鉴了CAM(Cloud Availability Manager)组在实际使用过程当中所获得的一些经验。所以我在这里把本身的笔记总结成一篇文章,分享出来。java

 

技术选型git

  技术选型经常是一个很是严谨的过程。因为一个项目一般是由数十位甚至上百位开发人员协同开发的,所以一个精准的技术选型经常可以大幅提升整个项目的开发效率。在尝试为某一类需求设计解决方案时,咱们经常会有不少种能够选择的技术。为了可以精准地选择一个适合于这些需求的技术,咱们就须要考虑一系列有关学习曲线,开发,维护等众多方面的因素。这些因素主要包括:github

  • 该技术所提供的功能是否可以完整地解决问题。
  • 该技术的扩展性如何。是否容许用户添加自定义组成来知足特殊的需求。
  • 该技术是否有丰富完整的文档,而且可以以避免费甚至付费的形式获得专业的支持。
  • 该技术是否有不少人使用,尤为是一些大型企业在使用,并存在着成功的案例。

  在该过程当中,咱们会逐渐筛选市面上所能找到的各类技术,并最终肯定适合咱们需求的那一种。算法

  针对咱们刚刚所提到的需求——记录并处理系统自动生成的大量数据,咱们在技术选型的初始阶段会有不少种选择:Key-Value数据库,如Redis,Document-based数据库,如MongoDB,Column-based数据库,如Cassandra等。并且在实现特定功能时,咱们经常能够经过以上所列的任何一种数据库来搭建一个解决方案。能够说,如何在这三种数据库之间选择经常是NoSQL数据库初学者所最为头疼的问题。致使这种现象的一个缘由就是,Key-Value,Document-based以及Column-based其实是对NoSQL数据库的一种较为泛泛的分类。不一样的数据库提供商所提供的NoSQL数据库经常具备略为不一样的实现方式,并提供了不一样的功能集合,进而会致使这些数据库类型之间的边界并非那么清晰。数据库

  恰如其名所示,Key-Value数据库会以键值对的方式来对数据进行存储。其内部经常经过哈希表这种结构来记录数据。在使用时,用户只须要经过Key来读取或写入相应的数据便可。所以其在对单条数据进行CRUD操做时速度很是快。而其缺陷也同样明显:咱们只能经过键来访问数据。除此以外,数据库并不知道有关数据的其它信息。所以若是咱们须要根据特定模式对数据进行筛选,那么Key-Value数据库的运行效率将很是低下,这是由于此时Key-Value数据库经常须要扫描全部存在于Key-Value数据库中的数据。数组

  所以在一个服务中,Key-Value数据库经常用来做为服务端缓存使用,以记录一系列经由较为耗时的复杂计算所获得的计算结果。最著名的就是Redis。固然,为Memcached添加了持久化功能的MemcacheDB也是一种Key-Value数据库。缓存

  Document-based数据库和Key-Value数据库之间的不一样主要在于,其所存储的数据将再也不是一些字符串,而是具备特定格式的文档,如XML或JSON等。这些文档能够记录一系列键值对,数组,甚至是内嵌的文档。如:服务器

 1 {
 2     Name: "Jefferson",
 3     Children: [{
 4         Name:"Hillary",
 5         Age: 14
 6     }, {
 7         Name:"Todd",
 8         Age: 12
 9     }],
10     Age: 45,
11     Address: {
12         number: 1234,
13         street: "Fake road",
14         City: "Fake City",
15         state: "NY",
16         Country: "USA"
17     }
18 }

  有些读者可能会有疑问,咱们一样也能够经过Key-Value数据库来存储JSON或XML格式的数据,不是么?答案就是Document-based数据库经常会支持索引。咱们刚刚提到过,Key-Value数据库在执行数据的查找及筛选时效率很是差。而在索引的帮助下,Document-based数据库则可以很好地支持这些操做了。有些Document-based数据库甚至容许执行像关系型数据库那样的JOIN操做。并且相较于关系型数据库,Document-based数据库也将Key-Value数据库的灵活性得以保留。网络

  而Column-based数据库则与前面两种数据库很是不一样。咱们知道,一个关系型数据库中所记录的数据经常是按照行来组织的。每一行中包含了表示不一样意义的多个列,并被顺序地记录在持久化文件中。咱们知道,关系型数据库中的一个常见操做就是对具备特定特征的数据进行筛选及操做,并且该操做经常是经过WHERE子句来完成的:

1 SELECT * FROM customers WHERE country='Mexico';

  在一个传统的关系型数据库中,该语句所操做的表可能以下所示:

  而在该表所对应的数据库文件中,每一行中的各个数值将被顺序记录,从而造成了以下图所示的数据文件:

  所以在执行上面的SQL语句时,关系型数据库并不能连续操做文件中所记录的数据:

  这大大下降了关系型数据库的性能:为了运行该SQL语句,关系型数据库须要读取每一行中的id域和name域。这将致使关系型数据库所要读取的数据量显著增长,也须要在访问所需数据时执行一系列偏移量计算。何况上面所举的例子仅仅是一个最简单的表。若是表中包含了几十列,那么数据读取量将增大几十倍,偏移量计算也会变得更为复杂。

  那么咱们应该如何解决这个问题呢?答案就是将一列中的数据连续地存在一块儿:

  而这就是Column-based数据库的核心思想:按照列来在数据文件中记录数据,以得到更好的请求及遍历效率。这里有两点须要注意:首先,Column-based数据库并不表示会将全部的数据按列进行组织,也没有那个必要。对某些须要执行请求的数据进行按列存储便可。另一点则是,Cassandra对Query的支持其实是与其所使用的数据模型关联在一块儿的。也就是说,对Query的支持颇有限。咱们立刻就会在下面的章节中对该限制进行介绍。

  至此为止,您应该可以根据各类数据库所具备的特性来为您的需求选择一个合适的NoSQL数据库了。

 

Cassandra初体验

  OK,在简单地介绍了Key-Value,Document-based以及Column-based三种不一样类型的NoSQL数据库以后,咱们就要开始尝试着使用Cassandra了。鉴于我我的在使用一系列NoSQL数据库时经常遇到它们的版本更新缺少API后向兼容性这一状况,我在这里直接使用了Datastax Java Driver的样例。这样读者也能从该页面中查阅针对最新版本客户端的示例代码。

  一段最简单的读取一条记录的Java代码以下所示:

Cluster cluster = null;
try {
    // 建立链接到Cassandra的客户端
    cluster = Cluster.builder()
            .addContactPoint("127.0.0.1")
            .build();
    // 建立用户会话
    Session session = cluster.connect();

    // 执行CQL语句
    ResultSet rs = session.execute("select release_version from system.local");
    // 从返回结果中取出第一条结果
    Row row = rs.one();
    System.out.println(row.getString("release_version"));
} finally {
    // 调用cluster变量的close()函数并关闭全部与之关联的连接
    if (cluster != null) {
        cluster.close();
    }
}

  看起来很简单,是么?其实在客户端的帮助下,操做Cassandra实际上并非很是困难的一件事。反过来,如何为Cassandra所记录的数据设计模型才是最须要读者仔细考虑的。与你们所最为熟悉的关系型数据库建模方式不一样,Cassandra中的数据模型设计须要是Join-less的。简单地说,那就是因为这些数据分布在Cassandra的不一样结点上,所以这些数据的Join操做并不能被高效地执行。

  那么咱们应该如何为这些数据定义模型呢?首先咱们要了解Cassandra所支持的基本数据模型。这些基本数据模型有:Column,Super Column,Column Family以及Keyspace。下面咱们就对它们进行简单地介绍。

  Column是Cassandra所支持的最基础的数据模型。该模型中能够包含一系列键值对:

1 {
2     "name": "Auther Name",
3     "value": "Sam",
4     "timestamp": 123456789
5 }

  Super Column则包含了一系列Column。在一个Super Column中的属性能够是一个Column的集合:

1 {
2     "name": "Cassandra Introduction",
3     "value": {
4         "auther": { "name": "Auther Name", "value": "Sam", "timestamp": 123456789},
5         "publisher": { "name": "Publisher", "value": "China Press", "timestamp": 234567890}
6     }
7 }

  这里须要注意的是,Cassandra文档已经再也不建议过多的使用Super Column,而缘由却没有直接说明。听说这和Super Column经常须要在数据访问时执行反序列化相关。一个最为常见的证据就是,网络上经常会有一些开发人员在Super Column中添加了过多的数据,并进而致使和这些Super Column相关的请求运行缓慢。固然这只是猜想。只不过既然官方文档都已经开始对Super Column持谨慎意见,那么咱们也须要在平常使用过程当中尽可能避免使用Super Column。

  而一个Column Family则是一系列Column的集合。在该集合中,每一个Column都会有一个与之相关联的键:

 1 Authers = {
 2     “1332”: {
 3         "name": "Auther Name",
 4         "value": "Sam",
 5         "timestamp": 123456789
 6     },
 7     “1452”: {
 8         “name”: “Auther Name”,
 9         “value”: “Lucy”,
10         “timestamp”: 012343437
11     }
12 }

  上面的Column Family示例中所包含的是一系列Column。除此以外,Column Family还能够包含一系列Super Column(请谨慎使用)。

  最后,Keyspace则是一系列Column Family的集合。

  发现了么?上面没有任何一种方法可以经过一个Column(Super Column)引用另外一个Column(Super Column),而只能经过Super Column包含其它Column的方式来完成这种信息的包含。这与咱们在关系数据库设计过程当中经过外键与其它记录相关联的使用方法很是不一样。还记得以前咱们经过外键来建立数据关联这一方法的名称么?对的,Normalization。该方法能够经过外键所指示的关联关系有效地消除在关系型数据库中的冗余数据。而在Cassandra中,咱们要使用的方法就是Denormalization,也便是容许能够接受的必定程度的数据冗余。也就是说,这些关联的数据将直接记录在当前数据类型之中。

  在使用Cassandra时,哪些不应抽象为Cassandra数据模型,而哪些数据应该有一个独立的抽象呢?这一切决定于咱们的应用所经常执行的读取及写入请求。想一想咱们为何使用Cassandra,或者说Cassandra相较于关系型数据库的优点:快速地执行在海量数据上的读取或写入请求。若是咱们仅仅根据所操做的事物抽象数据模型,而不去理会Cassandra在这些模型之上的执行效率,甚至致使这些数据模型没法支持相应的业务逻辑,那么咱们对Cassandra的使用也就没有实际的意义了。所以一个较为正确的作法就是:首先根据应用的需求来定义一个抽象概念,并开始针对该抽象概念以及应用的业务逻辑设计在该抽象概念上运行的请求。接下来,软件开发人员就能够根据这些请求来决定如何为这些抽象概念设计模型了。

  在抽象设计模型时,咱们经常须要面对另一个问题,那就是如何指定各Column Family所使用的各类键。在Cassandra相关的各种文档中,咱们经常会遇到如下一系列关键的名词:Partition Key,Clustering Key,Primary Key以及Composite Key。那么它们指的都是什么呢?

  Primary Key其实是一个很是通用的概念。在Cassandra中,其表示用来从Cassandra中取得数据的一个或多个列:

1 create table sample (
2     key text PRIMARY KEY,
3     data text
4 );

  在上面的示例中,咱们指定了key域做为sample的PRIMARY KEY。而在须要的状况下,一个Primary Key也能够由多个列共同组成:

1 create table sample {
2     key_one text,
3     key_two text,
4     data text,
5     PRIMARY KEY(key_one, key_two)
6 };

  在上面的示例中,咱们所建立的Primary Key就是一个由两个列key_one和key_two组成的Composite Key。其中该Composite Key的第一个组成被称为是Partition Key,然后面的各组成则被称为是Clustering Key。Partition Key用来决定Cassandra会使用集群中的哪一个结点来记录该数据,每一个Partition Key对应着一个特定的Partition。而Clustering Key则用来在Partition内部排序。若是一个Primary Key只包含一个域,那么其将只拥有Partition Key而没有Clustering Key。

  Partition Key和Clustering Key一样也能够由多个列组成:

1 create table sample {
2     key_primary_one text,
3     key_primary_two text,
4     key_cluster_one text,
5     key_cluster_two text,
6     data text,
7     PRIMARY KEY((key_primary_one, key_primary_two), key_cluster_one, key_cluster_two)
8 };

  而在一个CQL语句中,WHERE等子句所标示的条件只能使用在Primary Key中所使用的列。您须要根据您的数据分布决定到底哪些应该是Partition Key,哪些应该做为Clustering Key,以对其中的数据进行排序。

  一个好的Partition Key设计经常会大幅提升程序的运行性能。首先,因为Partition Key用来控制哪一个结点记录数据,所以Partition Key能够决定是否数据可以较为均匀地分布在Cassandra的各个结点上,以充分利用这些结点。同时在Partition Key的帮助下,您的读请求应尽可能使用较少数量的结点。这是由于在执行读请求时,Cassandra须要协调处理从各个结点中所获得的数据集。所以在响应一个读操做时,较少的结点可以提供较高的性能。所以在模型设计中,如何根据所须要运行的各个请求指定模型的Partition Key是整个设计过程当中的一个关键。一个取值均匀分布的,却经常在请求中做为输入条件的域,经常是一个能够考虑的Partition Key。

  除此以外,咱们也应该好好地考虑如何设置模型的Clustering Key。因为Clustering Key能够用来在Partition内部排序,所以其对于包含范围筛选的各类请求的支持较好。

 

Cassandra内部机制

  在本节中,咱们将对Cassandra的一系列内部机制进行简单地介绍。这些内部机制不少都是业界所经常使用的解决方案。所以在了解了Cassandra是如何使用它们的以后,您就能够很是容易地理解其它类库对这些机制的使用,甚至在您本身的项目中借鉴及使用它们。

  这些常见的内部机制有:Log-Structured Merge-Tree,Consistent Hash,Virtual Node等。

 

Log-Structured Merge-Tree

  最有意思的一个数据结构莫过于Log-Structured Merge-Tree。Cassandra内部使用相似的结构来提升服务实例的运行效率。那它是如何工做的呢?

  简单地说,一个Log-Structured Merge-Tree主要由两个树形结构的数据组成:存在于内存中的C0,以及主要存在于磁盘中的C1

  在添加一个新的结点时,Log-Structured Merge-Tree会首先在日志文件中添加一条有关该结点插入的记录,而后再将该结点插入到树C0中。添加到日志文件中的记录主要是基于数据恢复的考虑。毕竟C0树处于内存中,很是容易受到系统宕机等因素的影响。而在读取数据时,Log-Structured Merge-Tree会首先尝试从C0树中查找数据,而后再在C1树中查找。

  在C0树知足必定条件以后,如其所占用的内存过大,那么它所包含的数据将被迁移到C1中。在Log-Structured Merge-Tree这个数据结构中,该操做被称为是rolling merge。其会把C0树中的一系列记录归并到C1树中。归并的结果将会写入到新的连续的磁盘空间。

几乎是论文中的原图

  就单个树来看,C1和咱们所熟悉的B树或者B+树有点像,是不?

  不知道您注意到没有。上面的介绍突出了一个词:连续的。这是由于C1树中同一层次的各个结点在磁盘中是连续记录的。这样磁盘就能够经过连续读取来避免在磁盘上的过多寻道,从而大大地提升了运行效率。

 

MemtableSSTable

  好,刚刚咱们已经提到了Cassandra内部使用和Log-Structured Merge-Tree相似的数据结构。那么在本节中,咱们就将对Cassandra的一些主要数据结构及操做流程进行介绍。能够说,若是您大体理解了上一节对Log-Structured Merge-Tree的讲解,那么理解这些数据结构也将是很是容易的事情。

  在Cassandra中有三个很是重要的数据结构:记录在内存中的Memtable,以及保存在磁盘中的Commit Log和SSTable。Memtable在内存中记录着最近所作的修改,而SSTable则在磁盘上记录着Cassandra所承载的绝大部分数据。在SSTable内部记录着一系列根据键排列的一系列键值对。一般状况下,一个Cassandra表会对应着一个Memtable和多个SSTable。除此以外,为了提升对数据进行搜索和访问的速度,Cassandra还容许软件开发人员在特定的列上建立索引。

  鉴于数据可能存储于Memtable,也可能已经被持久化到SSTable中,所以Cassandra在读取数据时须要合并从Memtable和SSTable所取得的数据。同时为了提升运行速度,减小没必要要的对SSTable的访问,Cassandra提供了一种被称为是Bloom Filter的组成:每一个SSTable都有一个Bloom Filter,以用来判断与其关联的SSTable是否包含当前查询所请求的一条或多条数据。若是是,Cassandra将尝试从该SSTable中取出数据;若是不是,Cassandra则会忽略该SSTable,以减小没必要要的磁盘访问。

  在经由Bloom Filter判断出与其关联的SSTable包含了请求所须要的数据以后,Cassandra就会开始尝试从该SSTable中取出数据了。首先,Cassandra会检查Partition Key Cache是否缓存了所要求数据的索引项Index Entry。若是存在,那么Cassandra会直接从Compression Offset Map中查询该数据所在的地址,并从该地址取回所须要的数据;若是Partition Key Cache并无缓存该Index Entry,那么Cassandra首先会从Partition Summary中找到Index Entry所在的大体位置,并进而从该位置开始搜索Partition Index,以找到该数据的Index Entry。在找到Index Entry以后,Cassandra就能够从Compression Offset Map找到相应的条目,并根据条目中所记录的数据的位移取得所须要的数据:

较文档中原图略做调整

  发现了么?实际上SSTable中所记录的数据仍然是顺序记录的各个域,可是不一样的是,它的查找首先经由了Partition Key Cache以及Compression Offset Map等一系列组成。这些组成仅仅包含了一系列对应关系,也就是至关于连续地记录了请求所须要的数据,进而提升了数据搜索的运行速度,不是么?

  Cassandra的写入流程也与Log-Structured Merge-Tree的写入流程很是相似:Log-Structured Merge-Tree中的日志对应着Commit Log,C0树对应着Memtable,而C1树则对应着SSTable的集合。在写入时,Cassandra会首先将数据写入到Memtable中,同时在Commit Log的末尾添加该写入所对应的记录。这样在机器断电等异常状况下,Cassandra仍能经过Commit Log来恢复Memtable中的数据。

  在持续地写入数据后,Memtable的大小将逐渐增加。在其大小到达某个阈值时,Cassandra的数据迁移流程就将被触发。该流程一方面会将Memtable中的数据添加到相应的SSTable的末尾,另外一方面则会将Commit Log中的写入记录移除。

  这也就会形成一个容易让读者困惑的问题:若是是将新的数据写入到SSTable的末尾,那么数据迁移的过程该如何执行对数据的更新?答案就是:在须要对数据进行更新时,Cassandra会在SSTable的末尾添加一条具备当前时间戳的记录,以使得其可以标明自身为最新的记录。而原有的在SSTable中的记录随即宣告失效。

  这会致使一个问题,那就是对数据的大量更新会致使SSTable所占用的磁盘空间迅速增加,并且其中所记录的数据不少都已是过时数据。所以在一段时间以后,磁盘的空间利用率会大幅降低。此时咱们就须要经过压缩SSTable的方式释放这些过时数据所占用的空间:

  如今有一个问题,那就是咱们能够根据重复数据的时间戳来判断哪条是最新的数据,可是咱们应该如何处理数据的删除呢?在Cassandra中,对数据的删除是经过一个被称为tombstone的组成来完成的。若是一条数据被添加了一个tombstone,那么其在下次压缩时就被认为是一条已经被删除的数据,从而不会添加到压缩后的SSTable中。

  在压缩过程当中,原有的SSTable和新的SSTable同时存在于磁盘上。这些原有的SSTable用来完成对数据读取的支持。一旦新的SSTable建立完毕,那么老的SSTable就将被删除。

  在这里咱们要提几点在平常使用Cassandra的过程当中须要注意的问题。首先是,因为经过Commit Log来重建Memtable是一个较为耗时的过程,所以咱们在须要重建Memtable的一系列操做前须要尝试手动触发归并逻辑,以将该结点上Memtable中的数据持久化到SSTable中。最多见的一种须要重建Memtable的操做就是从新启动Cassandra所在的结点。

  另外一个须要注意的地方是,不要过分地使用索引。虽说索引能够大幅地增长数据的读取速度,可是咱们一样须要在数据写入时对其进行维护,形成必定的性能损耗。在这点上,Cassandra和传统的关系型数据库没有太大区别。

 

Cassandra集群

  固然,使用单一的数据库实例来运行Cassandra并非一个好的选择。单一的服务器可能致使服务集群产生单点失效的问题,也没法充分利用Cassandra的横向扩展能力。所以从本节开始,咱们就将对Cassandra集群以及集群中所使用的各类机制进行简单地讲解。

  在一个Cassandra集群中经常包含着如下一系列组成:结点(Node),数据中心(Data Center)以及集群(Cluster)。结点是Cassandra集群中用来存储数据的最基础结构;数据中心则是处于同一地理区域的一系列结点的集合;而集群则经常由多个处于不一样区域的数据中心所组成:

  上图所展现的Cassandra集群由三个数据中心组成。这三个数据中心中的两个处于同一区域内,而另外一个数据中心则处于另外一个区域中。能够说,两个数据中心处于同一区域的状况并很少见,可是Cassandra的官方文档也没有否认这种集群搭建方式。每一个数据中心则包含了一系列结点,以用来存储Cassandra集群所要承载的数据。

  有了集群,咱们就须要使用一系列机制来完成集群之间的相互协做,并考虑集群所须要的一系列非功能性需求了:结点的状态维护,数据分发,扩展性(Scalability),高可用性,灾难恢复等。

  对结点的状态进行探测是高可用性的第一步,也是在结点间分发数据的基础。Cassandra使用了一种被称为是Gossip的点对点通信方案,以在Cassandra集群中的各个结点之间共享及传递各个结点的状态。只有这样,Cassandra才能知道到底哪些结点能够有效地保存数据,进而将对数据的操做分发给各结点。

  在保存数据的过程当中,Cassandra会使用一个被称为是Partitioner的组成来决定数据到底要分发到哪些结点上。而另外一个和数据存储相关的组成就是Snitch。其会提供根据集群中全部结点的性能来决定如何对数据进行读写。

  这些组成内部也使用了一系列业界所经常使用的方法。例如Cassandra内部经过VNode来处理各硬件的性能不一样,从而在物理硬件层次上造成一种相似《企业级负载平衡简介》一文所中提到过的Weighted Round Robin的解决方案。再好比其内部使用了Consistent Hash,咱们也在《Memcached简介》一文中给出过介绍。

  好了,简介完成。在下面几节中,咱们就将对Cassandra所使用的这些机制进行介绍。

 

Gossip

  首先就是Gossip。其是用来在Cassandra集群中的各个结点之间传输结点状态的协议。它每秒都将运行一次,并将当前Cassandra结点的状态以及其所知的其它结点的状态与至多三个其它结点交换。经过这种方法,Cassandra的有效结点能很快地了解当前集群中其它结点的状态。同时这些状态信息还包含一个时间戳,以容许Gossip判断到底哪一个状态是更新的状态。

  除了在集群中的各个结点之间交换各结点的状态以外,Gossip还须要可以应对对集群进行操做的一系列动做。这些操做包括结点的添加,移除,从新加入等。为了可以更好地处理这些状况,Gossip提出了一个叫作Seed Node的概念。其用来为各个新加入的结点提供一个启动Gossip交换的入口。在加入到Cassandra集群以后,新结点就能够首先尝试着跟其所记录的一系列Seed Node交换状态。这一方面能够获得Cassandra集群中其它结点的信息,进而容许其与这些结点进行通信,又能够将本身加入的信息经过这些Seed Node传递出去。因为一个结点所获得的结点状态信息经常被记录在磁盘等持久化组成中,所以在从新启动以后,其仍然能够经过这些持久化后的结点信息进行通信,以从新加入Gossip交换。而在一个结点失效的状况下,其它结点将会定时地向该结点发送探测消息,以尝试与其恢复链接。可是这会为咱们永久地移除一个结点带来麻烦:其它Cassandra结点总以为该结点将在某一时刻从新加入集群,所以一直向该结点发送探测信息。此时咱们就须要使用Cassandra所提供的结点工具了。

  那么Gossip是如何判断是否某个结点失效了呢?若是在交换过程当中,参与交换的另外一方好久不回答,那么当前结点就会将目标结点标示为失效,并进而经过Gossip协议将该状态传递出去。因为Cassandra集群的拓扑结构可能很是复杂,如跨区域等,所以其用来判断一个结点是否失效的标准并非在多长时间以内没有响应就断定为失效。毕竟这会致使很大的问题:两个在同一个Lab中的结点进行状态交换会很是快,而跨区域的交换则会比较慢。若是咱们设置的时间较短,那么跨区域的状态交换经常会被误报为失效;若是咱们所设置的时间较长,那么Gossip对结点失效的探测灵敏度将下降。为了不这种状况,Gossip使用的是一种根据以往结点间交换历史等众多因素综合起来的决策逻辑。这样对于两个距离较远的结点,其将拥有较大的时间窗,从而不会产生误报。而对于两个距离较近的结点,Gossip将使用较小的时间窗,从而提升探测的灵敏度。

 

Consistent Hash

  接下来咱们要讲的是Consistent Hash。在一般的哈希算法中经常包含着桶这个概念。每次哈希计算都是在决定特定数据须要存储在哪一个桶中。而若是桶的数量发生了变化,那么以前的哈希计算结果都将失效。而Consistent Hash则很好地解决了该问题。

  那Consistent Hash是如何工做的呢?首先请考虑一个圆,在该圆上分布了多个点,以表示整数0到1023。这些整数平均分布在整个圆上:

  在上图中,咱们突出地显示了将圆六等分的六个蓝点,表示用来记录数据的六个结点。这六个结点将各自负责一个范围。例如512这个蓝点所对应的结点就将记录从哈希值为512到681这个区间的数据。在Cassandra以及其它的一些领域中,这个圆被称为是一个Ring。接下来咱们就对当前须要存储的数据执行哈希计算,并获得该数据所对应的哈希值。例如一段数据的哈希值为900,那么它就位于853和1024之间:

  所以该数据将被蓝点853所对应的结点记录。这样一旦其它结点失效,该数据所在的结点也不会发生变化:

  那每段数据的哈希值究竟是如何计算出来的呢?答案是Partitioner。其输入为数据的Partition Key。而其计算结果在Ring上的位置就决定了究竟是由哪些结点来完成对数据的保存。

 

Virtual Node

  上面咱们介绍了Consistent Hash的运行原理。可是这里还有一个问题,那就是失效的那个结点上的数据该怎么办?咱们就没法访问了么?这取决于咱们对Cassandra集群数据复制方面的设置。一般状况下,咱们都会启用该功能,从而使得多个结点同时记录一份数据的拷贝。那么在其中一个结点失效的状况下,其它结点仍然能够用来读取该数据。

  这里要处理的一个状况就是,各个物理结点所具备的容量并不相同。简单地说,若是一个结点所能提供的服务能力远小于其它结点,那么为其分配相同的负载将使得它不堪重负。为了处理这种状况,Cassandra提供了一种被称为VNode的解决方案。在该解决方案中,每一个物理结点将根据其实际容量被划分为一系列具备相同容量的VNode。每一个VNode则用来负责Ring上的一段数据。例如对于刚刚所展现的具备六个结点的Ring,各个VNode和物理机之间的关系则可能以下所示:

  在使用VNode时,咱们经常须要注意的一点就是Replication Factor的设置。从其所表示的意义来说,Cassandra中的Replication Factor和其它常见数据库中所使用的Replication Factor没有什么不一样:其所具备的数值用来表示记录在Cassandra中的数据有多少份拷贝。例如在其被设置为1的状况下,Cassandra将只会保存一份数据。若是其被设置为2,那么Cassandra将多保存一份这些数据的拷贝。

  在决定Cassandra集群所须要使用的Replication Factor时,咱们须要考虑如下一系列因素:

  • 物理机的数量。试想一下,若是咱们将Replication Factor设置为超过物理机的数量,那么必然会有物理机保存了同一份数据的两部分拷贝。这实际上没有太大的做用:一旦该物理机出现异常,那就会一次损失多份数据。所以就高可用性这一点来讲,Replication Factor的数值超过物理机的数量时,多出的这些数据拷贝意义并不大。
  • 物理机的异构性。物理机的异构性经常也会影响您所设Replication Factor的效果。举一个极端的例子。若是说咱们有一个Cassandra集群并且其由五台物理机组成。其中一台物理机的容量是其它物理机的4倍。那么将Replication Factor设置为3时将会出现具备较大容量的物理机上存储了一样的数据这种问题。其并不比设置为2好多少。

  所以在决定一个Cassandra集群的Replication Factor时,咱们要仔细地根据集群中物理机的数量和容量设置一个合适的数值。不然其只会致使更多的无用的数据拷贝。

 

注:这篇文章写于15年8月。鉴于NoSQL数据库发展很是快,并且经常具备一系列影响后向兼容性的更改(如Spring Data Neo4J已经不支持@Fetch)。所以若是您发现有什么描述已经发生了改变,请帮留下评论,以便其它读者参考。在此感激涕零

转载请注明原文地址并标明转载:http://www.cnblogs.com/loveis715/p/5299495.html

商业转载请事先与我联系:silverfox715@sina.com

公众号必定帮忙别标成原创,由于协调起来太麻烦了。。。

相关文章
相关标签/搜索