暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

cassandra 结构学习2

原创 不忘初心方得始终 2021-11-15
1788

由前面的cassandra学习一文我们能够简单的认识和使用cassandra。现在让我们来更深层次的学习Cassandra。

1. 知识储备

1.1 宽列存储模型Wide Column Databases

1.1.1 wide cilumn

一种多维嵌套排序的map,数据存储在列的单元格中,并归入列族”。可以将数据分组为列并允许无限数量的存储。

宽列数据库(Wide Column Databases)或称列族数据库(Column Family Databases),指的是一类NoSQL数据库,它能很好地用于存储可收集的巨量数据。它的架构使用持久的、稀疏的矩阵、多维度的映射(行值、列值和时间戳),采用表格式,意在实现大规模的可扩展性(超过PB规模)。Column Family商店并没有遵循关系模型,也没有针对连接进行优化。

对于具有临时查询模式、高层聚合和不断变化的数据库要求的应用来说,宽列数据库不是首选。这种类型的数据存储不能保持良好的Data Lineage(Data Lineage描述了数据的来源,移动,特征和质量)。(https://en.wikipedia.org/wiki/Wide-column_store)

img

1.1.2 分析

简单来说,Wide-column store是概念上Column有无穷多的column。每一行可以有不同的column,相当于一个无限多column的table,每个行有很多的NULL value (空格),所以像个sparse table。同时无schema限制可以自由扩展。享受了schema-free的好处。

又有一个名词叫paritioned row store. 把这些ROW打散到不同的节点上。So called wide column就reflect了这个分布式特性。

为了支持这个无限column,每一行的每个column必须要带着column的名字。所以,有人说wide column store就是一个二维的KV store。

1
2
3
row1: , , 
row2:
row3:

一个特殊的概念是column family. 比如course column family,就有attributes course:name和course:time.
每个column family物理上是存在一个file里面的。

Maybe this is a better image of four wide column databases.

Wide column databases

Column family和relational table之间的不同就在于column family能够不断的加column。Wide column store就是用KV store来包装成NoSQL数据库,提供一些简单的SQL服务。Tradeoff 一些功能比如Join,来提供schema的灵活性。注意这个和columnar DB,columnar format,有本质不同。那个是有schema的,只是把column的数据物理存放在一起,便于SELECT。

举个栗子来说,对于传统客户表来说,客户信息可能是这样。

1
2
3
Customer ID      name          address             address3
----------- --------- --------------- ------------------------
100001 John Smith 10 Victory Lane Pittsburgh, PA 15120

对于上述,很显然的是不同人都存在同样的属性,现实中每个人都存在自己的个性,所以我们为了使每个人都拥有自己的个性第一个wide-area database可以像下面这种设计,

1
2
3
4
5
Customer ID    Attribute    Value
----------- --------- ---------------
100001 name John Smith
100001 address 1 10 Victory Lane
100001 address 3 Pittsburgh, PA 15120

如果我们想要更多的属性可以像下面这样

1
2
3
4
5
6
7
Customer ID    Attribute    Value
----------- --------- ---------------
100001 name John Smith
100001 address 1 10 Victory Lane
100001 address 3 Pittsburgh, PA 15120
100001 fav color blue
100001 fav shirt golf shirt

上述很显然的不够优雅,因此我们可以把Customer ID: 100001作为super column name,如下

1
2
3
4
5
Customer ID: 100001
Attribute Value
--------- --------------
fav color blue
fav shirt golf shirt

这样拥有和实体一样多的超级列模型。它们可以在单独的NoSQL表中,也可以放在一起作为一个超级列族。列族和超级列族只是给图片中的前两个模型一个行id,以便更快地检索信息。

1.2 SSTables

SSTable的全称是Sorted Strings Table,是一种不可修改的有序的键值映射,提供了查询、遍历等功能。每个SSTable由一系列的块(block)组成,Bigtable将块默认设为64KB。在SSTable的尾部存储着块索引,在访问SSTable时,整个索引会被读入内存。BigTable论文没有提到SSTable的具体结构,LevelDb日知录之四: SSTable文件这篇文章对LevelDb的SSTable格式进行了介绍,因为LevelDB的作者JeffreyDean正是BigTable的设计师,所以极具参考价值。每一个片(tablet)在GFS里都是按照SSTable的格式存储的,每个片可能对应多个SSTable。

1.3 Bigtable数据模型

BigTable是一种压缩的、高效能的、高可扩展性的,基于Google档案系统(Google File System,GFS)的数据存储系统,用于储存大规模结构化数据,适用于云端计算

BigTable也是采用Wide Column存储模型。

BigTable不是传统的关系型数据库,不支援JOIN这样的SQL语法,BigTable更像今日的NoSQL的Table-oriented,优势在于扩展性和性能。BigTable的Table资料结构包括row key、col key和timestamp,其中row key用于储存倒转的URL,例如www.google.com必须改成com.google.www。BigTable使用大量的Table,在Table之下还有Tablet。每一个Tablets大概有100-200MB,每台机器有100个左右的Tablets。所谓的Table是属于immutable的SSTables,也就是存储方式不可修改。另外Table还必须进行压缩,其压缩又分成table的压缩或系统的压缩。客户端有一指向META0的Tablets的指标,META0 tablets保储所有的META1的tablets的资料记录。

1.3.1 Bigtable依赖于google的几项技术

Bigtable依赖于google的几项技术。用GFS来存储日志和数据文件;按SSTable文件格式存储数据;用Chubby管理元数据。

image-20201029170646390

GFS参见谷歌技术”三宝”之谷歌文件系统。BigTable的数据和日志都是写入GFS的。

SSTable的全称是Sorted Strings Table,是一种不可修改的有序的键值映射,提供了查询、遍历等功能。

Chubby是一种高可用的分布式锁服务,Chubby有五个活跃副本,同时只有一个主副本提供服务,副本之间用Paxos算法维持一致性,Chubby提供了一个命名空间(包括一些目录和文件),每个目录和文件就是一个锁,Chubby的客户端必须和Chubby保持会话,客户端的会话若过期则会丢失所有的锁。关于Chubby的详细信息可以看google的另一篇论文:The Chubby lock service for loosely-coupled distributed systems。Chubby用于片定位,片服务器的状态监控,访问控制列表存储等任务。

hbase就是模仿bigtable的。

1.4 gossip协议

1.4.1 定义

gossip 协议(gossip protocol)又称 epidemic 协议(epidemic protocol),是基于流行病传播方式的节点或者进程之间信息交换的协议,在分布式系统中被广泛使用,比如我们可以使用 gossip 协议来确保网络中所有节点的数据一样。gossip protocol 最初是由施乐公司帕洛阿尔托研究中心(Palo Alto Research Center)的研究员艾伦·德默斯(Alan Demers)于1987年创造的。

从 gossip 单词就可以看到,其中文意思是八卦、流言等意思,我们可以想象下绯闻的传播(或者流行病的传播);gossip 协议的工作原理就类似于这个。gossip 协议利用一种随机的方式将信息传播到整个网络中,并在一定时间内使得系统内的所有节点数据一致。Gossip 其实是一种去中心化思路的分布式协议,解决状态在集群中的传播和状态一致性的保证两个问题。

gossip

1.4.2 gossip 优势

可扩展性(Scalable)

gossip 协议是可扩展的,一般需要 O(logN) 轮就可以将信息传播到所有的节点,其中 N 代表节点的个数。每个节点仅发送固定数量的消息,并且与网络中节点数目无法。在数据传送的时候,节点并不会等待消息的 ack,所以消息传送失败也没有关系,因为可以通过其他节点将消息传递给之前传送失败的节点。系统可以轻松扩展到数百万个进程。

容错(Fault-tolerance)

网络中任何节点的重启或者宕机都不会影响 gossip 协议的运行。

健壮性(Robust)

gossip 协议是去中心化的协议,所以集群中的所有节点都是对等的,没有特殊的节点,所以任何节点出现问题都不会阻止其他节点继续发送消息。任何节点都可以随时加入或离开,而不会影响系统的整体服务质量(QOS)

最终一致性(Convergent consistency)

Gossip 协议实现信息指数级的快速传播,因此在有新信息需要传播时,消息可以快速地发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。

1.4.3 gossip 协议的类型

前面说了节点会将信息传播到整个网络中,那么节点在什么情况下发起信息交换?这就涉及到 gossip 协议的类型。目前主要有两种方法:

  • Anti-Entropy(反熵):以固定的概率传播所有的数据
  • Rumor-Mongering(谣言传播):仅传播新到达的数据

1.4.3.1 Anti-Entropy

Anti-Entropy 的主要工作方式是:每个节点周期性地随机选择其他节点,然后通过互相交换自己的所有数据来消除两者之间的差异。Anti-Entropy 这种方法非常可靠,但是每次节点两两交换自己的所有数据会带来非常大的通信负担,以此不会频繁使用。

Anti-Entropy 使用“simple epidemics”的方式,所以其包含两种状态:susceptibleinfective,这种模型也称为 SI model。处于 infective 状态的节点代表其有数据更新,并且会将这个数据分享给其他节点;处于 susceptible 状态的节点代表其并没有收到来自其他节点的更新。

1.4.3.2 Rumor-Mongering

Rumor-Mongering 的主要工作方式是:当一个节点有了新的信息后,这个节点变成活跃状态,并周期性地联系其他节点向其发送新信息。直到所有的节点都知道该新信息。因为节点之间只是交换新信息,所有大大减少了通信的负担。

Rumor-Mongering 使用“complex epidemics”方法,相比 Anti-Entropy 多了一种状态:removed,这种模型也称为 SIR model。处于 removed 状态的节点说明其已经接收到来自其他节点的更新,但是其并不会将这个更新分享给其他节点。

因为 Rumor 消息会在某个时间标记为 removed,然后不会发送给其他节点,所以 Rumor-Mongering 类型的 gossip 协议有极小概率使得更新不会达到所有节点。

一般来说,为了在通信代价和可靠性之间取得折中,需要将这两种方法结合使用。

1.4.4 gossip 协议的通讯方式

不管是 Anti-Entropy 还是 Rumor-Mongering 都涉及到节点间的数据交互方式,节点间的交互方式主要有三种:Push、Pull 以及 Push&Pull。

  • Push:发起信息交换的节点 A 随机选择联系节点 B,并向其发送自己的信息,节点 B 在收到信息后更新比自己新的数据,一般拥有新信息的节点才会作为发起节点。
  • Pull:发起信息交换的节点 A 随机选择联系节点 B,并从对方获取信息。一般无新信息的节点才会作为发起节点。
  • Push&Pull:发起信息交换的节点 A 向选择的节点 B 发送信息,同时从对方获取数据,用于更新自己的本地数据。

1.5 Cassandra的节点之间交互(gossip)

种子节点(节点的代表):

当节点第一次启动时,它会去查配置文件cassandra.yaml从而得到它属于的集群名称,但是它如何获得集群中其他节点的信息呢?就是通过种子节点(seed node).记住,同一集群中所有的节点的cassandra.yaml中必须有相同的种子节点列表。

image-20201029194224730

选派谁做种子节点没什么特别的意义,仅仅在于新节点加入到集群中时走gossip流程时有用,所以它们没什么特权。

注意:在多个data center集群中,seed list应该至少有一个在每个datacenter中。建议在每个data center中配置多于1个seed node,以便容错。否则gossip必须与其他data center交互当启动一个节点时。不建议将每个节点都标记为seed节点,因为这会增加维护和减少gossip性能。建议使用较少的seed列表(每个data center三个节点)

Cassandra故障探测:

Gossip 进程(Gossiper)通过每个节点的心跳(heartbeat)来感知每个节点是否还存活。它会考虑到网络状况,负载等因素综合考虑来计算节点心跳时间的临界值。gossip过程中,每个节点都维护着其他节点gossip消息的内部到达次数。在Cassandra中,如果配置phi_convict_threshold,可以调节失败探测的敏感度,从而适应相对不可靠的网络环境,超过这个时间(秒)则被cassandra认为这个节点down了。

image-20201029194245443

节点down了只表示节点暂时与Cassandra集群脱离了,而不是永久脱离。所以,集群中的节点依然会向这个down了的节点定时发送gossip信息来探测是否这个节点恢复了。如果要永久的把这个节点从集群中移除,必须用

Cassandra节点故障恢复:

短时间down的恢复:

当一个节点恢复之后,也许它少了一些已经写入其他节点的数据,那么它将从它故障点开始,从其他备份获得数据).

