从零开始学TIDB二(TIDB简介与整体架构)


上一篇博客,我们简单介绍了怎么在Mac OS系统上面使用docker compose搭建一个包含3个pd,3个tikv,1个tidb的TIDB集群。本文,我们会详细介绍TIDB的一些基本概念和整体架构。

TIDB 简介

TIDB是pingcap公司开源的一款分布式数据库,结合了RDBMS和NOSQL两者的特性,支持对业务无感知的水平扩容,具备数据强一致性和高可用性。TIDB具备以下特性对某些业务应该有很大的吸引力。

  • 兼容MYSQL协议:由于TIDB兼容了MYSQL协议,业务几乎可以零成本的从MYSQL迁移到TIDB。

  • 水平弹性扩展:mysql虽然也可以分库分表水平扩展,但是如果需要继续水平扩容,就需要dba迁移数据,客户端可能还要修改路由规则。TIDB可以按需水平扩展吞吐或存储,数据自动迁移。

  • 分布式事物:TIDB支持分布式事物。

  • 高可用数据强一致:TIDB基于raft的多数派选举保证了数据的强一致性,并且在不丢失大部分副本的情况下,可以自动failover。

    TiDB 整体架构

    之前我们搭建了包含3个pd,3个tikv,1个tidb的TIDB集群,从这里可以看出TIDB其实主要包含了以下3个模块:
    tidb server,tikv server,pd server

  • tidb server:tidb server负责和客户端进行交互。tidb server接受客户端的sql语句进行解析,通过PD获取sql关联的数据对应的tikv server对应的地址,然后访问相应的tikv server,获取数据(或者修改数据),然后返回结果给客户端。tidb server本身不存储任何数据,只提供计算能力,本身是无状态的,可以根据需要水平扩展。

  • pd server:pd server是整个TIDB的管理模块。主要负责以下工作:存储集群的元信息(某个key存储在哪个tikv上);对tikv集群进行调度和负载均衡(数据迁移,raft group leader的迁移);分配全局唯一且递增的事物id;pd本身通过raft协议选举leader保证高可用性,建议线上至少部署3个pd节点。

  • tikv server:tikv server负责存储数据。tikv server使用rocksdb,提供了分布式key-value的存储引擎。数据存储的基本单位是region,每个tikv负责多个region,每个region包含了一个存储range,从start到end的一个范围。tikv使用raft协议做复制,保持数据的强一致和容灾。副本以region为单位进行管理,不同tikv节点上的leader和副本region构成一个raft group,互为副本。由pd来统一负责region在tikv上的负载均衡。(这里可以类比于kafka架构,region类似于partition,副本类似于replica)

总结下:TIDB由tidb,tikv,pd三个模块组成,tidb负责与客户端通信,提供了计算能力,tikv负责存储真正的数据,提供存储能力,pd作为管理模块,存储集群的一些元信息以及负责对整个集群进行管理调度。可以看到TIDB不同于MYSQL和其他Nosql存储,它是计算和存储分开的,各个模块都可以通过无线的水平扩展保证高可用性。

TiDB 的最佳适用场景

简单来说,TiDB 适合具备下面这些特点的场景:

  • 数据量大,单机保存不下
  • 不希望做 Sharding 或者懒得做 Sharding
  • 访问模式上没有明显的热点
  • 需要事务、需要强一致

SQL到KV的映射

通过上面介绍,我们知道了tikv基于rocksdb存储引擎以key-value的形式保存数据,这个和其他RDBMS基于table rows来保存数据不同。但是tidb又兼容了MYSQL协议,这是什么意思呢?就是我们完全可以像使用mysql一样使用tidb,我们可以发送一条sql命令给tidb

select * from user

正因为tidb兼容了mysql协议,所以业务可以几乎零成本的迁移到tidb。下面我们来看下tidb是怎么完成从SQL到KV的转换的。
假设我们有以下一张表:

CREATE TABLE User {
    ID int,
    Name varchar(20),
    Age int,
    PRIMARY KEY (ID),
    Key idxAge (age)
};

对于一个table来说,需要存储的数据包括以下三个部分:

  • 表的元信息
  • Table中的row
  • 索引数据

