这个系列共三篇译文:
TiDB 官方设计文档翻译(一)
TiDB 官方设计文档翻译(二)
TiDB 官方设计文档翻译(三)
原文:
https://pingcap.github.io/blog/2016/10/17/how-we-build-tidb/
5 如何开发
在本节中,将介绍TiKV和TiDB的架构和核心技术。
5.1 架构
关于TiKV架构,让我们从下往上看。
- 最底层,RocksDB。
- 上一层,Raft KV,是一个分布式层。
- MVCC,Multiversion并发控制。 我相信很多人都熟悉MVCC。 TiKV是一个多版本的数据库。 MVCC使我们能够支持无锁读取和ACID事务。
- 事务层:事务模型的灵感来自Google的Percolator。 它主要是一个优化的两阶段提交协议。 此模型依靠时间戳分配器为每个事务分配单调递增时间戳,因此可以检测冲突。 稍后会详细说明。
- KV API:它是一组编程接口,并允许开发人员put或get数据。
- Placement 驱动程序:Placement 驱动程序是非常重要的部分,它有助于实现地理复制,水平伸缩和分布式事务。 它是集群的大脑。
关于TiDB架构:
- MySQL客户端:顶层是一系列MySQL客户端。 这些客户端向下一层发送请求。 你仍然可以使用任何你已经熟悉的MySQL驱动程序。
- 负载均衡器:这是一个可选层。 如HAProxy或LVS。
- TiDB服务器:它是无状态的,客户端可以连接到任何TiDB服务器。 在TiDB服务器中,顶层是MySQL协议,它提供MySQL协议支持; 下一层是SQL优化器,用于将MySQL请求翻译为TiDB SQL。
- 底层是KV API和分布式SQL API(DistSQL API)。 如果底层存储引擎支持协处理器,TiDB SQL Layer将使用DistSQL API,这比KV API高效得多。 TiDB支持可插拔存储引擎。 我们建议TiKV作为默认存储引擎。
5.2 TiKV核心技术
让我们来看看TiKV核心技术。
我们构建TiKV作为分布式键值层以存储数据。
5.2.1 TiKV软件堆栈
让我们来看看软件堆栈。
首先,我们可以看到有一个客户端连接到TiKV。 我们还有几个TiKV节点。 在每个节点内,有一个store存储在物理磁盘。 在每个store里面有很多region。 region是数据移动的基本单位,根据Raft协议进行复制。 每个Region都复制到几个节点。 Raft Group由一个Region的副本组成。 Region更像是一个逻辑概念,在单个store中,许多Region可能共享相同的Rocksdb实例。
5.2.2 Placement Driver
关于Placement Driver,这个概念来自Google Spanner的原始文件。 它提供了整个集群的信息。 它有以下职责:
- 存储元数据:客户端有每个Region的位置信息的缓存。
- 维护复制约束,默认情况下为3个副本。
- 处理数据移动从而实现。 当Placement Driver注意到负载太高时,它将重新平衡数据或使用Raft转移领导权。
感谢Raft,Placement Driver也是一个高可用的集群。
5.2.3 Raft
在TiKV中,我们使用Raft进行缩放和复制。 我们有多个Raft组。 工作负载分布在多个region。 在一个大集群中可能有数百万个region。 一旦region太大,它将被分裂成两个较小的区域,就像细胞分裂。
下面我将展示横向扩容的过程。
如上图所示,我们有4个节点,即节点A,节点B,节点C和节点D。还有3个Region,Region1,Region2和Region3。节点A上有3个Region。
为了平衡数据,需要添加一个新节点,节点E。第一步是将领导权从节点A上的Region1的副本传输到节点B上的副本。
第二步,给节点E添加Region1的副本
第三步,从节点A中删除Region1的副本。
现在数据是平衡的,集群从4个节点扩展到5个节点。
这就是TiKV如何扩容。 让我们看看它如何处理自动故障转移。
5.2.4 MVCC
- 每个事务在此事务的开始时间看到数据库的快照。 在事务提交之前,其他事务将不会看到此事务所做的任何更改。
- 数据使用以下格式的版本标记:Key_version:value。
- MVCC还确保无锁快照读取。
5.2.5 事务
这些是事务API。 作为一个程序员,我想编写如下代码:
txn := store.Begin() // start a transaction
txn.Set([]byte("key1"), []byte("value1"))
txn.Set([]byte("key2"), []byte("value2"))
err = txn.Commit() // commit transaction
if err != nil {
txn.Rollback()
}
说到事务,它主要是一个优化过的两阶段提交协议。 在事务模型中,有3个列族,即cf:lock,cf:write和cf:data。
- cf:lock:未提交的事务正在写此单元格,并包含主锁的位置/指针。 对于每个事务,我们选择一个主锁来指示事务的状态。
- cf:write:它存储数据的提交时间戳
- cf:data:存储数据本身
让我们看一个例子:Bob想要给Joe转7美元。
- 初始状态:Joe有2美元,Bob有10美元。
- 转账事务通过写入lock列锁定Bob的帐户开始。 此锁是事务的主锁。 事务将开始时间戳7写入数据。
- 事务现在锁定Joe的帐户,并写入Joe的新余额。 锁是事务的二级锁,并包含对主锁的引用; 所以我们可以使用这个二级锁找到主锁。
- 事务现在已达到提交点:删除主锁,并在新时间戳(提交时间戳)8处用写记录替换它。写记录包含指向存储数据的时间戳的指针。 将来读行“Bob”中的列“bal”将看到值$ 3。
- 通过添加写记录并在次要单元删除锁来完成事务。 在这种情况下,只有“Joe”一行。(译者注:这里数字应该是写错了,变成转账4美元了,领会原作者的核心意思就好)
下面是事务完成时的样子
说明
如有转载,请注明出处
http://blog.csdn.net/antony9118/article/details/60470115