长时间down的恢复:

如果一个节点down的时间超过max_hint_window_in_ms设置的值,则超过这个时间的写操作部分是没办法恢复的。这时候我们可以在所有节点上运行nodetool维护,以确保他们有一致的数据。

image-20201029194256701

1.5 一致性哈希

1.5.1 基本概念了解

1
int index = hash(target) % node.length

普通Hash算法在平均性上来说做的足够好,但是由于该算法使用节点取余的方法,强依赖node的数目,因此,当node数发生变化的时候,数据所对应的node就会发生剧烈的变化,而发生变化的成本就是我们需要在node数发生变化的时候,数据需要迁移。一般增加一个node还是减少一个node,都需要迁移99%的数据。

基于上述普通hash算法的代价太大提出一致性hash,主要增加哈希值空间圆环,把node和target分别映射到hash环上,这样一来node数量和targe就解耦了,然后根据index相对position位置把数据放到那个槽上。

1
2
int position = hash(pc_sn) % 2^32; // 计算机再
int index = hash(target) % 2^32;

上述依然存在一些问题,即当两个槽在哈希空间环上太近的时候会造成数据分布不均匀的问题。所以基于上述问题引入了虚拟节点机制,把虚拟节点映射到环上之后,之后虚拟节点在映射到真实的节点上即可。

1.5.2 Cassandra 数据分布和复制

在Cassandra中,数据分布和复制是同时进行的。数据通过表来组织,通过主键标识——它决定了数据被存储到哪个节点。副本就是数据行的拷贝。当数据在第一次写入的时候,也就作为第一份副本。

影响复制的因素:

Virtual nodes(虚拟节点):分配数据所有权到物理机
Partitioner(分割器):分割集群中的数据
Replicationstrategy(复制策略):决定每行数据的副本
Snitch:定义存储副本的复制策略的拓扑结构

1.5.2.1 一致性hash

一致性哈希允许分布在集群中的数据在节点增加或减少时最小化重组。一致性哈希是通过主键分割数据的。例如我们有下面的数据:

wKioL1cBIcCxqAXpAABWEppDSnQ885.jpg

Cassandra为每个分区键分配一个哈希值。

image-20201029195320810

集群中的每个节点通过哈希值负责维护一定范围的数据。

image-20201029195401791

Caasandra通过分区键和其负责的数据范围存储数据到每个节点。例如,在四个节点的集群中,例子中的数据是这样分布的:

wKioL1cBIefgD0VPAACYLbk-LgQ947.jpg

但是一致性哈希的路由算法尚有不足之处。在查询过程中,查询消息要经过O(N)步(O(N)表示与N成正比关系,N代表系统内的节点总数)才能到达被查询的节点。不难想象,当系统规模非常大时,节点数量可能超过百万,这样的查询效率显然难以满足使用的需要。换个角度来看,即使用户能够忍受漫长的时延,查询过程中产生的大量消息也会给网络带来不必要的负荷。

