0
1
2
0
专栏/.../

TIKV分布式事务简介

 Lystorm  发表于  2024-04-07

借着这次社区 PCTA/PCTP/PCSD 免费考证的活动,看了不少的教程与优秀的社区文章,选了其中的一个点展开总结一下,也希望可以写成文章让大家给看看我这块的理解是不是存在偏差。

分布式事务是事务的一种,指的是事务的参与者,支持事务的服务器,参与事务的资源服务器,事务的管理者等角色分别位于不同的节点上。

 通常我们用ACID来定义事务(ACID概念定义)。简单说一下这些概念以及TiDB数据库是怎么实现 ACID 的:

 A(原子性):构成事务的所有操作,要么全部执行成功,要么全部执行失败,不会出现部分成功或者部分失败的情况,TiKV基于GOOGLE的Percolator论文实现原子性,核心思想是通过使用Primary Key所在region的原子性来保证。

C(一致性):在事务执行前后,数据库的一致性约束没有被破坏,比如,小李去银行取100块钱,取之前是500,取之后应该是400,取之前和取之后的数据为正确数值就为一致性,如果取出100,而银行里面的钱没有减少,就没有达到一致性的要求。TiKV在写入数据之前,会对数据的一致性进行校验(Raft算法),校验通过才会写入内存并返回成功。

I(隔离性):隔离性主要用于处理并发场景,TiDB 目前只支持一种隔离级别 快照隔离 (Snapshot Isolation, SI) ,又称其为“可重复读”,即在事务内可重复读,只能读到该事务启动时已经提交的其他事务修改的数据,未提交的数据或在事务启动后其他事务提交的数据是不可见的(通过MVCC实现,本段提到的隔离级别会在后续MVCC机制的介绍中展开介绍,此处先暂做了解)。

D(持久性):事务一旦提交成功,数据全部持久化到TiKV, 此时即使 TiDB 服务器宕机也不会出现数据丢失。

首先看一下下面这张我根据自己的理解梳理出来的一个简易的流程图:

no-alt

整体的流程可以在上图左侧看到,TIDB Server收到请求后,会向PD发起TSO请求(TSO 是一个单调递增的时间戳,由 PD leader 分配。TiDB 在事务开始时会获取 TSO 作为 start_ts、提交时获取 TSO 作为 commit_ts,依靠 TSO 实现事务的 MVCC),申请一个开始时间戳start_ts,TIDB Server会在内存中先修改数据,当数据要进行持久化时,开始进行两阶段提交,两阶段提交包括了prewrite和commit两个步骤,commit完成后,事务才真正的完成了事务的持久化。

流程内部涉及到的逻辑较为抽象,因此本文就对一个实际的事务实现流程进行说明,以便于理解。

一个TIKV集群,Node0为leader节点,Node1与Node2为follow节点,两个follow节点的逻辑完全相同,因画布限制,不对Node2进行详细介绍,仅介绍Node1与Node0。目前集群中已经存在了两条数据,分别为<1,'小一'>和<2,'小二'>。

TIKV的数据最终在RocksDB底层存储的数据格式如上图所示,可以看到是存储在三个CF中,Lock CF存储锁信息,Default存储数据的key,start_ts,value信息,Write CF存储数据的key,commit_ts,start_ts信息。接下来会在对事务流程的介绍过程中,详细说明这些信息。

 现在TIKV接到了事务请求,需要执行以下操作:

①将<1,'小一'>更新为<1,‘小李’>

②新增<3,'小张'>

接到请求后,TIDB Server会向PD发起TSO请求,申请一个开始时间戳start_ts=110,同时会在内存中修改数据,修改‘小一’为'小李,新增<3,'小张'>,此时所有的操作、数据都还停留在内存中,事务是要进行持久化的,开始进行持久化时,就进入了2阶段提交步骤。

prewrite阶段,会将内存中修改的数据,全部都写入到磁盘中,并且给数据上锁。

