TiDB存储引擎TiKV揭秘

目录

一、TiKV 介绍

二、RocksDB

三、TiKV 与 RocksDB 架构

        3.1 用户数据保存

        3.2 TiKV 中 Region


一、TiKV 介绍

        TiKV 是一个分布式事务型的键值数据库,提供了满足 ACID 约束的分布式事务接口,并且通过 Raft 协议保证了多副本数据一致性以及高可用。TiKV 作为 TiDB 的存储层,为用户写入 TiDB 的数据提供了持久化以及读写服务,同时还存储了 TiDB 的统计信息数据。

        与传统的整节点备份不同,TiKV 参考了 Spanner 设计了 multi-raft-group 的副本机制。将数据按照 key 的范围划分成大致相等的切片(region),每一个切片会有多个副本(默认3个),其中一个副本是 leader,提供读写服务。TiKV 通过 PD 对这些 Region 以及副本进行调度,以保证数据和读写负载都均匀的分散在各个 TiKV 上,这样的设计保证了整个集群资源的充分利用并且可以随着机器数量的增加水平扩展。

         任何持久化的存储引擎,数据终归要保存在磁盘上,TiKV 也不例外。但是 TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责。

二、RocksDB

        TiKV 为何选择 RocksDB 来存储数据呢?

        这个选择的原因是开发一个单机存储引擎工作量很大,特别是要做一个高性能的单机引擎,需要做各种细致的优化,而 RocksDB 是由 Facebook 开源的一个非常优秀的单机 KV 存储引擎,可以满足 TiKV 对单机引擎的各种要求。这里可以简单的认为 RocksDB 是一个单机的持久化 Key-Value Map。

        RocksDB 是由 Facebook 基于 LevelDB 开发的一款提供键值存储与读写功能的 LSM-tree 架构引擎。用户写入的键值对会先写入磁盘上的 WAL(Write Ahead Log),然后再写入内存中的跳表。LSM-tree 由于将用户的随机修改(插入)转化为对 WAL 的顺序写,因此具有比 B 树类存储引擎更高的写吞吐。

        内存中的数据达到一定阈值后,会刷到磁盘上生成 SST 文件(Sorted String Table),SST 又分为多层(默认最多6层),每一层的数据达到一定阈值后会挑选一部分 SST 合并到下一层,每一层的数据是下一层的10倍(因此 90% 的数据存储在最后一层)。      

        操作流程如下:

  1. 新写入的数据首先被写入 memtable,而非直接写入磁盘。这种设计极大地减少了磁盘 I/O,降低了写入延迟,尤其适合需要频繁写入和快速响应的应用场景。
  2. memtable 有固定的内存容量限制,由参数 write_buffer_size 控制。当 memtable 的大小接近或达到这一限制时,RocksDB 会触发以下动作:
    1. 转换为 Immutable Memtable:当前 memtable 被标记为不可变(Immutable),新的写入操作会被导向到一个新的 memtable。
    2. Flush 到 SSTable:后台线程将 Immutable Memtable 中的数据序列化并写入磁盘,形成一个 SSTable(Sorted String Table),这是一个持久化的、经过排序的键值对集合。
    3. WAL 更新:在 memtable 数据写入磁盘的过程中,RocksDB 会维护 Write Ahead Log (WAL, 也称预写日志),确保即使在崩溃或意外关机的情况下,尚未持久化的 memtable 数据也能通过回放 WAL 重建。
  3. RocksDB 支持多列族(Column Family),每个列族都有自己独立的 memtable。这意味着不同的数据集可以有不同的存储配置和访问模式,进一步提升了存储灵活性和查询效率。