1.5.2.2 虚节点

  1. 虚节点简化了Cassandra的任务处理:
  2. 不需要计算和分配令牌到每个节点;
  3. 不需要在新增或减少节点后重新平衡集群。当节点加入到集群后,该节点假设自己负责集群中其他节点数据的一部分。当节点挂掉后,负载将转换到集群中的其他节点
  4. 重建一个挂掉的节点非常快,因为它自动融入到集群中的其他节点;
  5. 改善了集群中异构机器的使用效果,可以按照机器的配置分配不同的虚拟节点(就是说可以让高配置的集群承担多的工作)。

那么数据是如何通过虚拟节点在集群中分布的呢?

在以前的1.2版本,我们必须计算并分配唯一的令牌给集群中的节点。每个令牌决定了节点在环中的位置以及通过哈希值计算到的数据。从1.2版本开始,Cassandra允许每个节点拥有多个令牌。这种新的范式叫做虚拟节点(vnodes)。虚拟节点(vnodes)允许每个节点拥有许多分布到集群中的小的分区范围。虚拟节点(vnodes)通过一致性哈希分布数据而不通过请求产生令牌和相关的任务。

1.5.2.3 数据复制

Cassandra存储副本到多个节点确保可靠性和故障恢复能力。复制策略决定了在哪个节点存储副本。

集群中的副本数量也就是复制因子。如果复制因子是1,那么就表示集群中只有一个行的一个拷贝。复制因子是2表示每个行有两份拷贝,当然它们是存储在不同的节点上的。

目前有两个复制策略SimpleStrategy和NetworkTopologyStrategy。

SimpleStrategy:只能用于一个数据中心的情况。

SimpleStrategy把第一份数据放到哪个节点取决于分割器,其他的副本放到环中顺时针的下一个节点(不考虑拓扑关系,也就是机架或数据中心)。

NetworkTopologyStrategy:如果想要以后方便扩展,那么强烈推荐这个策略。

如果你想要有多个数据中心,那么就选NetworkTopologyStrategy这个策略。它指定了在每个数据中心存储的副本数。 NetworkTopologyStrategy在直到到达其他机架的第一个节点时都通过顺时针的方式存储副本在同一个数据中心。 NetworkTopologyStrategy尝试把数据放到其他机架,因为同一个机架可能会因为同样的原因挂掉。

当决定在每个数据中心放置几份副本时,有两个主要考虑的地方:(1)本地读,防止跨机房,(2)故障场景。下面是在配置多个数据中心通常的做法:

每个数据中心两份副本:这种配置容忍每个副本组在单一节点失效并能在一致性为ONE的时候允许本地读。

每个数据中心三份副本:这种配置容忍一个节点在强一致性标准LOCAL_QUORUM下失效或者在一致性标准ONE下有一个数据中心多个节点失效。

非对称的复制分组也是可以的。比如,可以在一个数据中心使用三份副本处理实时应用程序的请求,然后在其他地方使用一份副本处理分析。

选择keyspace的复制选项

为了设置keyspace的复制策略,可以查阅CREATEKEYSPACE命令。

当使用NetworkToplogyStrategy策略创建keyspace时,你将使用为snitch定义的数据中心名字。为了把副本存储到正确的位 置,Cassandra要求使用snitch配置的数据中心名字来定义keyspace。例如,如果集群使用PropertyFileSnitch,那么 在创建keyspace的时候就需要在cassandra-topologies.properties文件中定义用户自定义的数据中心名和机架名。如果 使用EC2Snitch,就需要定义EC2的数据中心和机架名(一般国内暂时用不着)。

1.6 Cassandra Snitch

Snitch决定了节点属于哪个数据中心和机架。Snitch通知Cassandra网络拓扑以便请求被有效的路由,并且允许Cassandra在服务器增加到数据中心或机架的时候能够分发副本。特别的,复制策略如何放置副本是基于新snitch提供的信息。Cassandra不会把副本放到一个机架里面(如果机架断电,那就over了)。

1.6.1 动态snitching

监控从大量副本读取数据的性能和选择最优的副本是基于这些历史条件的。默认情况下,所有的snitch也使用动态的snitch层监控读的延时,并且,在可能的情况下,不路由请求低性能的节点。动态snitch是默认启用的,这个也是大多数情况推荐的。

1.6.2 SimpleSnitch

这个选项是只有一个数据中心的时候才能用。默认情况下,SimpleSnitch是不知道数据中心和机架信息的。如果要用的话,在定义keyspace的时候就需要使用SimpleStrategy并指定复制因子。

1.6.3 RackInferringSnitch

这是通过机架或数据中心的IP来决定节点的位置。

1.6.4 PropertyFileSnitch

它也是通过机架或数据中心决定节点的位置。这个就需要自己配置cassandra-topology.properties。如果你的IP没有规则或者有 复杂的复制增加需求就可以使用这个snitch。这个snitch需要你在cassandra-topology.properties定义每个节点(有 点麻烦)。

1.6.5 GossipingPropertyFileSnitch

这个snitch在新增节点的时候会通过gossip自动更新每个节点。

其他还有EC2Snitch,EC2MultiRegionSnitch,GoogleCloudSnitch,CloudstackSnitch等,由于在外部共有云上,对此暂时不做研究。

2.cassandra

通过上文cassandra 的结构模型中我们了解到Cassandra使用了BigTable的数据模型即宽列存储模型(Wide Column 有很大Stores),这与面向行(row)的传统的关系型数据库键值存储的key-value数据库不同。不过与BigTable和其模仿者HBase不同,Cassandra的数据并不存储在分布式文件系统如GFSHDFS中,而是直接存于本地。但是与BigTable一样,Cassandra也是日志型数据库,即把新写入的数据存储在内存的Memtable中并通过磁盘中的CommitLog来做持久化,内存填满后将数据按照key的顺序写进一个只读文件SSTable中,每次读取数据时将所有SSTable和内存中的数据进行查找和合并。这种系统的特点是写入比读取更快。

Cassandra的系统架构与Dynamo类似,是基于一致性哈希的完全P2P架构,每行数据通过哈希来决定应该存在哪个或哪些节点中[17]。集群没有master的概念,所有节点都是同样的角色,彻底避免了整个系统的单点问题导致的不稳定性,集群间的状态同步通过Gossip协议来进行P2P的通信。

2.1 架构理念

2.1.1 架构简介

Cassandra是设计用于跨多节点方式处理大数据,它没有单点故障;这种架构设计之初就考虑到了系统和硬件故障。Cassandra地址发生失效问题,通过采用跨节点的分布式系统,将数据分布在集群中的所有节点上解决。每个节点使用P2P的gossip协议来改变集群中的自己和其他节点的状态信息。写操作按顺序记录在每个节点的commit log上,以确保数据持久化。数据写入到一个in-memory结构,叫做memtable,类似于一个write-back缓存。每当memtable满了时,数据就写入到硬盘SSTable数据文件中。所有的写都自动分区和复制。Cassandra定期的使用compaction压缩SSTable。丢弃标记为tombstone的过期数据。为了保证集群数据的一致性,可以采用不同的repair机制。

Cassandra是一个分区行存储数据库,行被保存在tables且必须有一个primary key。Cassandra的架构允许任何授权用户连接到任意数据中心的任意节点,使用CQL语言访问数据。为了使用简单,CQL使用类SQL语法。开发人员通过使用cqlsh,DevCenter和应用驱动来访问CQL。典型的,一个集群有一个keyspace对于每个应用,包含了很多不同的tables,类似于schema。

客户端的读和写可以被发送到集群中的任意节点。当一个客户端连接到一个节点,那个节点服务器作为特殊的客户端操作的coordinator。Coordinator作为客户端应用和节点之间的代理。Coordinator基于集群的配置,决定了请求被发送到哪个节点上。

