写点什么

NoSQL 数据库——Cassandra

用户头像
hanaper
关注
发布于: 3 小时前
NoSQL数据库——Cassandra

技术选型

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

  • 该技术所提供的功能是否能够完整地解决问题。

  • 该技术的扩展性如何。是否允许用户添加自定义组成来满足特殊的需求。

  • 该技术是否有丰富完整的文档,并且能够以免费甚至付费的形式得到专业的支持。

  • 该技术是否有很多人使用,尤其是一些大型企业在使用,并存在着成功的案例。  

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

针对我们刚刚所提到的需求——记录并处理系统自动生成的大量数据,我们在技术选型的初始阶段会有很多种选择: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 数据库。

NoSQL数据库

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


{Name: "Jefferson",Children: [{Name:"Hillary",Age: 14}, {Name:"Todd",Age: 12}],Age: 45,Address: {number: 1234,street: "Fake road",City: "Fake City",state: "NY",Country: "USA"}}


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

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


SELECT * FROM customers WHERE country='Mexico';


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


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


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

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


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

至此为止,您应该能够根据各种数据库所具有的特性来为您的需求选择一个合适的 NoSQL 数据库了。


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 所支持的最基础的数据模型。该模型中可以包含一系列键值对:{"name": "Auther Name","value": "Sam","timestamp": 123456789}Super Column 则包含了一系列 Column。在一个 Super Column 中的属性可以是一个 Column 的集合:{"name": "Cassandra Introduction","value": {"auther": { "name": "Auther Name", "value": "Sam", "timestamp": 123456789},"publisher": { "name": "Publisher", "value": "China Press", "timestamp": 234567890}}}这里需要注意的是,Cassandra 文档已经不再建议过多的使用 Super Column,而原因却没有直接说明。据说这和 Super Column 常常需要在数据访问时执行反序列化相关。一个最为常见的证据就是,网络上常常会有一些开发人员在 Super Column 中添加了过多的数据,并进而导致和这些 Super Column 相关的请求运行缓慢。当然这只是猜测。只不过既然官方文档都已经开始对 Super Column 持谨慎意见,那么我们也需要在日常使用过程中尽量避免使用 Super Column。  而一个 Column Family 则是一系列 Column 的集合。在该集合中,每个 Column 都会有一个与之相关联的键:Authers = {“1332”: {"name": "Auther Name","value": "Sam","timestamp": 123456789},“1452”: {“name”: “Auther Name”,“value”: “Lucy”,“timestamp”: 012343437}}上面的 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 中取得数据的一个或多个列:create table sample (key text PRIMARY KEY,data text);在上面的示例中,我们指定了 key 域作为 sample 的 PRIMARY KEY。而在需要的情况下,一个 Primary Key 也可以由多个列共同组成:create table sample {key_one text,key_two text,data text,PRIMARY KEY(key_one, key_two)};在上面的示例中,我们所创建的 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 同样也可以由多个列组成:create table sample {key_primary_one text,key_primary_two text,key_cluster_one text,key_cluster_two text,data text,PRIMARY KEY((key_primary_one, key_primary_two), key_cluster_one, key_cluster_two)};而在一个 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 内部排序,因此其对于包含范围筛选的各种请求的支持较好。


NoSQL 数据库​github.com


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 树中同一层次的各个结点在磁盘中是连续记录的。这样磁盘就可以通过连续读取来避免在磁盘上的过多寻道,从而大大地提高了运行效率。Memtable 和 SSTable  好,刚刚我们已经提到了 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 和传统的关系型数据库没有太大区别。NoSQL 数据库​github.comCassandra 集群  当然,使用单一的数据库实例来运行 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 将使用较小的时间窗,从而提高探测的灵敏度。NoSQL 数据库​github.comConsistent 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 时,我们要仔细地根据集群中物理机的数量和容量设置一个合适的数值。否则其只会导致更多的无用的数据拷贝。

用户头像

hanaper

关注

还未添加个人签名 2018.05.07 加入

还未添加个人简介

评论

发布
暂无评论
NoSQL数据库——Cassandra