TiKV
1.TiKV作用
- 数据持久化
- 分布式一致性
- MVCC
- 分布式事务
- Coprocessor
2.RocksDB
- RocksDB针对Flash存储进行优化,延迟极小,使用LSM存储引擎
- 高性能的Kye Value数据库
- 完善的持久化机制,同时保证性能和安全性
- 良好的支持范围查询
- 为需要存储TB级别数据到本地FLASH或者RAM的应用服务器涉及
- 针对存储在高速设备中的中小键值进行优化-可以存储在FLASH或者直接存储在内存中
- 性能随着CPU数量线性提升,对多核系统友好
- RocksDB写入:
在插入一条数据时,会先写入到WAL(内存中断电就丢失了,先写日志记录),再写入到内存中(memtable),断电后虽然内存中数据没有了,但是可以重读WAL日志完成故障恢复。
(这里有个SYNC_LOG参数=True,含义是不开启的话,写入WAL LOG会经过操作系统,批量写入到WAL中,这样也有数据丢失的可能,所以需要开启这个参数,直接写入WAL不经过操作系统)
在不断追加写入到memtable中时,会有一个write buffer参数,当达到设定的大小后,就会将数据转存到inmutable memtable中,不能进行更改了。至此RocksDB重新开启一个memtable。这是immutable会有进程将其刷入SST文件磁盘中(如果immutable来不及写入SST中,数量达到5个就会进入write stall进入自我保护模式,限制写入memtable的速度)
Level 0:内存数据的副本
-
RocksDB的查询
最近读的数据会缓存在BLOCK CACHE中,如果再用就可以直接用
如果BLOCK CACHE中没找到,就去memtable中找,再去immutable依次往下找(这里在level里面的数据其实会自动排序,大于值就直接跳过了,加速) -
RocksDB Column Families(列簇)
之前上面介绍的是一个RocksDB方法,如果涉及多个表的操作,数据存成SST就会比较杂乱,这时CF列簇就有了作用,每个CF值操作一张表的数据,这样查询也会加速,可以用不同的CF去查(数据分片技术)
可以指定写入的CF,不指定则按Default写。
注:这里分了CF的内存,SST文件,但是WAL日志没有分开
3.分布式事务
- 分布式事务分为
- begin:start timestamp=100
- perwrite:修改数据+锁信息
- commit:commit timestamp=110
在一个事务提交后,进入以下两阶段:
首先与PD交互获得开始的timestamp信息,然后进入prewrite阶段,在这个阶段,会用到之前提到过的column families技术,有多个列簇来进行负责。
首先修改的数据内容,会进入default列簇,以PUT<3_100,frank>的形式存在(其中100是开始时间戳),其次,会有一个Lock聚簇,来负责存放锁的信息,< 3,W,pk,3,100…> 其中W表示是写锁。
提交的第二阶段commit阶段,从pd获取事务结束的时间戳,会由write列簇来记录修改后的内容,put<3_110,100>其中110是commit的时间戳,100是事务开始的时间戳
在完成所有操作后,会对锁进行清楚,在lock列簇中,将变为PUT<D,pk,3,100>,D是delete,表示这个锁已经被删除了。
到这里,整个事务就算完成了。
注:如果有人想读3这个数据,会到write列簇中去进行查到(可以看到最近一次修改是什么时候),会拿到时间戳100的内容去default列簇中去查找,这样就找到了最近一次修改是修改了3为frank。如果在write的列簇中,没有找到修改记录,在lock列簇中找到了它,说明这个数据正在被修改,不可读。
- write列簇
- 当用户写入了一行数据,如果该行数据长度小于255字节,那么会被存储到write列,否则的话该行数据会被放入default列中。(事务修改的值比较小的话,会直接放到write列簇中,这样write列簇中既有提交信息,也有修改信息。)
default列:用于存储超过255字节长度的数据。
tikv如何解决分布式事务
在tidb中,一个数据的修改,有可能存放在不同的kv节点中
例如:
begin:startup timestamp=100的事务获取了开始事务的时间戳。
用户发出commit指令后,则进入两阶段提交,prewrite,将修改的数据写入到tikv,将锁信息也写入到tikv中。
prewrite结束后,进入第二阶段,commit阶段,获得事务结束的时间戳,写入事务的提交信息,并清除锁。
- 当用户写入了一行数据,如果该行数据长度小于255字节,那么会被存储到write列,否则的话该行数据会被放入default列中。(事务修改的值比较小的话,会直接放到write列簇中,这样write列簇中既有提交信息,也有修改信息。)
分布式事务时,如果同时操作
begin
<1,tom> ->><1,jack>
<2,andy>->><2,candy>
commit;
这条语句时,在tikv node1中会按之前的步骤,先把修改的数据放到default column families中,然后在lock column families中生成锁的信息。
这里会有一些区别:
tikv node1:<1,(w,pk,1,100…)>
tikv node2:<2,(w,@1,2,100…)>
在操作多行数据时,加锁的时候只会给数据的第一行加上锁,其他行的数据是指向的形式@1,有没有锁跟着主锁走。
其中w表示的是写锁,@1表示主锁是1
以上是第一阶段prewrite阶段,后面进入第二阶段 commit阶段。与上并无区别。
第一在write column families中写入事务的更改的信息,加上commit 时间戳。
第二清除掉相关的锁信息
这里会有另一个问题,事务的原子性,因为每个tikv 节点都提交了不同行的数据,每个节点在完成2阶段后,数据就持久化了,但是在其他节点处理时如果遇到问题,就会导致这个事务的一部分数据持久化,一部分数据不会完成持久化。这里tidb没有提交的节点在write column families中没有看到更改后的数据,则会到lock columns families中去查看,看到了指向@1的锁信息,然后继续去@1的节点去进行查看,会发现@1的锁已经是删除状态,表明已经完成了二阶段提交,这时没有提交的节点就会赶紧在write column familes中补上更改的数据,然后清楚lock中的锁信息
- MVCC
在上面修改数据的过程中,修改10w条数据时,如果全部加锁,都不能读写的话,对并发的能力就太差了,这里就引进了MVCC技术,来防止这种情况,保障可读。
tikv在修改,插入等,都是采用插入新值的方式来的,原来的数据都在,只是版本号不同,这样就很容易实现MVCC。
读时:从write列簇中,找到提交的数据时间戳,去default中去查找数据。
写时:从write列簇中,找到提交的数据时间戳,不确定是否在事务中,所以去lock中查看有没有锁,这样写入就会被阻塞。在图中,修改id 4时,因为是修改数据,所以要去lock中去查看有没有锁信息,进去后会发现有一个@1的指向锁,所以写的 操作是被阻塞的。
4.Raft
一个raft group中,存在leader和follower,读写只走leader,在raft group中,leader长期没有同步信息就会默认下线,其余的follower会考虑重新竞选。
region在144mb时,进行分裂,同样,数据被删除很小的时候,也可以进行合并region。
-
在写入数据的时候,只会发送给leader角色
- 1.propose
操作被leader收到,leader准备同步,收到请求,将请求准备成raft日志 - 2.append
每条日志记录都有一个标识,是region的号加上日志当前的序号。将我们的raft日志写入到本地的rocksdb中(存放日志的rocksdb),持久化后,返回消息给leader,大部分节点返回成功的消息给leader后,多数派就认为这条数据 - 3.replicate
- 4.commited(多数派才能进行)
- 1.propose
-
raft日志复制
- 1.propose
客户端(tidb server或者其他写入tikv的客户端)写入一条raft log,需要写入一条日志 - 2.append
propose之后,接受到这个raft日志,持久化到raft rocksdb中 - 3.replicate
append:将leader所在tikv 节点的log 分发到其他副本所在节点中,其他节点收到后,也需要持久化到本地的rocksdb中,所以也会有append流程。 - 4.commited
其他副本所在节点,将raft log写入到本地的rocksdb中后,会返回一个响应值给leader,告诉leader已经将收到的raft log持久化了。(超过一半以上节点返回响应值,就属于committed) - 5.apply
真正的将raft日志的操作,应用到rocksdb kv中,数据才真正的持久化。
- 1.propose
-
Raft选举
election timeout=10s(初始状态下)超过这个时间,就会认为这个集群没有leader,期间那个follower突破了10秒没有leader,region就会从follower变更为condidate,变成candidate后,就会再集群内发起选举,告诉其他follower,要竞选leader,这时候就会以term的大小来界定进行投票,之后便会生成leader。
当leader宕机后,heartbeat time interval=10s,在这个时间后,没有收到leader的心跳信息,就会认为leader挂了,会将自己升级为candidate,在剩余的node中发起选举。 -
特殊情况
election timeout(初始阶段)时,多个节点同时到达10秒,这就会导致多个节点都会成为candidate,多个节点会竞争leader。这里会从random(随机从选举计时内每个节点挑选一个随机值来重新开始),这样就解决了这个问题。
election timeout:raft-election-timeout-ticks
hearbeat time interval:raft-heartbeat-ticks
raft-heartbeat-ticks *raft-base-tick-interval(心跳间隔)
raft-election-timeout-ticks *raft-base-tick-interval(心跳间隔)
5.读写与Coprocesser
- 数据的写入
raftstore pool:收到写请求之后,将写请求转化为raft log,持久到自己的rocksdb 日志中,本地持久化后再发送到其他节点的raftstore pool中,等待其他节点也将raftstore pool的数据转化为日志后,会回应一个committed,在大多数节点都响应后,raftstore pool会将这个日志读出来,交由apply pool,apply pool将日志的内容写入到rocksdb kv中。
在pd告诉tiserver关于leader的信息时,这个间隔内不能保证这50毫秒内,leader不会进行切换。
在读之前,向其他follower所在节点,发送一个心跳,让其他follower来回应当前节点是否是leader节点。