先从TiDB说起
TiDB 是什么
首先引用一下官方的定义:
TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 HTAP (Hybrid Transactional and Analytical Processing) 数据库,结合了传统的 RDBMS 和 NoSQL 的最佳特性。TiDB 兼容 MySQL,支持无限的水平扩展,具备强一致性和高可用性。TiDB 的目标是为 OLTP (Online Transactional Processing) 和 OLAP (Online Analytical Processing) 场景提供一站式的解决方案。
TiDB 具备如下核心特性:
- 高度兼容 MySQL
大多数情况下,无需修改代码即可从 MySQL 轻松迁移至 TiDB,分库分表后的 MySQL 集群亦可通过 TiDB 工具进行实时迁移。
- 水平弹性扩展
通过简单地增加新节点即可实现 TiDB 的水平扩展,按需扩展吞吐或存储,轻松应对高并发、海量数据场景。
- 分布式事务
TiDB 100% 支持标准的 ACID 事务。
- 真正金融级高可用
相比于传统主从 (M-S) 复制方案,基于 Raft 的多数派选举协议可以提供金融级的 100% 数据强一致性保证,且在不丢失大多数副本的前提下,可以实现故障的自动恢复 (auto-failover),无需人工介入。
- 一站式 HTAP 解决方案
TiDB 作为典型的 OLTP 行存数据库,同时兼具强大的 OLAP 性能,配合 TiSpark,可提供一站式 HTAP 解决方案,一份存储同时处理 OLTP & OLAP,无需传统繁琐的 ETL 过程。
TiDB 的设计目标是 100% 的 OLTP 场景和 80% 的 OLAP 场景。它解决了我们工作中面临的因为数据量大到一定程度后横向扩展受限,人工进行分库分表,人工进行数据库事务的问题。降低了开发人员的心智负担,让开发人员可以更专注于具体业务,提高了生产效率。
TiDB 整体架构
TiDB 有 TiDB Server 、PD Server、 TiKV Server 三个核心组件。
PD 是整个集群的管理模块,它拥有上帝视角。 其主要工作有三个:一是存储集群的元信息(某个 Key 存储在哪个 TiKV 节点);二是对 TiKV 集群进行调度和负载均衡(如数据的迁移、Raft group leader 的迁移等);三是分配全局唯一且递增的事务 ID。
TiKV Server 是一个分布式的提供事务的 Key-Value 存储引擎。 TiDB Server 负责接收 SQL 请求,处理 SQL 相关的逻辑,并通过 PD 找到存储计算所需数据的 TiKV 地址,与 TiKV 交互获取数据,最终返回结果。
TiDB Server 是无状态的,其本身并不存储数据,只负责计算,可以无限水平扩展,
在执行一条Insert语句会做什么事情?
画不多说, 先上图:
不要被这么复杂的图吓怕,因为其中的绝大部分内容都不会涉及(我也不会,不会还要拿来show --!!)。
主体流程还是很容易明白的,首先,TiDB会解析执行的SQL语句,使用parser 将其变成AST,然后通过执行计划将其拆解成一个个的executor。在获取到所有的executor的执行结果后,对数据进行拼装,最终返回给客户端。
那么,在数据库中数据是怎么进行存储,Server又是如何进行访问的呢?
其实,数据库将表中的row映射成kv结构,然后将kv进行物理存储。MySQL如此,TiDB也是如此。这种想法其实也很自然,数据库表中的每行使用id 作为key,行的内容作为value存储下来,查询某条记录,就变成了根据某个key,找对应的value。
Key的生成策略
首先,我们需要面对的问题就是对于一个数据库表,使用什么样的key生成策略来保存行的内容。同时因为数据库的索引结构也可以用表存储,因此,数据库的索引也会被持久化到kv 中。
TiDB 对每个表分配一个 TableID,每一个索引都会分配一个 IndexID,每一行分配一个 RowID(如果表有整数型的 Primary Key,那么会用 Primary Key 的值当做 RowID),其中 TableID 在整个集群内唯一,IndexID/RowID 在表内唯一,这些 ID 都是 int64 类型。
- 数据库行的key生成策略
${tablePrefix}_${rowPrefix}_${tableID}_${rowID}
其中tablePrefix
是固定的t_
, rowPrefix
是固定的r_
。比如 table table_demo
的ID 为10,其中第一条数据的row ID 是1,那么它的key是t_r_10_1
- 数据库索引key的生成策略
- 主键或者唯一键的索引
${tablePrefix}_${idxPrefix}_${tableID}_${indexID}_${indexColumnsValue}
其中idxPrefix
是固定的i_
, 比如id字段的索引ID 为2, 那么它对应的key 是 t_i_10_2_1
--> t_r_10_1
- 非主键索引
${tablePrefix}_${idxPrefix}_${tableID}_${indexID}_${ColumnsValue}_${rowID}
具体的细节可以查看参考资料,里面有非常详细的表述。这里不做过多的解释
TiDB 和TiKV的交互接口
在整个学习的过程中,头脑中需要有一个问题,select
,insert
操作是怎么在TiKV中实现的。
既然TiDB 已经将具体的数据库的row, 转化成了kv,那么问题本身也就转化成了在TiKV中如何实现get
,set
命令。
这里先列举相关的TiKV暴露给TiDB调用的接口,学习的过程可以从这里入手。
service Tikv {
// KV commands with mvcc/txn supported.
rpc KvGet(kvrpcpb.GetRequest) returns (kvrpcpb.GetResponse) {}
rpc KvScan(kvrpcpb.ScanRequest) returns (kvrpcpb.ScanResponse) {}
rpc KvPrewrite(kvrpcpb.PrewriteRequest) returns (kvrpcpb.PrewriteResponse) {}
rpc KvCommit(kvrpcpb.CommitRequest) returns (kvrpcpb.CommitResponse) {}
rpc KvImport(kvrpcpb.ImportRequest) returns (kvrpcpb.ImportResponse) {}
rpc KvCleanup(kvrpcpb.CleanupRequest) returns (kvrpcpb.CleanupResponse) {}
rpc KvBatchGet(kvrpcpb.BatchGetRequest) returns (kvrpcpb.BatchGetResponse) {}
rpc KvBatchRollback(kvrpcpb.BatchRollbackRequest) returns (kvrpcpb.BatchRollbackResponse) {}
rpc KvScanLock(kvrpcpb.ScanLockRequest) returns (kvrpcpb.ScanLockResponse) {}
rpc KvResolveLock(kvrpcpb.ResolveLockRequest) returns (kvrpcpb.ResolveLockResponse) {}
rpc KvGC(kvrpcpb.GCRequest) returns (kvrpcpb.GCResponse) {}
rpc KvDeleteRange(kvrpcpb.DeleteRangeRequest) returns (kvrpcpb.DeleteRangeResponse) {}
// RawKV commands.
rpc RawGet(kvrpcpb.RawGetRequest) returns (kvrpcpb.RawGetResponse) {}
rpc RawBatchGet(kvrpcpb.RawBatchGetRequest) returns (kvrpcpb.RawBatchGetResponse) {}
rpc RawPut(kvrpcpb.RawPutRequest) returns (kvrpcpb.RawPutResponse) {}
rpc RawBatchPut(kvrpcpb.RawBatchPutRequest) returns (kvrpcpb.RawBatchPutResponse) {}
rpc RawDelete(kvrpcpb.RawDeleteRequest) returns (kvrpcpb.RawDeleteResponse) {}
rpc RawBatchDelete(kvrpcpb.RawBatchDeleteRequest) returns (kvrpcpb.RawBatchDeleteResponse) {}
rpc RawScan(kvrpcpb.RawScanRequest) returns (kvrpcpb.RawScanResponse) {}
rpc RawDeleteRange(kvrpcpb.RawDeleteRangeRequest) returns (kvrpcpb.RawDeleteRangeResponse) {}
rpc RawBatchScan(kvrpcpb.RawBatchScanRequest) returns (kvrpcpb.RawBatchScanResponse) {}
// SQL push down commands.
rpc Coprocessor(coprocessor.Request) returns (coprocessor.Response) {}
rpc CoprocessorStream(coprocessor.Request) returns (stream coprocessor.Response) {}
// Raft commands (tikv <-> tikv).
rpc Raft(stream raft_serverpb.RaftMessage) returns (raft_serverpb.Done) {}
rpc Snapshot(stream raft_serverpb.SnapshotChunk) returns (raft_serverpb.Done) {}
// Region commands.
rpc SplitRegion (kvrpcpb.SplitRegionRequest) returns (kvrpcpb.SplitRegionResponse) {}
// transaction debugger commands.
rpc MvccGetByKey(kvrpcpb.MvccGetByKeyRequest) returns (kvrpcpb.MvccGetByKeyResponse) {}
rpc MvccGetByStartTs(kvrpcpb.MvccGetByStartTsRequest) returns (kvrpcpb.MvccGetByStartTsResponse) {}
}