ScyllaDB介绍
ScyllaDB是用C++重写的Cassandra,其官网宣称其每节点每秒可处理100万TPS。ScyllaDB完全兼容Apache Cassandra,拥有比Cassandra多10X倍的吞吐量,并降低了延迟。ScyllaDB是性能优异的NoSQL列存储数据库。
ScyllaDB在垃圾收集或者Compaction的时候不需要暂停(但是通过压测和Longevity测试发现Compaction的过程中对性能还是有很大影响)。
ScyllaDB在常规生产负载的时候可以添加或删除节点(通过nodetool来同步数据)。
ScyllaDB是一个P2P的分布式系统,集群中各节点之间相互平等。其数据分布于集群中的各节点,各节点之间每秒钟交换一次信息。
其每个节点使用Commit Log提交日志捕获写操作来保持数据的正确性。数据首先被写入MemTable(内存中的数据结构)中。当MemTable满后数据被写入SSTable(存储在硬盘上的数据文件)中。
用户可以使用类似于SQL的CQL来查询数据。用户可以链接至集群中的任意节点。
在集群中,一个Keyspace代表关系数据库中的一个数据库。一个Keyspace中可以包含多个表。
ScyllaDB架构分析
ScyllaDB在分布式方面参考了Amazon Dynamo,在数据建模方面参考了Google BigTable。下面先介绍几个概念:
KeyWord | Explain |
Gossip | 点对点通信协议,用以ScyllaDB集群中节点交换位置和状态信息。 |
Partitioner | 决定如何在集群中的节点间分发数据,即在哪个节点放置数据的第一个replica。 |
Replication placement strategy | 决定在哪些节点放置每行数据的其他replica。ScyllaDB在集群中的多个节点存储数据的多份拷贝(replicas)来确保可靠和容错。 |
Snitch | 定义了复制策略用来放置replicas和路由请求所使用的拓扑信息。 |
Virtual Nodes | 虚拟节点,指定数据与物理节点的所属关系。 |
Token Ring | 令牌环 |
下面来进行具体分析:
1. Gossip
a. 节点通信
ScyllaDB使用点对点的通讯协议gossip来在集群中的节点间交换位置和状态信息。
Gossip进程每秒运行一次,与至多三个其他节点交换信息,这样所有节点可以很快了解集群中的其他节点信息。
Gossip协议的具体表现形式就是配置文件中的seeds种子节点,一个注意点是同一个集群中所有节点的种子节点应该一致。如果种子节点不一致,有时候会出现集群分裂,即会出现两个集群。一般来说,首先需要启动种子节点,以便尽早发现集群中的其他节点。
集群中的每个节点都和其他节点交换信息,由于随机和概率,一定会穷举出集群中所有的节点。同时,每个节点都会保存集群中所有其他节点的信息,这样随便连接到哪一个节点都能知道集群中的其他所有节点。比如,CQL随便连接集群中的哪一个节点都能获取集群中所有节点的状态,也就是说任何一个节点关于集群中的所有节点信息都应该是一致的。
b. 失败检测与恢复
Gossip可检测其他节点是否正常以避免将请求路由至不可达或性能差的节点(后者需配置为dynamic snitch方可)。
对于失败的节点,其他节点会通过Gossip定期与之联系以查看是否恢复而非简单将之移除。若需强制添加或删除集群中的节点,需要使用nodetool工具来操作。
一旦某节点被标记为失败,其错过的写操作会由其他replicas存储一段时间(需开启Hinted handoff,若节点失败的时间超过了max_hint_window_in_ms,错过的写不再被存储)。Down掉的节点经过一段时间恢复以后需要执行repair操作,一般在所有的节点运行nodetool repair以确保数据的一致。
Dynamic snitch特性:查询请求路由到某个节点,如果这个节点Down掉或者相应慢,则应该能够查询其他节点上的副本。
删除节点:节点失败后,仍然在集群中。通过remove node可以将节点从集群中下线。区别就是status如果不存在了就说明下线了。DN则仍在集群中。
失败节点数据:数据无法存储到失败的节点,所以会有其他节点暂时保存,等他恢复以后,再将错过的写补充上去。
2. 数据存储路由
a. 一致性hash
如上图所示:key的范围是0~2^32形成的一个环,叫做hash空间环,即hash的值空间。对集群的服务器(比如ip地址)进行hash,都能确定其在环空间上的位置。
定位数据访问到相应服务器上的算法:将数据key使用相同的hash函数计算出hash值,通常根据此hash值来确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其定位到的服务器。
由于一致性哈希算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜的问题,所以引入了虚拟节点。如下图所示:
把每台server分成v个虚拟节点,再把所有虚拟节点(n*v)随机分配到一致性hash的圆环上,这样所有的用户从自己圆环上的位置顺时针往下取到第一个vnode就是自己所属节点,当此节点存在故障时,再顺时针取下一个节点作为替代节点。
在ScyllaDB中,每个节点分配了一个单独的token代表环中的一个位置。每个节点存储将row key映射为hash值以后落在该节点应当承担的唯一的一段连续的hash值范围内的数据。每个节点也包含来自其他节点的row的副本。
而使用虚拟节点允许每个节点拥有多个较小的不连续的hash值范围。在ScyllaDB集群中的节点就使用了虚拟节点,虚拟节点随机选择且不连续。数据的存放位置也由row key映射而得的hash值确定,但是是落在更小的分区范围内。
使用虚拟节点的好处是:
1)无需为每个节点计算,分配token。
2)添加移除节点后无需重新平衡集群负载。
3)重建死掉的节点更快。
4)改善了在同一集群使用异种机器。
3. Partitioner
在ScyllaDB中,table的每行由唯一的PRIMARY KEY标识。partitioner实际上是Hash函数用以计算PRIMARY KEY的token。ScyllaDB依据这个token值在集群中放置对应的行。
3. 数据复制
ScyllaDB当前由两种可用的复制策略:
SimpleStrategy:仅用于单数据中心,将第一个replica放在由partitioner确定的节点中,其余的replicas放在上述节点顺时针方向的后续节点中。
NetworkTopologyStrategy:可用于较复杂的多数据中心。可以指定在每个数据中心分别存储多少份replicas。
复制策略在创建keyspace时指定,如:
1. CREATE KEYSPACE Excelsior WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 3};
2. CREATE KEYSPACE Excalibur WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'dc1':3, 'dc2': 2};
其中dc1,dc2这些数据中心名称要与snitch中配置的名称一致。上面的拓扑策略表示在dc1上配置3个副本,在dc2上配置2个副本。
4. 写操作
协调者(coordinator)将write请求发送到拥有对应row的所有replica节点,只要节点可用便获取并执行写请求。
写一致性级别(Write Consistency Level)确定要有多少个replica节点必须返回成功的确认信息。成功意味着数据被正确写入了Commit Log和MemTable。
假设:R = Nodes Read, W = Node Written, N = Replication Factor, Q = QUORUM = N / 2 + 1。
ANY: 至少写成功一个节点,即使是一个Hinted Handoff。
ONE:至少成功写入一个复制节点(replica node)
QUORUM:至少成功写入Q个复制节点。
LOCAL_QUORUM:至少在coordinator node所在的当前DC成功写入Q个复制节点。
EACH_QUORUM:在每个DC成功写入Q个复制节点。
ALL:成功写入集群中的每个复制节点。
写入流程如下图所示:
先将数据写进内存中的数据结构Memtable,同时追加到磁盘中的Commit Log中。
Memtable内容超出指定容量后会被放进将被刷入磁盘的队列中。
若将被刷入磁盘的数据超出了队列长度,会锁定写,并将内存数据刷金磁盘中的SSTable,之后Commit Log被清空。
5. 读操作
读请求有两种:
1)直接读请求(Direct Read)
2)后台读修复请求(RR:Read Repair)
与直接读请求联系的replica数据由一致性级别决定(上图中请求了R1和R3两个节点)。后台读修复请求被发送到没有收到直接读请求的replica(R2),以确保请求的row在所有的replica上一致。
协调者首先与一致性级别确定的所有replica联系,被联系的节点返回请求的数据。若多个节点被联系,则来自各replica的row会在内存中作比较,若不一致,则协调者使用含最新数据的replica向client返回结果。
读流程如下图所示:
ScyllaDB的数据可能在Memtable中,也可能在多个SSTable中,每个地方都可能有在某个Column对应的值。读取的步骤如下所示:
1)判断row cache中是否有需要读取的数据,如果有则直接返回。
2)从Memtable中获取数据。
3)从多个SSTable中获取相关的数据。
首先检查BloomFilter,每个SSTable都有一个BloomFilter,用以在任何磁盘IO前检查请求PK对应的数据在SSTable中是否存在。
BloomFilter可能误判但不会漏判:判断存在,但实际上可能不存在;若判断不存在,则一定不存在。如不存在,流程就不会访问这个SSTable。若数据很可能存在,则检查Partitionkey cache(索引的缓存),之后根据索引条目是否找到二执行不同的步骤:
若在缓存中找到:
从compression offset map中查找拥有对应数据的压缩块。
从磁盘中取出压缩的数据,返回结果集。
没有在索引缓存中:
搜索Partition Summary(partition index的样本集)确定index条目在磁盘中的近似位置。
从磁盘中SSTable内取出index条目。
从compression offset map中查找拥有对应数据的压缩块。
从磁盘中去除压缩的数据,返回结果集。
将从Memtable和SSTable中的数据进行合并后返回给客户端。
6. ScyllaDB高性能的改进
ScyllaDB是基于C异步分片变成框架Seastar重写的。
Seastar是一个完全分片(share-nothing)的设计:每个logic core一个thread,每个core有自己的资源:CPU,network,Disk I/O,memory。多个core之间没有资源的竞争,随着core数量的增加,扩展性和性能也随之提升。对于多个core之间的通信,采用point-to-point queue发送和接收异步消息。所有的core之间没有数据共享,没有锁,没有cache lines频繁的miss。与此同时,seastar也是一个异步变成框架。
由下图可以看出,ScyllaDB的性能比Cassandra提升巨大:
ScyllaDB的CQL
CQL可以看作是SQL的一个子集,其部分特点如下:
1)他不支持表之间的join和sub query。
2)所有的查询条件必须在PRIMARY KEY上。
3)假设PRIMARY KEY(a, b, c),如果c的查询条件为非等,则a,b的查询条件必须为等于。
4)PRIMARY KEY不能被修改。
5)ScyllaDB表中的每条记录都可以设置TTL。