对于tidb来说,必须能把以上数据通过某种算法转换成key-value形式发送给tikv。因为我们要像多数RDBMS一样支持大象OLTP业务,需要支持快速读取,保存,修改,删除数据,所以这个转换算法的设计就尤为关键了。
我们可以把tikv想象成一个包含很多key-value键值对的map,可以类比JAVA的TreeMap,将key按照一定的规则进行排序。这样tikv其实就是一个全局有序的分布式key-value引擎,这点很重要,能保证我们根据某个条件快速检索出需要的key。下面我们看下,tidb是怎么讲table转换为key-value的。

  • TiDB 对每个表分配一个 TableID,每一个索引都会分配一个 IndexID,每一行分配一个 RowID(如果表有整数型的 Primary Key,那么会用 Primary Key 的值当做 RowID),其中 TableID 在整个集群内唯一,IndexID/RowID 在表内唯一,这些 ID 都是 int64 类型。 每行数据按照如下规则进行编码成 Key-Value pair:
    Key: tablePrefix_rowPrefix_tableID_rowID
    Value: [col1, col2, col3, col4]
  • 其中 Key 的 tablePrefix/rowPrefix 都是特定的字符串常量,用于在 KV 空间内区分其他数据。 对于 Index 数据,根据Index是UK和非UK分别按照以下不同规则编码成Key-Value
    Unique Index:因为indexColumnsValue是唯一的,所以下面构造的key也是全局唯一的
    Key: tablePrefix_idxPrefix_tableID_indexID_indexColumnsValue
    Value: rowID
  • 非Unique Index:因为indexColumnsValue不是唯一的,所以不能按照上述规则来构造key,我们可以按照下面规则
    Key: tablePrefix_idxPrefix_tableID_indexID_ColumnsValue_rowID
    Value:null

说明:对于上述key中出现的tablePrefix,rowPrefix,idxPrefix都是一些常量。另外tableID式全局唯一的,rowID和indexID是表内唯一的。这样一个 Table 内部所有的 Row 都有相同的前缀,一个 Index 的数据也都有相同的前缀。这样一个表的所有 Row 数据就会按照 RowID 的顺序排列在 TiKV 的 Key 空间中,某一个 Index 的数据也会按照 Index 的 ColumnValue 顺序排列在 Key 空间内。显然,这种构造key的方式对于点查和范围查询都比较友好。

tablePrefix,rowPrefix,idxPrefix对应常量

var(
    tablePrefix     = []byte{'t'}
    recordPrefixSep = []byte("_r")
    indexPrefixSep  = []byte("_i")
)

下面举个例子来说明上述构造规则,我们还是以最开始的User表来构造以下3条数据:

id  name          age
1   'zhangsan'    20
2   'lisi'        18
3   'wangwu'      22

User表存在Primary Key(id),假设User表tableId 为1,所以上述3条数据可以转换为以下key-value pair

key         value 
t_r_1_1     ['zhangsan', 20]
t_r_1_2     ['lisi', 18]
t_r_1_3     ['wangwu', 22]

另外该表在age上还有一个Index,假设indexID为1,对应key-value pair如下

key             value
t_i_1_1_20_1    null
t_i_1_1_18_2    null
t_i_1_1_22_3    null

MVCC

很多数据库都会实现多版本控制(MVCC),TiKV 也不例外。 TiKV 的 MVCC 实现是通过在 Key 后面添加 Version 来实现,简单来说,没有 MVCC 之前,可以把 TiKV 看做这样的:

Key1 -> Value
Key2 -> Value
……
KeyN -> Value

有了 MVCC 之后,TiKV 的 Key 排列是这样的:

Key1-Version3 -> Value
Key1-Version2 -> Value
Key1-Version1 -> Value
……
Key2-Version4 -> Value
Key2-Version3 -> Value
Key2-Version2 -> Value
Key2-Version1 -> Value
……
KeyN-Version2 -> Value
KeyN-Version1 -> Value
……

注意,对于同一个 Key 的多个版本,我们把版本号较大的放在前面,版本号小的放在后面,这样当用户通过一个 Key + Version 来获取 Value 的时候,可以将 Key 和 Version 构造出 MVCC 的 Key,也就是 Key-Version。然后可以直接 Seek(Key-Version),定位到第一个大于等于这个 Key-Version 的位置。


文章作者: 叶明
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶明 !
评论
 上一篇
Redis热点Key解决方案 Redis热点Key解决方案
随着公司业务规模越来越大,基本上所有的业务都接入了Squirrel,目前我们线上redis集群支撑了每天服务端万亿+、端到端3000亿+的调用量。用户因为一些业务的特性触发热点问题是业务需要,是不可避免,理所当然的,难免会触发“热点key问题”。
2018-10-01
下一篇 
从零开始学TIDB一(搭建TIDB集群) 从零开始学TIDB一(搭建TIDB集群)
本文介绍如何在单机上(Mac OS)通过 Docker Compose 快速一键部署一套 TiDB 测试集群,我们会从零开始搭建一套由3 个 PD,3 个 TiKV,1 个 TiDB构成的集群。主要包括以下两个步骤: Mac OS系统上安
2018-08-26
  目录