2.1.2 关键结构

  • Node

    存储数据的地方,是Cassandra的基础组件。

  • Data center

相关节点的集合。Data center可以是物理的datacenter或者是虚拟的data center。不同的workloads应该使用不同的data center。使用不同的data center可以预防Cassandra事物不被其他的workloads影响,保证请求发送到低延迟的data center。依赖于复制因子,数据可以被写入到多个data center。Data centers不能跨物理位置。

  • Cluter

    一个集群包含一个或多个data centers。它可以跨物理位置。

  • Commit log

    所有的数据第一次都被写入到commit log作持久化。之后数据被flush到SSTables。

  • SStable

A sorted stringtable(SSTable)是一个不可变的数据文件,Cassandra将memtables定期的写入其中。

2.1.3 Cassandra关键组件和配置

  • Gossip

Gossip是一中P2P的通讯协议,Cassandra用它来发现并分享节点间的地址和状态信息。Gossip的信息也被持久化到本地节点,当有节点启动的时候,这些信息就被立即使用。

  • Partitioner

Partitioner决定了集群节点的数据分布。基本上,partitioner是一个hash函数,计算partition key的token值。每行数据通过partition key进行唯一识别的,通过token值分布在集群。Murmur3Partitioner是新的Cassandra集群的缺省的分区策略,大部分案例都使用这个选项。

你必须设定partitioner,给每个节点分配一个num_tokens值。分配tokens的数据取决于系统的hardwarecapabilities。如果没有使用虚节点(vnodes),使用initial_token设置。

  • Replication factor(复制因子)

    集群中副本的总数量。Replication factor为1则每行有1个副本。Replication factor为2则每行有2个副本,每个副本都在不同的节点上。所有的副本都是同等重要的,没有主副本之说。通常情况下应该设置replication factor大于1,但不超过集群节点数量。

    image-20201030095518623

  • Replica placement strategy(副本存储策略)

Cassandra存储数据副本在多个节点上,确保了可靠性和容错性。replication strategy决定了哪个节点存放副本。对于大部分部署,建议使用NetworkTopologyStrategy,因为它很容易扩展到多个datacenters。

image-20201030095600665

当你创建keyspace时,你必须指定replica placement strategy和Replication factor。

  • Snitch

Snitch决定了节点属于哪个数据中心和机架。Snitch通知Cassandra网络拓扑以便请求被有效的路由,并且允许Cassandra在服务器增加到数据中心或机架的时候能够分发副本。

创建集群是必须指定snitch。所有的 snitches使用动态snitchlayer,监控性能和选择最佳的副本读取。它是缺省配置并且建议使用它在大部分部署中。在cassandra.yaml配置文件中配置动态snitch阀值。

缺省的SimpleSnitch无法识别数据中心或机架信息。即使用它作为但data center部署。GossipingPropertyFileSnitch建议在生产环境中使用。它定义了节点的data center,使用gossip传播信息到其他节点。

  • cassandra.yaml配置文件

这个是配置集群初始化属性、表的缓存参数、调优和资源利用率属性、超时设置、客户端连接、备份和安全的主要配置文件。

默认情况下,节点用于存储数据的位置配置在cassandra.yaml里面。

在生产环境下,可以把commitlog-directory和data_file_directories放到不同的磁盘驱动器。

  • System keyspace table properties

你可以设置存储配置属性在每一个keyspace或每一个table,基于编程方式或使用客户端应用,比如CQL。

通过上述我们知道了Cassandra使用的wide column存储模型,所以接下来我们将以实例的模式来说明Cassandra数据模型的设计。

2.2 数据模型设计

2.2.1 概念介绍

2.2.1.1 关系型数据库

在关系型数据库中,我们拥有数据库本身,这一般是对应于单个应用的最外层的容 器。数据库里包含若干张表。表有名字和一个或多个列,每个列也有名孛。当向表里添加数据的时候,要指定每一列对应的值。如果对于某一列,没有值相对应,就 要置空(null)。这组新的数据项给表添加了一行,之后,如果我们知道这行的唯一标识(主键),还可以读出这行内容。

2.2.1.2 简介

1)值的列表:数组或列表。缺点不知道值的含义,必须遍历判断值到底什么意思。

2)“名/值”对的映射:map。这样我们就可以知道值的具体含义。但是这还存在一个问题,例如我存储一个人信息

1
2
3
4
5
{
"id":1,
"name":'dong',
"age":20
}

但是,如果我再次存一个人呢,也就是存储多个对象,可能就不是那么容易了,因为我们无法使用重复的列名(key),但是我们常常需要这么做,所以我们可能需要使用嵌套来实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
[row key1:{
"id":1,
"name":'wen',
"age":20
},
row key2:{
"id":2,
"name":'dong',
"age":20,
"email":"201388@gmail.com"
}]
}

# 解释
列名:id(列名除了string可以是其它的类型,想下Java的map)
一组列:id,name,age
行:有关一组列的一条记录
列族:行的集合。也就是table
稀疏,多维数组:wen和dong显然列不太一样wen没有邮箱所以也不需要null值来浪费空间,我们根本不会为某些行存储一些列。

这样一来,我们把每个对象看成一组列。同时我们还需要(一组列对应一行)的概念,如果读取一行,就可以获取一个对象的所有名/值对,或者获取我们所关心的名称下的值。我们把每个拥有某组列的集合的对象称为行,每个行的唯一标识称为行键值(row key)。

Cassandra还定义了一个列族的概念,列族,就是为具有相似但 不同列集合的行而准备的容器。

超级列族:一个列族中的一行是一个名/值对的集合,而超级列族中的列还包含有一组子列。 所以在一个普通的列族里找到一个值可以通过行键值和列名来找到,而在一个类型 为“super”的列族中寻址需要行键值、列名和子列的名称。这里其实只有细微的差 别,超级列族仍然包含列,只是每个列里拥有子列罢了。

2.2.1.3 集群

如果只运行一个单节点,Cassandra大概不是最佳选择。正如之前提到的, Cassandra数据库系统是为跨越多台主机共同工作,对用户呈现为一个整体的分布 式系统设计的。所以,Cassandra的最外层结构就是集群(cluster),有时也叫做环 (ring),因为Cassandra将集群中的节点组织成一个环,并依此来分配数据到集群中 的节点上。
每个节点会存放部分数据的一个副本。如果一个节点宕机了,它的另一个副本可以 响应查询请求。P2P协议允许数据以对用户透明的方式在节点间互相复制,副本 因子就是所有节点中存放的相同数据的副本数量。我们会在第6章里详细讨论这 个话题。

2.2.1.4 keyspace

集群是key space的容器,而且里面通常只有一个key space。key space是Cassandra中数据的最外层容器,和关系型数据库的概念非常接近。与关系型数据库类似, keyspace有一个名字和一些定义了整个keyspace范围的全局行为的属性。虽然人们常常建议,给每个应用建立一个单独的keyspace是个好主意,但实际上这没有太多事实依据。

与数据库是表的容器类似,keyspace是一个或多个列族的容器。列族就类似于关系 型数据库里的表,是集合了很多行的容器。每一行都有一些有序的列。列族的设置 就呈现了数据结构,每个keyspace都至少有一个列族,而通常会有多个列族。

在Cassandra中,可以针对keyspace设置的基本属性有如下几个:

  • 副本因子(Replication factor)

        简单地说,副本因子就是每行数据会复制到多少个节点上。如果副本因子是3, 那么每行数据将会复制到环上的三个节点,复制过程对于客户端是透明的。

    从本质上说,副本因子的选择决定了要为一致性付出多少性能代价。也就是说, 所得到的读写的一致性水平取决于副本因子的设定。

  • 副本放置策略(Replica placement strategy)

    副本放置策略是指数据的副本如何分布到环上。Cassandra本身有多种可选的放置策 略,用于决定键值到节点的映射方式。这些策略包括:SimpleStrategy (简单策略, 之前称为 RackUnawareStrategy,非机架感知策略)、OldNetworkTopologyStrategy (旧 网络拓扑策略,之前称为RackAwareStrategy,机架感知策略)和NetworkTopology- Strategy (网络拓扑策略,之前称为DatacenterShardStrategy,数椐中心分片策略)。