三、TiKV 与 RocksDB 架构

        RocksDB作为TiKV的核心存储引擎,用于存储Raft日志以及用户数据。每个TiKV实例中有两个RocksDB,一个用于存储Raft日志(通常被称为 raftdb),另一个用于存储用户数据以及MVCC信息(通常被称为 kvdb)。

         为什么需要两个 RocksDB呢?

        在 TiKV 中,使用两个 RocksDB 实例对应的是其内部的存储结构设计。具体来说,TiKV 将数据分为两部分存储:KV 数据(键值对数据)和 Raft Log 数据,分别使用两个独立的 RocksDB 实例进行管理。这种设计的目的是为了清晰分离不同性质的数据,并针对性地进行优化,确保系统的高效运行。

        将二者分离的主要目的如下:

  • 隔离负载:不同类型的操作,有不同的性能要求和访问模式,分开存储可以避免互相干扰,提高整体系统的稳定性和效率。
  • 精细化管理:针对各自的特点进行定制化的配置和优化,如调整压缩算法、缓存策略、自如放大控制等,以适应各自的工作负载。
  • 简化故障恢复:在节点故障或数据迁移时,可以独立地处理 KV 数据和 Raft Log,简化恢复流程,提高恢复速度。

        3.1 用户数据保存

        上面提到 RocksDB 支持多列族(Column Family),TiKV 使用 RocksDB 保存数据时使用了四个列族,分别是:raft、lock、default和write。

        Raft 列族:用于存储各个 Region 的元信息。仅占极少空间,可以无需关注。

        lock 列族:用于存储悲观事务的悲观锁以及分布式事务的一阶段 Prewrite 锁。当用户的事务提交之后,lock Column Family 中对应的数据会很快删除掉,因此大部分情况下,lock Column Family 中的数据也很少。如果 lock Column Family  中数据大量增加,说明有大量事务等待提交,系统出现了故障或 bug。

        write 列族:用于存储用户真实写入的数据以及 MVCC 信息,当用户写入了一行数据时,如果改行长度小于 255 字节,那么会被存储 write 列中,否则的话该行会被存入 default 列中。由于 TiDB 的非 unique 索引存储的 value 为空,unique 索引存储的 value 为主键索引,因此二级索引只会占用 write Column Family 的空间。

        default 列族:用于存储超过 255 字节长度的数据。

        3.2 TiKV 中 Region

        为了实现存储的水平扩展,数据将被分散在多台机器上。对于一个 KV 系统,将数据分散在多台机器上有两种比较典型的方案:

  • Hash:按照 Key 做 Hash,根据 hash 值选择对应的存储节点
  • Range:按照 Key 分 Range,某一段连续的 Key 都保存在一个存储节点上

        TiKV 选择了第二种方式,将整个 Key-Value 空间分成很多段,每一段是一系列连续的 Key,将每一段叫做一个 Region,可以用[StartKey, EndKey)这样一个左闭右开区间来描述。每个Region 中保存的数据量默认维持在 96MiB(可通过配置修改)。

        数据按照 Key 切分成很多 Region,每个 Region 的数据只会保存在一个节点上面(暂不考虑多副本)。TiDB 系统会有一个组件 (PD) 来负责将 Region 尽可能均匀的散布在集群中所有的节点上,这样一方面实现了存储容量的水平扩展(增加新的节点后,会自动将其他节点上的 Region 调度过来),另一方面也实现了负载均衡(不会出现某个节点有很多数据,其他节点上没什么数据的情况)。同时为了保证上层客户端能够访问所需要的数据,系统中也会有一个组件 (PD) 记录 Region 在节点上面的分布情况,也就是通过任意一个 Key 就能查询到这个 Key 在哪个 Region 中,以及这个 Region 目前在哪个节点上(即 Key 的位置路由信息)。

        TiKV 是以 Region 为单位做数据的复制,也就是一个 Region 的数据会保存多个副本,TiKV 将每一个副本叫做一个 Replica。Replica 之间是通过 Raft 来保持数据的一致,一个 Region 的多个 Replica 会保存在不同的节点上,构成一个 Raft Group。其中一个 Replica 会作为这个 Group 的 Leader,其他的 Replica 作为 Follower。默认情况下,所有的读和写都是通过 Leader 进行,读操作在 Leader 上即可完成,而写操作再由 Leader 复制给 Follower。

        以 Region 为单位做数据的分散和复制,TiKV 就成为了一个分布式的具备一定容灾能力的 KeyValue 系统,不用再担心数据存不下,或者是磁盘故障丢失数据的问题。 

往期经典推荐

TiDB 数据库调度(PD)揭秘-CSDN博客

