rocksdb engine 写逻辑

# rocksdb engine 写逻辑

## 执行路径

DB::Put(key, value)是一个写操作简单封装, 最终都会打包一个WriteBatch对象,调用rocksdb::DBImpl::WriteImpl来完成写。

也可以手工构造一个WriteBatch,作为一个批量事务操作,放入多个key/value操作,一次提交。

```cpp

#0  rocksdb::MemTable::Add (this=0x1619800, s=1, type=rocksdb::kTypeValue, key=..., value=..., allow_concurrent=false, post_process_info=0x0) at db/memtable.cc:425

#1  0x00000000005c25c2 in rocksdb::MemTableInserter::PutCF (this=0x7fffffffa8b0, column_family_id=0, key=..., value=...) at db/write_batch.cc:869

#2  0x00000000005bdfe7 in rocksdb::WriteBatch::Iterate (this=0x7fffffffb0c0, handler=0x7fffffffa8b0) at db/write_batch.cc:381

#3  0x00000000005bfa41 in rocksdb::WriteBatchInternal::InsertInto (writers=..., sequence=1, memtables=0x1616100, flush_scheduler=0x1608808, ignore_missing_column_families=false, log_number=0, db=0x1608000,

concurrent_memtable_writes=false) at db/write_batch.cc:1206

#4  0x00000000004c85ae in rocksdb::DBImpl::WriteImpl (this=0x1608000, write_options=..., my_batch=0x7fffffffb0c0, callback=0x0, log_used=0x0, log_ref=0, disable_memtable=false) at db/db_impl.cc:4953

#5  0x00000000004c6b72 in rocksdb::DBImpl::Write (this=0x1608000, write_options=..., my_batch=0x7fffffffb0c0) at db/db_impl.cc:4585

#6  0x00000000004ccd99 in rocksdb::DB::Put (this=0x1608000, opt=..., column_family=0x15d6500, key=..., value=...) at db/db_impl.cc:5803

#7  0x00000000004c69f0 in rocksdb::DBImpl::Put (this=0x1608000, o=..., column_family=0x15d6500, key=..., val=...) at db/db_impl.cc:4560

```

## WriteBatch

一个WriteBatch就是一个事务,里面会有很多条操作记录,可以调用WriteBatch.Put/Delete...等操作加入操作(Key/Value)

WriteBatch.rep_ 是一个binary buffer, 用于存储batch中所有操作的记录,格式如下:

WriteBatch.content\_flags_ 标记batch中含有的操作类型集合。

field | length  | description |

---------:| :----- |:-----

kHeader | FixInt64 | 序列号,单调递增, Batch sequence的起始值

Count | FixInt32 | 操作记录个数

Type | FixInt8 + Var32Int | 操作类型 + column_family(if != 0)

Key | Var32String | key binary buffer

Value | Var32String | value binary buffer

Type/Key/Value每个记录重复一条, kHeader/Count batch对象共用

## rocksdb::DBImpl::WriteImpl

- 新建一个WriteThread::Writer对象,关联到传入的batch object.

- 调用write\_thread_.JoinBatchGroup(&w);

### Group Commit

为了提高commit性能,存储引擎会将很多线程的并发write合并到一个group,批量写日志,write memory table,然后一次性commit.

JoinBatchGroup调用LinkOne(w, &linked_as_leader);将当前write\_thread_中的writer连接成一个链表,其中write\_thread_.newest\_writer_是链表的头,是最新加入的follower,而第一个加入链表的也就是当前group的leader(link_older=nullptr).

follower(newest_writer) --*link_older*--> follower --*link_older*--> follower --*link_older*--> **leader** ----> nullptr

如果当前的Writer成为了Leader,那么返回做剩下的提交逻辑,如果当前已经有了Leader,需要等待Write.state成为STATE_GROUP_LEADER | STATE_PARALLEL_FOLLOWER | STATE_COMPLETED

- As Leader

- 检查是否需要Flush, 如果需要,找出所有column_family中最大的MemTable的CF,调用SwitchMemtable,冻结当前active memtable, 调用SchedulePendingFlush调度刷盘。

- 取当前的versions_的LastSequence。 开始持有DBImpl::mutex

- 调用write\_thread_.EnterAsBatchGroupLeader,这个函数确定当前提交的批次应该包含哪些数据

- 计算当前可以批量提交的最大长度max_size; 如果leader.size<128KB max_size=leader.size+128KB,如果>128KB,max_size=1MB.

