0
0
1
0
专栏/.../

TiKV 事务介绍

 TiDBer_jYQINSnf  发表于  2024-08-05

这一篇介绍 tikv 怎么实现的跨节点事务。内容参考了不少 TiDB 的文档和视频资料,这里也算是消化以后的一次总结,希望能对大家理解 tikv 的事务有所帮助。

事务原理介绍

TiKV 采用了 Google Percolator 这篇论文中所述的事务模型,事务主要由客户端驱动,tikv server 端实现了 prewrite 、 commit、rollback 等接口.在客户端调用 client.Commit() 时,开始进入分布式事务 prewrite 和 commit 流程,客户端通过把要修改的 key 分组,发送给对应的tikv-server,调用不同的tikv-server接口实现事务逻辑。

TiKV 的主要事务逻辑如图:



图表



这里注意:重点在 commit 阶段,tikv-client 首先向持有 primary key 锁的 tikv-server发送grpc请求,当 primary key 锁被提交后才可以并行发送后续的锁的提交请求。把分布式事务的成功和失败的决策点缩小到单一一个节点上。

下图是 TiDB 事务2阶段提交的过程,这里假设是把 <1,Jack>,<2,Candy> 两个属于不同region、不同tikv的key按事务写入tikv。





TIKV 底层是 rocksdb,rocksdb 支持多个column family 原子性的写入一组key。tikv正是依赖这一特性,利用3个column family 实现 percolator 论文所述的事务模型。

上图中同一种颜色标识的两个column family 中的key代表一次原子写入,要么都成功,要么都失败。

1.首先在所有行的写操作中选出一个作为 primary row,其他的为 secondary rows

2.然后是 prewrite,在default cf 和 lock cf 写入数据,把要写入的变更按region分成2个grpc,分别发送给2个tikv。

3.两个tikv 分别在default cf中写入数据,lock cf 中写入锁。其中 tikv1 中的是主锁,tikv2的是从锁。上锁前会检查:该行是否已经有别的客户端已经上锁 (Locking)是否在本次事务开始时间之后,检查versions ,是否有更新 [startTs, +Inf) 的写操作已经提交 (Conflict)在这两种种情况下会返回事务冲突。否则,就成功上锁。将行的内容写入 row 中,版本设置为 startTs。

4.提交时,tikv_client 首先给主锁所在的region发送grpc,在write cf中写入commit记录put<1_100,100>,删除lock cf 中的锁<1,(D,pk,1,100..)>,这两个cf的数据利用rocksdb支持的多cf的原子写入保证同时成功或失败。

5.主锁提交成功后才给tikv2发送grpc,tikv2 在write cf中写入commit记录 put<2_110,100>,删除lock cf中的锁<2,(D,@1, 2,100...)>。 同样也是原子写入。



防止死锁







为了防止一个事务的key prewrite后,事务还没提交,tikv-client 异常退出后,事务残留后,其他 tikv-client 无法读取到这个key,tikv给prewrite设置了ttl,当ttl过期后可以执行清除锁。 如果ttl时间太短,又会导致大的事务还没提交就锁失效,如果ttl时间太长,又会导致清理锁的时间太长,导致某一个key长时间无法读取。

因此 tikv-server 中引入了 txn_heartbeat 接口。每次prewrite,根据prewrite的数据的大小不同,给锁设置的超时时间从3秒到120秒不等。同时在prewrite以后,开启一个goroutine不断刷新ttl,直到ttl达到整个事务的最大提交时间或者事务提交。

事务中的读操作:

1.检查该行是否有 Lock 标记,如果有,表示目前有其他事务正占用此行,如果这个锁已经超时则尝试清除,否则等待超时或者其他事务主动解锁。注意此时不能直接返回老版本的数据,否则会发生幻读的问题。

2.读取至 startTs 时该行最新的数据,方法是:读取 write cf ,找出时间戳为 [0, startTs], 获取最大的时间戳 t,然后读取为于 t 版本的数据内容。

大致的事务提交流程介绍到这。

0
0
1
0

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

评论
暂无评论