leader节点上Lock CF和Default CF中分别会写入两条信息,第一条数据:Lock CF中的<1,(w,pk,110..)>,1代表key=1,w代表write操作,pk代表这是一个主锁,prewrite阶段,会将写请求的第一行数据作为主锁(primary key),110表示start_ts,".."中的内容表示锁的其他信息,如校验信息,指针信息等,不影响整体逻辑理解,所以暂不做展开,下文也不再做解释。Default CF中的put<1_110,'小李'>,put代表数据写入,1_110代表key=1,事务的start_ts=110,‘小李’是value。第二条数据:Lock CF中的<3,(w,@1,110..)>,3代表key=3,w代表write操作,@1代表这是一个副锁,prewrite阶段,会将写请求除了第一行数据之外的行作为副锁(secondary key),110表示start_ts。Default CF中的put<3_110,'小张'>,put代表数据写入,3_110代表key=3,事务的start_ts=110,‘小张’是value。

follow节点上的Lock CF和Default CF中也会写入两条信息,第一条数据:Lock CF中的<1,(w,@1,110..)>,1代表key=1,w代表write操作,@1代表这是一个副锁,110表示start_ts,。Default CF中的put<1_110,'小李'>,put代表数据写入,1_110代表key=1,事务的start_ts=110,‘小李’是value。第二条数据:Lock CF中的<3,(w,@1,110..)>,3代表key=3,w代表write操作,@1代表这是一个副锁,110表示start_ts。Default CF中的put<3_110,'小张'>,put代表数据写入,3_110代表key=3,事务的start_ts=110,‘小张’是value。

写入Lock CF就是对数据进行上锁的过程,上锁前,会校验是否有别的客户端对数据已经进行上锁,且primary key和secondary key是一前一后进行的,prewrite阶段会等待所有节点返回成功后才会进行下一步的commit,如果这中间有任何一步或者任何节点失败了,那么prewrite就直接失败,所有节点直接触发回滚(Rollback)。

commit阶段,TIKV会向PD获取commit_ts,PD会确保commit_ts的值大于start_ts,本示例中commit_ts=120,获取commit_ts后,将数据写入Write CF中,并将Lock CF中的锁删除。

leader节点上的Write CF和Lock CF会写入两条数据,第一条数据:Write CF中的put<1_120,110>,put代表数据写入,1_120代表key=1,事务的commit_ts=120,110代表start_ts=110。Lock CF中的<1,(D,pk,120..)>,1代表key=1,D代表delete锁操作,pk代表这是一个主锁,120表示commit_ts。第二条数据:Write CF中的put<3_120,110>,put代表数据写入,3_120代表key=3,事务的commit_ts=120,110代表start_ts=110。Lock CF中的<3,(D,@1,120..)>,3代表key=1,D代表delete锁操作,@1代表这是一个副锁,120表示commit_ts。

follow节点上的Write CF和Lock CF也会写入两条数据,第一条数据:Write CF中的put<1_120,110>,put代表数据写入,1_120代表key=1,事务的commit_ts=120,110代表start_ts=110。Lock CF中的<1,(D,@1,120..)>,1代表key=1,D代表delete锁操作,@1代表这是一个副锁,120表示commit_ts。第二条数据:Write CF中的put<3_120,110>,put代表数据写入,3_120代表key=3,事务的commit_ts=120,110代表start_ts=110。Lock CF中的<3,(D,@1,120..)>,3代表key=1,D代表delete锁操作,@1代表这是一个副锁,120表示commit_ts。

数据在读取时,会寻找Lock CF和Write CF中是否存在复合条件的数据,若在Write CF中存在复合条件的值,则从Write CF中取出key和start_ts的值,去Default CF中获取这个key对应的value值。因此Write CF中存储的是事务的key+commit_ts和start_ts的值。Lock CF与Write CF读取的逻辑,这块的内容会在MVCC的文章中展开说明,此时暂时先做个了解。

commit阶段也存在回滚机制,与prewrite不同的事,commit阶段只有当primary key的事务提交失败了,才会触发回滚。至此TIKV的分布式事务介绍完毕。

0
1
2
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论