- 调用CreateMissingNewerLinks(newest_writer),将整个链表的反向链接建立起来(link_newer),成了一个双向链表。

- 从leader开始反向遍历,一直到newest_writer,  累加每个batch的size,一直到max_size,超过之后截断,同时检查每个writer的sync/no_slowdown/disableWAL是否一致,不一致的地方也开始截断,用last_writer标记链表的结束位置,作为函数输出参数返回。w->callback->AllowWriteBatching(),也可以设置不想被batch的writer. 并且把符合条件的writer batch都push到write_group的vector中。

- 检查是否可以parallel提交,条件有几个:1. memtable支持,2.allow_concurrent_memtable_write设置,3.write_group 有多个batch, 4. batch中没有merge操作。

- 确定当前提交的group的current_sequence=last_sequence+1(作为起始sequence), 并且将sequence先进行占位,一次性**为write_group中每个batch的每个操作记录都分配一个sequence**. (注意writer.ShouldWriteToMemtable标记为false的不计入sequence)

- 将write_group中的每个batch的数据都append到一个新的WriteBatch对象merged_batch中(tmp\_batch_),如果group中只有一个batch, 那么就用这个batch,没必要拷贝数据了。

- 设置新的merged_batch对象的sequence为current_sequence(起始sequence)

- 写WAL,用的数据就是merged\_batch中的rep_ (如果是多个batch,那么tmp\_batch_可以清理了)

- 如果不允许并发:串行执行write memtable,调用WriteBatchInternal::InsertInto将write_group中所有数据串行写入到memtable.

- 并行执行(concurrent_memtable_writes)

- WriteThread::ParallelGroup 建立一个并行写memtable group,pg.leader/last_writer分别指向链表的头和尾。

- write\_thread_.LaunchParallelFollowers: 设置链表中每个writer.sequence为之前分配好的sequence,(注意每个batch分配自己的一段,调用InsertInto的时候再每个Key设置自己的sequence),设置writer.state为STATE_PARALLEL_FOLLOWER

- 作为Leader的writer batch开始写memtable

- 调用write\_thread_.CompleteParallelWorker(&w)判断是不是最后一个完成write memtable的线程

- 正常情况下(如果early_exit_allowed为false),只有leader会最后做收尾工作,因此即便leader不是最后一个写完memtable的线程,也会等待writer.state == STATE\_COMPLETED 才会退出, 并返回true,表示需要做最后的提交工作(update versions_.LastSequence),leader的STATE_COMPLETEDe是由最后一个退出的follower线程设置的。

- follower线程如果不是最后一个完成工作的线程,那么会一直等到writer.state == STATE_COMPLETED退出。

- follower线程如果是最后一个完成工作的线程,那么会先把group.leader.state设置为STATE_COMPLETED,然后等待自己变成STATE_COMPLETED,退出。

- 如果 CompleteParallelWorker返回了true(leader等到了STATE\_COMPLETED或者自己就是最后一个),做提交动作更新全局的sequence: versions_->SetLastSequence(last_sequence);

- 调用write\_thread_.ExitAsBatchGroupLeader

- 注意当前的newest\_writer_可能已经加了很多新的write batch进来了,在上一次commit的过程中,新进来的write batch还会一直往write\_thread_的链表上挂,但是本次提交的截止的点在EnterAsBatchGroupLeader时候确定的,因此退出的时候会将本次提交链还剩余的writer链,重新建立好反向链接,设置紧接着的writer为新的Leader,之前调用JoinBatchGroup等待的线程,又可以继续执行下一批事务。

- 遍历write_group中所有的writer, 设置所有writer的state为STATE_COMPLETED,这样还在调用CompleteParallelWorker的follower线程就会退出。

- As follower

- JoinBatchGroup之后如果没有成为Leader,那就等着Leader线程在LaunchParallelFollowers的时候把自己设置为follower状态,一旦设置完,就进入follower写memtable逻辑,最后判断CompleteParallelWorker是否可以退出,一般是等待所有的batch都干完活以后退出。

- 如果allow_concurrent_memtable_write没有打开, follower线程会一直等待Leader干完所有的事情,最后调用ExitAsBatchGroupLeader设置状态为STATE_COMPLETED后直接退出。

### writer链表在group commit过程中的变化

![link_list](rocksdb_writer_link_list.png)

### writer对象状态变迁图

![state_flow](rocksdb_writer_state_flow.png)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容