深入浅出 TiDB MVCC:揭秘分布式数据库中的多版本并发控制-CSDN博客

深入浅出 Drools 规则引擎-CSDN博客

MySQL文件系统解密:binlog、redolog与undolog如何守护数据安全与一致性_mysqlbin 解密-CSDN博客

走进 Mybatis 内核世界:理解原理,释放更多生产力-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/616259.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

量子信息产业生态研究(一):关于《量子技术公司营销指南(2023)》的讨论

写在前面。量子行业媒体量子内参(Quantum Insider)编制的《量子技术公司营销指南》是一本实用的英文手册,它旨在帮助量子科技公司建立有效的营销策略,同时了解如何将自己定位成各自的行业专家。本文对这篇指南的主要内容进行了翻译…

两步解决 Flutter Your project requires a newer version of the Kotlin Gradle plugin

在开发Flutter项目的时候,遇到这个问题Flutter Your project requires a newer version of the Kotlin Gradle plugin 解决方案分两步: 1、在android/build.gradle里配置最新版本的kotlin 根据提示的kotlin官方网站搜到了Kotlin的最新版本是1.9.23,如下图所示: 同时在Ko…

相机参数的意义

相机标定的意义: 相机标定:使用带有pattern的标定板来求解相机参数的过程;用一个简化的数学模型来代表复杂的三维到二维的成像过程;相机参数包括:相机内参(焦距等),外参&#xff08…

了解虚拟路由器冗余协议(VRRP)

虚拟路由器冗余协议(VRRP)是一种被广泛使用的网络协议,旨在增强网络的可靠性和可用性。对于网络管理员和工程师来说,了解VRRP是确保网络能够实现无缝故障转移和保持不间断连接的关键。本文将深入探讨VRRP的基础知识,包…

ISP图像处理pipeline简介1

ISP 是什么? ISP (Image Signal Processor),图像信号处理器,是用于摄影和视频处理的一种专用芯片。它是用来干什么的呢?简单说就是用来将图像传感器(CCD, CMOS)信号转化成可视的信号的功能,这里…

2024信息工程、软件与计算机工程国际会议(ICIESCE2024)

2024信息工程、软件与计算机工程国际会议(ICIESCE2024) 会议简介 随着互联网的不断创新,信息工程、软件和计算机工程在各个领域得到了广泛应用。为了为来自世界各地的专家学者提供一个分享通信和计算机工程领域研究成果的平台,2024年信息工程…

强化学习实践0

gym是目前强化学习最常用的工具之一,一直在迭代升级。2021年gym库不再更新,推出了gymnasium作为替代 1。 gymnasium与gym之间的主要不同在于reset和step的返回参数数目发生了变化,具体变化见版本变化。 gym目前并不支持python3.11版本…

【重回王座】ChatGPT发布最新模型gpt-4-turbo-2024-04-09

今天,新版GPT-4 Turbo再次在大型模型排行榜上荣登榜首,成功超越了此前领先的Claude 3 Opus。另外,新模型在处理长达64k的上下文时,性能竟能够与旧版在处理26k上下文时的表现相当。 目前GPT-4 Turbo仅限于ChatGPT Plus的用户&…

minio-docker单节点部署SDK测试文件上传下载

目录 一,docker部署minio单节点单磁盘 二,SDK测试上传下载 一,docker部署minio单节点单磁盘 1.拉取镜像 # 下载镜像 docker pull minio/minio 2.查看镜像 docker images 3.启动minio(新版本) 创建本机上的挂载目录,这个可以…

C++11的更新介绍(lamada、包装器)

🪐🪐🪐欢迎来到程序员餐厅💫💫💫 主厨:邪王真眼 主厨的主页:Chef‘s blog 所属专栏:c大冒险 总有光环在陨落,总有新星在闪烁 lambda表达式 C98中的一个…

【软件设计师】计算机软考下午题试题六,Java设计模式之简单工厂模式。

【软件设计师】计算机软考下午题试题六,Java设计模式之简单工厂模式。 代码如下: //简单工厂模式 public class SimpleFactory {public static void main(String[] args) {Product ProductAFactory.createProduct("A");ProductA.info();Produc…