2.2.1.5 列

列(column)是Cassandra数据模型中的最基本数据结构单元。列是一个由名称、值和时钟构成的三元组,这个时钟可以看做一个时间戳。再次强调,虽然在关系型 数据库中,我们非常熟悉列这个名词,但如果你觉得Cassandra和关系型数据库里的列是一样的,就大错特错了。首先在关系型数据库中,你需要通过预先定义所 有列的名字来指定表的结构,而在稍后写的时候,则只要根据预先定义好的数据结 构提供值就可以了。

然而,在Cassandra之中不需要预先定义列,只要在keyspace里定义列族,之后就 可以开始写数据了。这是因为Cassandra之中所有列的名字都是由客户端提供的。 这可以让应用在使用数据时灵活得多,.也允许数据模型随着时间推移来循序渐进地 改变

名称和值的数据类型是Java的字节数组,内容经常是字符串。因为名称和值是 二进制类型的,所以它们可以是任意长度的。时钟的接口类型是org.apache, cassandra.db• IClock2,但0.7版本仍然后向兼容之前的时间戳。

image-20201030112951465

2.2.1.6 列族

列族(column family)是容纳一组有序的行的容器,每行都包含一组有序的列。在 关系型数据库世界里,按照模型来建立数据库的时候,会首先指定数据库的名字 (对应于keyspace)和表的名字(有些类似于列族,但不要真的认为两者是一回事, 因为事实并非如此),然后定义每张表中列的名字。

有几个不错的原因可以说明白列族和关系型数据库中的表实际相去甚远。首先, Cassandra被认为是无schema的,因为尽管定义了列族,但没定义列,你可以随 意在任意列族中添加任意的列,只要需要。其次,列族有两个属性:名称和比较器 (comparator)。比较器是在查询数据时返回的列的排序方式,可以根据long、byte、 UTF8或其他排序方式进行。
在关系型数据库中,表在磁盘上如何排序通常对用户是透明的,而且很少有人会建 议根据RDBMS如何在磁盘上存储表来进行数据建模的。这是另一个需要时刻注意 的列族与表的区別。因为每个列族在磁盘上都存储为不同的文件,所以把相关的列 放在同一个列族中十分重要。

列族与关系型数据库的表的另一个不同在于,关系型数据库仅定义了列,而用户提 供了值,这就是行。但在Cassandra中,一个列族可以放很多个列,甚至可以定义 为超级列族。使用超级列族的好处是允许嵌套定义。
标准的列族类型是Standard,这是默认情况;而对于超级列族,它的类型被设置为Super 。

向一个Cassandra列族中写数据的时候,要指定一个或多个列的值。这些值都通过称为行的唯一标识指定。行有唯一的键值,称为行键值(row key),类似于关系型 数据库表中的主键,可以唯一标识一行。所以,说Cassandra是面向列的并不确切, 如果你把行理解为是列的容器倒是更有利于对这一数据模型的理解。.这也是为什么 一些人认为Cassandra的列族类似于四维哈希:

1
[Keyspace][ColumnFamily] [Key][Column]

我们可以用一种类似JSON的方式来表示Hotel列族,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Hotel {
key: AZC_043 {
name: Cambria Suites Hayden,
phone: 480-444-4444,
address: 400 N. Hayden Rd.,
city: Scottsdale,
state: AZ,
zip: 85255
}
key: AZS_011 {
name: Clarion Scottsdale Peak,
phone: 480-333-3333,
address: 3000 N. Scottsdale Rd,
city: Scottsdale,
state: AZ,
zip: 85255
}
}

在这个例子中,行键值是Hotel列族的唯一主键,其中的列包括名称、电话、地址、 城市、州和邮政编码。虽然所有这些行恰巧都定义了这些相同的列,但实际上你可以 让一行有四列,而另一行有400列,并且两者没有任何交叠,这都没什么问题。

同一行的所有数据必须存放在集群中的同一台机器上,这是Cassandra的 多副本设计的核心要求。这一限制的原因在于每行都有一个关联的行键 值,这个键值决定了放置数据副本的位置。更进一步,每列的大小不能超 过2 GB。在设计数据模型的时候,请时刻注意这些限制。

2.2.1.7 超级列

超级列(super column)是一种特殊的列。两种列都是名/值对,但普通列的值是字节数组,而超级列的值是一个子列的映射。超级列不能存储其他超级列的映射。也 就是说,超级列仅允许使用一层,但是它并不限制列的数量。

超级列的基本数据结构包含它的名字和它存储的列,它的名字和普通的列一样,是 字节数组下图。它存储的列保存为一个映射,键值是列的名字,而值是那 些列。

image-20201030135228370

当使用之前看到的普通列时,Cassaiidm看 起来像是一个四维哈希表。但当引入超级列时,它变成了一个五维哈希:

1
[Keyspace][ColumnFamily][Key][SuperColumn][SubColumn]

要使用超级列,就需要定义列族类型为Super。然后,和用普通列一样,仍然使用 行键值,但这个行键值指向的是一个普通列的列表或是映射的名字了,这里的普通 列有时也叫做子列。
这里有一个名为PointOf Interest的超级列族定义作为例子。在酒店行业中,兴 趣点(point of interest)是酒店附近旅客可能乐于造访的地方,比如公园、博物馆、 动物园或旅游景点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PointOfInterest (SCF){

SCkey: Cambria Suites Hayden {
key: Phoenix Zoo {
phone: 480-555-9999,
desc: They have animals here.
}
key: Spring Training {
phone: 623-333-3333,
desc: Fun for baseball fans.
},// Cambria 行结束
}

SCkey: (UTF8) Waldorf=Astoria {
key: Central Park {
desc: Walk around. It1s pretty.
}
key: Empire State Building {
phone: 212-777-7777,
desc r Great view from the 102nd floor.
}
}
}

Pointof Interest超级列族有两个超级列,每个对应于一’个不问的酒店(Cambria Suites Hayden和Waldorf=Astoria),行键值是不同的兴趣点的名字,比如Phoenix Zoo或是Central Park。每行都有一些列作为描述(desc列),有些行有电话号码, 而有些没有。这和关系型数据库的表不同,关系型数据库的表里每行结构都是一样 的,而列族和超级列里仅仅是一组类似的记录而已。

2.2.1.8 组合键

super cilumn的问题

使用超级列进行建模的时候,需要考虑一个重要的问题:Cassandra不对子列进行索 引,所以,当加载一个超级列进入内存的时候,所有子列都会被载入。

现在你可以使用一个自己设置的组合键绕过这个问题。组合键看起来就像
这些都是在建模时考虑的问题,当你进入到实战阶段的时候会回来检查这些问题。 不过,如果数据模型将来可能会有上千个子列,那么你可能就应该采用其他什么方 法,而不是超级列了。替代方法之一就是组合键。与使用超级列里的子列不同,组 合键使用普通列族里的普通列,只是在键的名字里加入一个自定义的分隔符,在客 户端取出的时候分析这个键值就可以了。
这是一个组合键的例子,这个例子还用在了 Cassandra具体化视图设计模式里,同 时也使用了一种我称之为无值列的Cassandra常用设计模式:

1
2
3
4
5
HotelByCity (CF) Key: city:state {
key: Phoenix:AZ {AZC_043: -, AZS—011: -}
key: San Francisco:CA {CAS—021: -}
key: New York:NY {NYN_042: -}
}

