TiDB 官方设计文档翻译(二)

这个系列共三篇译文: 
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()

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

说到事务,它主要是一个优化过的两阶段提交协议。 在事务模型中,有3个列族,即cf:lock,cf:write和cf:data。

  • cf:lock:未提交的事务正在写此单元格,并包含主锁的位置/指针。 对于每个事务,我们选择一个主锁来指示事务的状态。
  • cf:write:它存储数据的提交时间戳
  • cf:data:存储数据本身

让我们看一个例子:Bob想要给Joe转7美元。

  1. 初始状态:Joe有2美元,Bob有10美元。

这里写图片描述

  1. 转账事务通过写入lock列锁定Bob的帐户开始。 此锁是事务的主锁。 事务将开始时间戳7写入数据。

这里写图片描述

  1. 事务现在锁定Joe的帐户,并写入Joe的新余额。 锁是事务的二级锁,并包含对主锁的引用; 所以我们可以使用这个二级锁找到主锁。

这里写图片描述

  1. 事务现在已达到提交点:删除主锁,并在新时间戳(提交时间戳)8处用写记录替换它。写记录包含指向存储数据的时间戳的指针。 将来读行“Bob”中的列“bal”将看到值$ 3。

这里写图片描述

  1. 通过添加写记录并在次要单元删除锁来完成事务。 在这种情况下,只有“Joe”一行。(译者注:这里数字应该是写错了,变成转账4美元了,领会原作者的核心意思就好)

这里写图片描述

下面是事务完成时的样子

这里写图片描述

展开阅读全文

没有更多推荐了,返回首页