4个协议:
事务层(普通、分布式)
mvcc层
raft层
coprocessor层
其他知识点
持久化写入、读取。
leader选举。
rocksdb用的是lsm tree引擎,增删改都是添加数据,删除就是添加一个delete操作。
1.持久化写入
写入memtable(然后预写入wal),write buffer_size一定量的时候,memtable转成immutable memtable,1个immutable memtable就可以落盘了,到write stall个后会让memtable写入限速。
2.持久化查询
Block cache中缓存着最近最常读的数据
Block cache——memtable——immutable memtable——level0——level1.。。。。。。。。
找key数据的时候,每个sst文件会给出最大值和最小值,如果key在次中,就二分法继续找,不在就下一个文件。
每个文件都有bloom filter(过滤器),如果它说这个key不在这个文件里,那key肯定不在里面,如果说在,那不一定在里面。
3.簇列(cf)
每个cf有自己的block cache、memtable、immutable memtable、sst。多个cf共享几个wal(默认3个)
不同的cf存不同的一张表或者几张表。
4.事务
基础事务
事务的存储流程。
Begin
<3,xxx>——<3,frank>
Commit;
1.新begin,然后从pd中获得时间戳TSO,也就是start_ts
2.把修改的数据读取出来,读到tidb server的内存中,内存中先修改,
3.commit进行两阶段提交
一阶段:prewrite:内存中修改的数据写入到tikv节点中,然后将锁信息写入到tikv中。
细:key+tso+value(3_100,frank)写信息存到default的cf中,update和delete只放新值。给修改的第一行的key加个主锁,锁信息放到lock的cf中 <3,(W,pk,3,100……)> 3是key,W是写锁 ,pk表示主锁,key,100是事务开始时间start_ts,后面的不加锁,依附于第一行。
二阶段:commit:pd组件中要时间TSO(commit_ts),还会写个提交信息
细:pd获取时间commit_ts,在write的cf中写入信息<3_110,100> key+start_ts+commit_ts。再做个锁清理,在lock的cf中插入3,(D,pk,3,100……) D表示锁被删了。
锁信息在内存中,不写到tikv节点中,别人感知不到锁,就是乐观事务。
锁信息提前从内存中写入tikv节点,别人也能在commit前感知到锁,就是悲观事务。
读
先到write的cf中看,最近一次修改时间,拿着key+start_ts到default的cf中找到value。
如果在write的cf中没看到值,但是lock的cf中有,那就不能读(不能读最新的值,可以读老的,也就是mvcc功能)
写
如果数据小于255字节,会直接存储到write的cf中,大于255字节就是正常写到default的cf或者自己指定的cf。
分布式事务流程(数据在多个tikv)
Begin
<1,tom>——<1,jack>
<2,andy>——<2,candy>
Commit;
1.只写新值进去。<1,tom>——<1,jack>只写<1,jack>
2.只有第一行需要加主锁,后面的都是指向锁。
3.update和delete只放新值(put和del)
两行数据放在不同的tikv node上。
1.新begin,然后从pd中获得时间戳TSO,也就是start_ts
2.把修改的数据读取出来,读到tidb server的内存中,内存中先修改,
3.commit进行两阶段提交
一阶段:prewrite(预写):内存中修改的数据写入到tikv节点中,然后将锁信息写入到tikv中。
细:
第一行<1,jack>(放在tikv node1中):
key+tso+value(1_100,jack)写信息存到default的cf中,update和delete只放新值。给修改的第一行的key加个主锁,锁信息放到lock的cf中 <1,(W,pk,1,100……)>,1是key,W是写锁 ,pk表示主锁,key,100是事务开始时间start_ts,后面的不加锁,依附于第一行。
第二行<2,candy>(放在tikv node2中):
key+tso+value(2_100,candy)写信息存到default的cf中,update和delete只放新值。给修改的第二行的key加个锁指向,锁信息放到lock的cf中 <2,(W,@1,2,100……)> ,2是key,W是写锁 ,@1表示锁的指向,指向key1,2表示key,100是事务开始时间start_ts,后面的不加锁,依附于第一行。
二阶段:commit(提交):pd组件中要时间TSO(commit_ts),还会写个提交信息
细:
第一行:
pd获取时间commit_ts,在write的cf中写入信息<1_110,100> key+start_ts+commit_ts。再做个锁清理,在lock的cf中插入1,(D,pk,1,100……) D表示锁被删了。
第二行:
在write的cf中写入信息<2_110,100> key+start_ts+commit_ts。再做个锁清理,在lock的cf中插入2,(D,@1,2,100……) D表示锁被删了。
Tso 120时间 读
看tikv node1的write的cf,看结束时间离120最近的是110,然后开始时间是100,再到default里找value。
2节点一样。
原子性:
如果tikv node1提交了,tikv node2没提交,并且tikv node2宕了。
读取,发现tikv node2的write的cf中没数据,发现lock中有个指向1的锁,然后去node1的lock的cf发现锁已经被删了。在node2的write中补上<2,110_100>,在lock中补上<2,(D,@1,2,100……)>
简单来说:宕了起来后,default中的key和value(数据还在)还在,从1中找到锁的记录,回到2补全commit的二阶段。
5.MVCC(多版本并发控制)
本质就是查不到最新的可以查到老的数据。写会造成堵塞
TSO在120的时候
读取key=1,2,4
1 jack
2 candy
4 tony
事务1完成,事务2未提交,所以读在write里读到的就是上面的值。
假设全部是悲观事务。即使没commit,别人也能感觉到锁。可见图中lock的cf。
假设当前TSO=120
Key=1
想读
在write的cf中读 key(1)离当前时间(120)最近的commit_ts(110)的行,获取到了start_ts(100),拿着开始时间(100)和key(1)去default的cf找value(jack),成功获取值。
想写
在write的cf中读key(1)离当前时间(120)最近的commit_ts(110)的行,获取到了start_ts(100),
拿着开始时间(100)和key(1)去lock的cf找到锁<1,(W,pk,1,115,…..)> ,发现lock中的start_ts(115)大于write的cf中的put<1_110,100>的100,所以有锁且没提交,不能写,造成堵塞。
Key=2
想读
在write的cf中读 key(2)离当前时间(120)最近的commit_ts(110)的行,获取到了start_ts(100),拿着开始时间(100)和key(2)去default的cf找value(candy),成功获取值。
想写
在write的cf中读key(2)离当前时间(120)最近的commit_ts(110)的行,获取到了start_ts(100),拿着开始时间(100)和key(2)去lock的cf找锁,没发现锁,能写,不造成堵塞。
Key=4
想读
在write的cf中读 key(4)离当前时间(120)最近的commit_ts(90)的行,获取到了start_ts(80),拿着开始时间(80)和key(4)去default的cf找value(tony),成功获取值。
想写
在write的cf中读key(4)离当前时间(120)最近的commit_ts(90)的行,获取到了start_ts(80),拿着开始时间(80)和key(4)去lock的cf找到锁<4,(W,@1,115,…..)> ,根据@1去找到key=1的锁,发现有锁存在,所以有锁且没提交,不能写,造成堵塞。
6.raft
组成:
一个region(leader)有2个副本region(follower),只有leader角色才能读写,follower不能读写。
follower长时间收不到统治信息,状态会变成candidate,候选者,然后投票选择新的leader。
复制
1.propose操作
请求更改成日志,写入日志
1_1,log{put key=1,name=tom}
2.append操作
Rocksdb raft接收到了日志,并且持久化到了raft中
3.replicate复制操作
leader的节点的raft的log 复制到其他节点
3.1其他节点接收到日志,持久化到自己的raft中(append操作)
4.committed操作(raft的commit)(与事务的commit不一样)
其他节点持久化完日志后,会返回响应值(我已经把raft log存起来了),超过一半节点返回响应,就算整个写操作commit了。(多数节点都收到了)
5.apply操作
从rocksdb raft写入到rocksdb kv。
7.raft leader 选举
概念:
term为一段时间关系。(阶段1,阶段2,阶段3,………..)
election timeout 长时间没有leader的时间(无主之地,群雄逐鹿时间)
hertbeat time interval 就是follower节点没收到来自leader的统治信息的时间(造反时间)
rardom 多节点时间差不多的情况,每个节点都会从rardom——election timeout范围中随机选个时间值进阶(上帝掷骰时间)
一步快步步快,先成为candidate(候选者),大概率就会成为leader
投票都是包含自己的一票的
初始状态选举leader的规则:
初始状态,节点都是follower,在term1中,时间超过election timeout,没有leader,哪个节点率先突破election timeout就是candidate(候选者)了。给其他节点发起选举请求,并且进程term2。而其他节点如果发现请求方的term大于自己的,就会同意请求,并且自己也进入下个term。超过一半节点同意就成为leader。
leader宕机情况的选举leader的规则:
hertbeat time interval时间一到,没收到leader的统治信息,谁先到时间,谁就进入下个term,变成candidate(候选者),然后通过选举,成为新的leader。
初始同时进阶情况的选举leader的规则(特殊):
多节点时间差不多的情况,每个节点都会从rardom——election time out范围中随机选个时间值进阶,降低重复概率。
election timeout(群雄逐鹿时间)和hertbeat time interval(造反时间),2个时间为ticks个数,而ticks时间单位为raft-base-tick-interval(可调)
8.tikv的数据读写
tikv数据写入
客户发起commit完整流程:
1.propose操作
raftstore pool收到写请求,请求转化成raft log。
2.append操作
将raft log持久化到rocksdb raft中,本机持久化完成
3.replicate复制操作
将raft log复制到其他节点的raftstore pool中
-3.1其他节点接收到日志,持久化到自己的raft中(append操作)
其他节点的raftstore pool收到raft log后,将raft log持久化到本地的rocksdb raft中
4.committed操作(raft的commit)(与事务的commit不一样)
其他节点持久化完raft log后返回完成信息,半数(包含leader自己)以上节点返回完成信息后committed动作完成。
5.apply操作
rafstore pool把raft log读出来,写给apply pool,apply pool拿着raft log,写到rocksdb kv中。
tikv的数据ReadIndex读取
Raft commit:写进rocksdb raft中了
apply:写进rocksdb kv中了
在10:00时1-95、1-96都写进raft了
在10:05时想读1-95,但是1-95还在raft中,kv只到1-93,并记录下了挡球写进raft的1-97为readindex
在10:08时终于1-95写进kv中了,但是前面的读请求还在等待
在10:09时,readindex 97也写进kv了,此时1-95必定写进kv了,所以可读。
tikv的数据lease读取
在开始到election timeout(造反时间)这段时间内,leader不变。
tikv的数据follower读取
leader
follower
在follower读取,在leader写入95,follower向leader要commitindex(比如97),那follower这边要apply到97才能读想要的95。
9.coprocessor(协同处理器)
算子下推:
不一定要从tikv中读出出来,到tidb server中做过滤,
可以在每个tikv节点中过滤自己节点中的数据。然后再tidb server进行校验。
表扫、索引扫、过滤、limit求最大最小值、聚合、统计信息、采样 等等