这里发生了三件事情。首先,我们在外部定义了一个称为Hotel的列族,但还可以 再创建一个叫做HotelByCity的列族,存放酒店信息的反范式化数据。我们以不 同的方式重复保存了同样的信息,这和RDBMS里的视图非常类似,因为它允许我 们更快和更直接地进行查询。其次,当要通过城市来查询酒店时(因为很多客户都 是通过城市来搜索酒店的),可以创建一个表来定义新的行键值去搜索,但是,不同 的州有很多同名的城市(想想Springfield)3,所以我们不能直接使用城市的名字作 为行键值,应该加上州的名字。
再后,我们使用另一个称为无值列(valueless column)的模式。我们需要知道的只是城市里有哪些酒店,不需要反范式化更多的东西了。所以,这些列的名字就是它 们的值,而不需要对应的值了。这样,在插入列的时候,只需要保存一个空的字节 数组作为值就可以了。

2.2.1.9 Partition key & Clustering key & Primary Key 作用

在 Cassandra 里面这么多种 key 都有什么作用呢?总结起来主要如下:

  • Partition Key:将数据分散到集群的 node 上.PRIMARY KEY()声明中的第一个参数一般被称作partition key。在Cassandra中,partition key除去唯一地确定某一行数据的作用之外,还起到排序数据及在分布式系统中确定数据的位置的作用(这一点在分布式系统中极其重要)。
  • Primary Key:在 Single column Primary Key 情况下作用和 Partition Key 一样;在 Composite Primary Key 情况下,组合 Partition key 字段决定数据的分发的节点;
  • Clustering Key:决定同一个分区内相同 Partition Key 数据的排序,默认为升序,我们可以在建表语句里面手动设置排序的方式(DESC 或 ASC)

当数据被插入一个Cassandra集群中时,第一个步骤是根据所采用的一致性哈希(consistent hash)算法得出数据partition key所对应的哈希值。这个哈希值被用来确定数据应当被放在集群中的哪一个结点以及数据的冗余备份应当被放在哪几个结点。Cassandra默认采用的哈希算法是Murmur3。一致性哈希算法的特点是可以接受任何输入,但总会输出位于固定范围内的(当前集群的结点有对应的)值。简而言之,某一特定的partition key总会对应集群中的某一特定的结点,而这个partition key对应的数据也总是应当在这个结点上被找到。

