概述
CockroachDB是一个分布式关系型数据库,主要设计目标是可扩展,强一致和高可靠。
在无人干预情况下, 能以极短的中断时间容忍磁盘、主机、机架甚至整个数据中心的故障。
采用完全去中心化架构, 集群中各个节点的地位完全对等。
所有功能封装在一个二进制文件中, 可以做到尽量不依赖盘配置文件直接部署。
对外提供标准SQL接口,集群中任意节点都可以作为接入节点处理用户的SQL请求。
接入节点把SQL请求转换为KV操作,并且在必要时将该操作发送至其它节点进行处理,完成后将结果返回给客户端。
CockroachDB底层将数据组织成有序的Key-Value对形成一个KV map,其中Key和Value均为字节串。
KV map逻辑上按照范围被切分成大量的Key空间,每个Key空间称为Range。
每个Range数据由本地KV存储引擎(RocksDB,LevelDB的变体)存储。
每个Range被复制多份分布到多个CockroachDB节点上,Range副本数量可配置。
每个Range默认大小为64M,合理的Range大小有利于加速节点故障恢复和扩容,及均衡读写负载。
Range大小应该根据系统压力进行设置,以便管理更多Range。
支持水平扩展
添加更多节点可以提升整个集群的存储容量, 理论上最大可以支撑4EB的数据存储
客户端的查询请求可以发送到集群任意节点, 且每个查询可独立并发执行, 集群的吞吐能力可以随着节点数的增加线性提升。
查询以分布式任务的方式在各个数据节点并发执行,可以通过增加节点数来提升单个查询的性能。
支持强一致性
Range的多个副本之间使用Raft一致性协议, 所有一致性状态都存储在RocksDB中。
对同一个Range内数据的单一或批量修改, 由Raft保证Range操作的ACID语义。
涉及多个Range的操作, CockroachDB使用高效的无锁分布式事务保障ACID语义。
支持高可用
将Range副本分布在一个数据中心, 可以确保低延迟复制, 同时能容忍磁盘或机器故障。
如果将副本分布在不同机架, 即使某些网络交换机故障, CockroachDB仍可提供服务。
Range副本可以跨数据中心和跨地域分布, 以应对来自数据中心电源中断或网络中断, 以及区域电力故障等问题。
隔离级别
基于历史快照时间和当前时间, 提供外部一致的无锁读写。
快照隔离(SI) [Snapshot Ioslation]
SI提供无锁读写, 但是存在写偏序问题(write skew)
串行快照隔离(SSI) [Serializable Snapshot Isolation]
SSI消除了写偏序, 但在竞争激烈的系统中会存在性能问题
SSI是默认的隔离级别, 用户须根据实际性能情况, 选择合适的隔离级别。CockroachDB只提供有限的linearizability(严格的顺序一致性)。
架构
架构图
采用分层架构
SQL层
CockroachDB支持标准SQL, 当CockroachDB集群的某个节点收到SQL请求时,会经过SQL解析、SQL执行计划生成、SQL执行等重要步骤。
CockroachDB兼容PostgreSQL协议,对于报文的封装和解析完全按照PostgreSQL的方式进行,所以用户可以直接使用PostgreSQL的客户端访问CockroachDB。
CockroachDB对于用户的SQL语句按照PostgreSQL的语法进行解析,解析完成后生成抽象语法树(AST)
CockroachDB 会根据不同的语法树生成对应的执行计划。目前执行计划基本是基于规则的方式来生成的。
对于OLAP的SQL Statement, CockroachDB会将逻辑计划转化为物理执行计划,即通过分布式任务的方式进行并行执行。
当执行计划生成完毕后,CockroachDB会按照约定的方式开始执行,此时CockroachDB将调用事务性的KV接口。执行完成后通过协议层将执行结果返回给客户端。
分布式KV存储
负责Range路由寻址,提供统一的key-value存储。
可以由任意数量的CockroachDB物理节点组成,每个节点包含一个或多个Store(通常一个Store独占一块磁盘)
每个Store包含多个Range,Range为KV层数据管理的最小单元,每个Range的多个副本之间使用Raft协议进行同步
Range示意图
这个其实可以类比下hdfs的分片和elasticSearch的分片, 思想上都是相似的。
节点和Range可以根据不同的物理网络拓扑结构进行编排,从而在可靠性和性能之间折衷。
假设一个Range包含三个副本,每个副本可以位于不同的位置:
如果副本分布于同一台服务器上的多块磁盘,可以容忍单块磁盘故障。
如果副本分布于同一机架上的不同服务器,可以容忍单台服务器故障。
如果副本分布于同一个数据中心不同机架,可以容忍单个机架电源和网络故障。
如果副本分布于不同数据中心,可以容忍大规模网络中断或断电。
关键字
CockroachDB key可以是任意字节数组
key有两种类型:系统表key和用户表key
系统表key被CockroachDB用于内部数据结构和元数据。用户表key包含用户表数据(以及索引数据)
系统表key和用户表key通过前缀区分,并保证系统表key始终小于用户表key
系统表key有以下几种类型:
Global key存储集群级别的数据,例如“meta1”和“meta2” ,以及系统级别的key,例如节点和Store 的ID标识。
Store local key用于标识该Store的本地元数据(例如,StoreIdent结构),该部分元数据与所处Store生命周期相关,因此无需复制,即不会通过Raft同步到其它Store。
Range local key存储Range的元数据,并与Global key(实际存储层的全局Key)相关联。Range local key由一个特殊前缀,加Global key及一个特殊后缀组成。例如,事务记录是Range local key,形如: x01ktxn-。
Replicated Range ID local key存储Range元数据(RangeDescriptor),该元数据包含Range所有副本的元信息,这些元数据的变更会通过Raft同步。例如Range lease状态和事务的abort缓存记录。
Unreplicated Range ID local key存储该Range副本的元数据,例如Raft状态机的状态信息以及持久化后的Raft日志。
用户表key用于存储所有非系统数据
多版本数据
CockroachDB维护了数据的历史版本,版本之间通过事务的提交时间戳区分。
指定快照时间可以读取此时间戳之前的最新版本数据。
所有版本都有一个最小有效期,当系统进行compaction时,过期的版本数据会被系统回收。
为了防止长时间数据扫描(例如MapReduce)中历史数据被清理,用户也可自行指定数据有效期。
通过RocksDB存储每个key的提交时间戳和GC有效期,支持多版本数据。
无锁分布式事务
CockroachDB提供无锁分布式事务
支持的两种事务隔离界别
快照隔离级别(SI)
隔离级别实现简单,性能较好, 但是存在write skew 问题。
串行化快照隔离级别(SSI)
实现上稍微复杂一些, 但仍然能保证较高性能 (读写冲突严重的情况下稍弱), 但是不存在write skew问题。
默认使用SSI隔离级别
在对性能要求较高,并且没有write skew的情况下可使用SI隔离级别。
在冲突较少的情况下,SSI和SI性能相当,不需要加锁或额外写操作。
在冲突激烈的情况下,SSI仍然不需要加锁,但是会有更多事务被终止。
在任何长事务场景中,SI和SSI都能防止事务饿死。
SI和SSI之间的核心区别在于事务提交时,SI允许事务的候选时间戳变大,而SSI不允许。
SI和SSI都要求缓存该Range上发生的读操作结果
如果写操作时间戳比最近一次读操作时间戳要小, 则写操作失败。
每个Range都有一个缓存 (timestamp cache), 保存该Range中key被读取的最新时间戳。
读操作会更新相应的timestamp cache, 部分写操作 (例如Range删除) 也会更新timestamp cache。
timestamp cache中最老时间戳会被优先剔除。
每一个CockroachDB事务开始时都会分配一个随机优先级和一个“候选时间戳”。
候选时间戳是接收事务请求时节点分配的本地当前时间戳 (HLC), 作为事务提交的临时时间戳。
如果没有事务冲突,在事务完成所有操作后,该时间戳会成为事务的最终提交时间戳。
在跨多个节点的分布式事务执行过程中, 候选时间戳可能会变大, 但不会回退。