对于分布式系统而言,这一点极其重要。其原因是如果对某一特定数据,我们无法确定其所对应的结点位置的话,我们就总是需要遍历集群中的每一个结点才能找到需要的数据。对于小规模的集群,这样的操作可能还可以接受。但对于大规模的分布式数据库而言,这将会严重影响整个系统的效率。(https://blog.csdn.net/Yaokai_AssultMaster/article/details/77439897)

2.3 feature

2.3.1 cql

类似于Sql

  • DDL操作表
  • 支持DML操作Insert、update、Delete
  • 查询数据通过select

2.3.2 主键索引

2.3.2.1 格式

主键索引格式如下:

image-20201102144004382

  • summary.db:
    index文件的索引,对partition做sampling索引,加快查找。
  • index.db:
    索引文件,data文件中每个partition都会有一个索引项,指向该partition在data文件中的偏移,还有对clustering的采样(sampling)索引,可直接指向具体clustering数据偏移。
  • data.db:
    数据文件,完整格式

summary是一个sstable概述,header中记录了最大partition,最小partition,这样我们指定key查找时,可以很方便判断是否要跳过这个这个sstable。当memtable flush本地文件时,不断写data及index文件,同时也会对index.db文件取样,index.db索引项每写入128个,会在summary中记录一下当前采样项目,同时会记录index.db的文件偏移,summary文件目前就是为了加快查找。

index.db文件中的rowIndexEntry其实就是索引data文件中的PartitionKey数据的,一一对应,dataFilePostion记录了该partition在data文件中的偏移。在cassandra中一个partition允许上G大小,所以势必会有很多cluster,如果一次主键查找需要扫描整个上G数据段,效率可想而知。所以cassandra 在rowIndexEntry会写入很多indexInfo,indexInfo是clustering的索引,也是128行clustring数据采样一次,产生一个indexInfo,所以这个indexInfo会包括这段范围内的clustering信息,起始/结束时clustering,还有对应数据文件的偏移。rowIndexEntry数据结构尾部是一串offset,指向前面的一个个indexInfo,因为clustering是变长的,indexInfo也是变长的,使用offset记录偏移加速访问。正因为上述索引体系结构,对于partitionKey+clustering的查找,可以先找到indexInfo,再去读数据文件。

2.3.2.2 key

对于Cassandra来说,一级索引就是Primary Key. 因为查询的时候,可以直接根据Key算出token然后直接获取对应的记录。

就Cassandra而言,最关键的地方在于Key的设计。Cassandra之中一共包含下面5种Key:

  1. Primary Key
  2. Partition Key
  3. Composite Key
  4. Compound Key
  5. Clustering Key
2.3.2.2.1 Primary Key

primary key: 是用来获取某一行的数据, 可以是一列或者多列(复合列 composite)

1
Primary = Partition Key  + [Clustering Key] (Clustering Key 可选)

Clustering keys 包括下面两种情况:

(1) composite key
(2) compound key

1
2
3
4
5
6
7
8
9
10
11
12
-- 一列
create table stackoverflow (
key text PRIMARY KEY,
data text
);
-- 复合列
create table stackoverflow (
key_part_one text,
key_part_two int,
data text,
PRIMARY KEY(key_part_one, key_part_two)
);

在上面复合列的table之中,全称: Composite Primary Key

并且:

1
2
(1) key_part_one  --> partition key
(2) key_part_two --> clustering key

注意: partition key, clustering key 都可以是复合列。 参考下面这个更复杂一些的例子:

1
2
3
4
5
6
7
8
9
10
-- Multiple Partition Keys and Multiple Clustering Keys
create table stackoverflow (
k_part_one text,
k_part_two int,
k_clust_one text,
k_clust_two int,
k_clust_three uuid,
data text,
PRIMARY KEY((k_part_one,k_part_two), k_clust_one, k_clust_two, k_clust_three)
);

Partition Key : Cassandra会对partition key 做一个hash计算,并自己决定将这一条记录放在哪个node

比较好的做法就是尽量的将记录区分开来

Goog Sample: UUID 几乎唯一
Bad Sample: ZipCode/TimeZone, 非常有限
Partition Key的设计,可以完全的借用MySQL的主键。MySQL的主键可以是单一主键,也可以是复合主键。但是主键不能重复,这个在笔者见过的数据库产品之中都是一样的。

补充说明:

在Cassandra之中, 如果对相同的primary key 插入多次,实际上第二次开始类似更新,这个过程就像upsert。Cassandra会给每一行数据一个timestamp,如果有多行数据,Cassandra会取时间最新的数据返回

2.3.2.2.1 Clustering Key

Clustering Key : 主要用于进行Range Query. 并且使用的时候需要按照建表顺序进行提供信息.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 创建表
-- 注意state 这个field
CREATE TABLE users (
mainland text,
state text,
uid int,
name text,
zip int,
PRIMARY KEY ((mainland), state, uid)
)
-- 插入一些值
insert into users (mainland, state, uid, name, zip)
VALUES ( 'northamerica', 'washington', 1, 'john', 98100);
insert into users (mainland, state, uid, name, zip)
VALUES ( 'northamerica', 'texas', 2, 'lukas', 75000);
insert into users (mainland, state, uid, name, zip)
VALUES ( 'northamerica', 'delaware', 3, 'henry', 19904);
insert into users (mainland, state, uid, name, zip)
VALUES ( 'northamerica', 'delaware', 4, 'dawson', 19910);
insert into users (mainland, state, uid, name, zip)
VALUES ( 'centraleurope', 'italy', 5, 'fabio', 20150);
insert into users (mainland, state, uid, name, zip)
VALUES ( 'southamerica', 'argentina', 6, 'alex', 10840);

有效的查询

1
2
select * from users1 where mainland = 'northamerica';
select * from users where mainland = 'northamerica' and state > 'ca' and state < 'ny';

查询结果:

1
2
3
4
mainland     | state    | uid | name   | zip
-------------+----------+-----+--------+-------
northamerica | delaware | 3 | henry | 19904
northamerica | delaware | 4 | dawson | 19910

无效的查询

1
2
-- 没有提供state 信息
select * from users where mainland = 'northamerica' and uid < 5;

原因很简单,参考上一篇文章提到的数据模型:

1
Map>

Cassandra 整体数据可以理解成一个巨大的嵌套的Map。 只能按顺序一层一层的深入,不能跳过中间某一层。也就是说当PRIMARY KEY ((mainland), state, uid)简历主键(主键就是一级索引)的时候,只能根据mainland、mainlan and state、mainlan and state and uid这三种类型查询。

这里有个问题声明一下,因为partition key可以是组合键所以,我们如果查询就必须要组合键。ex:PRIMARY KEY((key_part_one, key_part_two),test) 我们仅仅可以使用key_part_one and key_part_two、key_part_one and key_part_two and test 这两组查询方式。

注意:

如果我强制不遵循一层一层的深入怎么做呢?如果一定要执行:在cql 之中增加ALLOW FILTERING

1
select * from users where mainland = 'northamerica' and uid < 5 ALLOW FILTERING;

ALLOW FILTERING不建议使用太多,并且Cassandra默认不允许: 会触发数据过滤(需要扫描的数据比较多)

1
2
3
不过 CASSANDRA/SCYLLA 并没有对使用Allow Filtering,抱持全面否定的觀點,原因在於說假設總資料量有1萬筆,你指定的條件符合而撈出的資料會有9000多筆以上,這個全撈後再做篩選的耗損其實不大,這樣子使用也沒什麼不好。

反過來說,假設總資料量有1萬筆,最後撈出符合條件的只有寥寥100筆,那麼用這樣的查詢方式,實在是不好。

总结一下:

Cassandra之中的存储,是2-level nested Map

Partition Key –> Custering Key –> Data
partition key: eq and in
clustering key: < <= = >= > in

2.3.3 二级索引,SASI

二级索引,作为辅助索引就是为了找到一级索引。然后再通过一级索引找到真正的值.

2.3.3.1 二级索引原理

Cassandra之中的索引的实现相对MySQL的索引来说就要简单粗暴很多了。他实际上是 自动偷偷新创建了一张表格,同时将原始表格之中的索引字段作为新索引表的Primary Key!并且存储的值为原始数据的Primary Key。

因此,什么样的数据、字段适合做二级索引,也就很清楚了。

我们翻译一下官方的解释:

(1)、什么时候不适合使用索引:

  1. High-cardinality 列。 相当于这一列的值很多很多的时候。
    1. 因为查询了很多结果只能取出一小部分数据集
  2. counter 类型的列
  3. 删除、更新太过频繁的列
    1. Cassandra删除、更新数据都会给老数据设置一个Tombstone(墓碑)。当Tombstone的数据查过10K的时候,就会报错
    2. 再加上需要同步的更新索引表,Tombstone本身的标记也会很消耗资源
  4. 数据集值太多
    1. 原文英文没怎么看懂,这是笔者自己的理解。
    2. 再次强调一下:二级索引里面存储的是原始数据的Primary Key。因此如果一次查询的数据过多就会遇到超时异常

(2)、总结

就是索引对应的数据值不能太多也不能太少。 太多就超时,太少就浪费资源(需要创建太多的Primary Key)。 同时索引的列还要稳定,不能频繁的删除或者更新~

所以个人认为, 能不用Cassandra之中的索引就不要用 ,还不如自己显示的创建一个

(3)、使用索引

索引的使用非常简单,参考下面的代码以及相应的注释:

1
2
3
4
5
6
-- 创建索引 
CREATE INDEX artist_names ON playlists( artist );
-- 查询
SELECT * FROM playlists WHERE artist = 'Fu Manchu';
-- 优化查询
SELECT * FROM playlists WHERE id = 62c36092-82a1-3a00-93d1-46196ee77204 AND artist = 'Fu Manchu';

2.3.3.2 SASI

传统二级索引只能进行简单的索引查询,SASI是对其增强,该索引可以在Cassandra中进行全文搜索(自Cassandra 3.4以来引入,但因相关重大bug的修复,我建议至少使用Cassandra 3.5以上)。

SASISSTable-Attached Secondary Index缩写,例如SASI索引文件生命周期和对应SSTable是相同。SASI并不是另一种全新Cassandra二级索引接口的实现,它引入了一个新的想法:让索引文件遵循的SSTable的生命周期。这意味着每当在磁盘上创建SSTable时,也会创建相应的SASI索引文件。什么时候创建SSTables?

  1. 正常flush时
  2. 在压实期间
  3. 在流操作期间(节点加入或下架)

为了启用这种新架构,必须修改Cassandra源代码以引入新SSTableFlushObserver类,该新类的目标是拦截SSTable刷新并生成相应的SASI索引文件。

A)SASI语法和用法
C)SASI生命周期
D)写路径
E)磁盘上的数据格式和布局
F)读取路径
G)磁盘空间使用
H)一些性能Benchmarks
I)SASI与搜索引擎

有人想将SASI与经典搜索引擎(例如ElasticSearchSolrDatastax Enterprise Search)进行比较。这种比较非常简单。尽管具有便利性,并且SASI已与Cassandra和CQL 紧密集成,但与真正的搜索引擎相比,它具有许多缺点。

  • SASI在磁盘上需要2次传递才能获取数据:1次传递以读取索引文件,而1次传递为常规的Cassandra读取路径,而搜索引擎以单次传递的方式检索结果(DSE Search也具有_singlePass_选项)。根据物理学定律,即使我们改进了Cassandra中的顺序读取路径,SASI始终会变慢
  • 尽管SASI允许切词和CONTAINS模式进行全文搜索,但对匹配的term没有评分
  • SASI以token范围顺序返回结果,从用户角度来看,可以将其视为随机顺序。即使使用LIMIT子句,也不可能要求对结果进行整体排序。搜索引擎没有此限制
  • 最后但并非最不重要的一点是,无法使用SASI执行聚合(或faceting)。

话虽如此,如果您不需要排序,分组或计分,那么SASI是搜索引擎替代绝佳选择。
但是,我永远不会想到有一天可以在Cassandra用_LIKE '%term%'_谓词,因此从这个角度来看,它已经比过去的局限性有了很大的改进。

J)SASI权衡
1
https://developer.aliyun.com/article/722259

2.3.4 物化视图

我们来看下传统二级索引的缺点:

image-20201102155531096

本地索引有个特性那就是索引后的数据在每个节点里都有,比如说:索引列建立在国家这个列,我在D节点有个索引列,如上有US,同时B节点里也有US这个索引,那这样通过索引查找数据,就需要在集群里每个节点要去发read query,那这样就相当于发多轮rpc,这样速度就降下来了。物化视图就是解决这个问题的 。物化视图可以简单的理解为全局表,也是遵从Cassandra的parttition分布策略,有个全局表也是根据parttion key分布到其它节点。

ex: user表根据国家创建视图user_by_country

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CREATE TABLE user(
id int PRIMARY KEY,
login text,
firstname text,
lastname text,
country text,
gender int
);

CREATE MATERIALIZED VIEW user_by_country
AS SELECT * //denormalize ALL columns
FROM user
WHERE country IS NOT NULL AND id IS NOT NULL
PRIMARY KEY(country, id);

INSERT INTO user(id,login,firstname,lastname,country) VALUES(1, 'jdoe', 'John', 'DOE', 'US');
INSERT INTO user(id,login,firstname,lastname,country) VALUES(2, 'hsue', 'Helen', 'SUE', 'US');
INSERT INTO user(id,login,firstname,lastname,country) VALUES(3, 'rsmith', 'Richard', 'SMITH', 'UK');
INSERT INTO user(id,login,firstname,lastname,country) VALUES(4, 'doanduyhai', 'DuyHai', 'DOAN', 'FR');

SELECT * FROM user_by_country;

country | id | firstname | lastname | login
---------+----+-----------+----------+------------
FR | 4 | DuyHai | DOAN | doanduyhai
US | 1 | John | DOE | jdoe
US | 2 | Helen | SUE | hsue
UK | 3 | Richard | SMITH | rsmith

SELECT * FROM user_by_country WHERE country='US';

country | id | firstname | lastname | login
---------+----+-----------+----------+-------
US | 1 | John | DOE | jdoe
US | 2 | Helen | SUE | hsue

解决了二级索引中多次rpc开销问题,当通过上图country=’US’去查的话他肯定散落到后端某个具体的node,只要对这个node发起single parttition query这个命令就可以得到结果了,不需要跟其它node打交道了。

原理实现

1
https://developer.aliyun.com/article/705400

选择使用物化视图还是二级SASI

如果您总是要使用分区键,我建议您使用二级索引。
当您不知道分区键时,实例化视图会更好

1
https://www.coder.work/article/6688880

2.3.5 UDF/UDA

image-20201102163744962

可以认为自定义function,类似java方法签名,指定LANGUAGE可以是java或者js

原理:

image-20201102164047604

1
2
https://www.bilibili.com/video/BV1WJ411g7NK?from=search&seid=1325769057914955391
// 35分钟

2.3.6 轻量级事务(CAS)

image-20201102164124084

每个node都可以并发写,所以引入cas,名称很好听轻量级事务但是它并不轻量而且和事务没什么关系,其实就是标准的compare and swap先比较再交换。语法里会指定关键字,ex: firstname=’Roxxane’为旧值的时候可以更新为‘Roxane’,实现是基于basic-paxos实现的.未来优化为Epaxos(优化会减少rpc,延时大大减少)

2.3.7 集合类型,counter

map,list,set三种集合类型。

1
2
3
4
5
6
7
create table ks.user(
id int primary key,
addr map>>,
complex map>>,
listcolumn list,
setcolumn set
)

img

上图解释了数据的存储方式:这个是一个复杂的两层cell

对于super Column key1:如果是set 就是其column名字setcolumn

subcolumn key1:set就是其 value,map就是key

column value1: 空,map是value

这里不支持太多层嵌套,多以这里使用frozen关键字,表示冻结,不会对里面元素做一些修改。

2.3.8 CDC

新一代数据库都有这个东西。

CDC(Change data capture)是Cassandra提供的一种用于捕获和归档数据写入操作的机制,这个功能在3.8以上版本支持。当对一个表设置了“cdc=true”属性之后,包含有这个表的数据的CommitLog在丢弃时会被移动到指定的目录中,用户可以自己编写程序消费(解析并删除)这些日志,实现诸如增量数据导出、备份等功能。本文介绍CDC功能的使用并分析其特点。

image-20201102170443541

1
https://developer.aliyun.com/article/725386

2.3.9 TTL

系统设置的过期时间。以秒为单位

image-20201102170635020

tetamp:解决冲突的;ttl:过期时间; expires_at:写入时间+ttl。没过期就返还给我们。

2.3.10 Batch

要么都成功,要不都失败。

image-20201102171019088

2.3.11 Online schema change

mysql改schema会阻塞写入数据。

2.3 术语和约定

  • 术语“Column Name” 和 “Column Key”被认为是一样的。同样的,“Super Column Name” 和 “Super Column Key”也认为是相同的。
  • 下图表示一个 Column Family (简称 CF) 中的一个 rowimg
  • 下图表示一个 Super Column Family (简称 SCF) 中的一个 rowimg
  • 下图表示一个 Column Family 中一个 row,它包含 Composite Columns。Composite Columns 的属性通过分隔符’|’连接。请注意,这里看到的只是数据的表现形式,Cassandra 内置了 Composite Column,它是一个对象,并不是使用’|’作为属性分隔符的字符串。(顺便说下,本文不要求你掌握 Super Column 和 Composite Column 方面知识。)img

基于上面的内容,让我们开始第一个实践吧!

不要把 Cassandra model想象成关系型数据库table

取而代之,应该把它想象成事一个有序的 map 结构。

对于一个新手来说,下面关系型数据库术语常常被对应到 Cassandra 模型

img

这种对比可以帮助我们从关系型数据库转换到非关系型数据库。但是当设计 Cassandra column famiy 的时候请不要这样去类比。取而代之,考虑它是一个 map 中嵌入另一个 map:外部 map 的 key 为 row key,内部 map 的 key 为 column key,两个 map 的 key 都是有序的。如下:

1
SortedMap>

why?

将 column family 想象成嵌套的并排序的 map 比关系型数据库 table 描述的更为准确,它将帮助你正确的进行 Cassandra 模型设计。

img

How?

  • Map 可以进行高效查询,同时排序的特性可以进行高效 column 扫描。在 Cassandra 中,我们可以使用 row key 和 column key 做高效查找和范围扫描
  • Column key 的数量是很庞大的(译者注:目前译者所使用的 Cassandra1.2.5 版本,每个 row 支持最多 20 亿个 columns)。换句话说你,你可以拥有一个 wide rows。
  • Column key 自身可以存储值,即你可以拥有一个没有值的 column。

如果集群使用 Order Preserving Partitioner (OOP) 策略进行数据存储, 就可以对 row key 进行范围查询。但是 OOP 大多数情况都不推荐使用(译者注:将 rowkey 按照顺序存储到节点上,如果分区不均匀,将导致数据读写不均衡),所以你可以认为外部的 map 是不排序的,如下:

1
Map>

上面提到的”Super Column”,认为它们是一组 column,这样的话,两级嵌套 map 就会像下面展示的一样变为三级嵌套 map:

1
Map>>

参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
---BigTable
https://blog.csdn.net/qq_39521554/article/details/88371744
https://lianhaimiao.github.io/2018/03/19/%E7%90%86%E8%A7%A3BigTable/
https://www.cnblogs.com/xybaby/p/9096748.html
https://zhuanlan.zhihu.com/p/33990921
https://zhuanlan.zhihu.com/p/73655714
---wide column
https://stackoverflow.com/questions/62010368/what-exactly-is-a-wide-column-store
https://www.dataversity.net/wide-column-database/#
https://blog.csdn.net/SpanningWings/article/details/104337992
---gossip
https://www.iteblog.com/archives/2505.html
https://blog.51cto.com/eric100/1759950
https://blog.51cto.com/supercharles888/864495
---哈希一致性
https://blog.wangqi.love/articles/Java/%E4%B8%80%E8%87%B4%E6%80%A7%E5%93%88%E5%B8%8C%E7%AE%97%E6%B3%95.html#more
---cassandra
https://www.infoq.cn/article/best-practice-of-cassandra-data-model-design
https://www.infoq.cn/article/best-practices-cassandra-data-model-design-part2
https://blog.csdn.net/Yaokai_AssultMaster/article/details/77439897
https://www.bilibili.com/video/BV1WJ411g7NK?from=search&seid=1325769057914955391
-- key
https://www.flyml.net/2016/09/05/cassandra-tutorial-right-way-to-use-key/
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论