一文搞懂leveldb写操作
Posted 神技圈子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞懂leveldb写操作相关的知识,希望对你有一定的参考价值。
前言
leveldb一直也它优秀的写性能而文明,本篇文章我们就来分析下leveldb的写流程。来搞懂为什么它的写性能如此优秀
整理流程
leveldb一次写入分为两部分:
- 第一步先将写操作写入日志。
- 第二步将写操作应用到内存数据库中。
在这里先写入日志的目的是为了保障写操作不丢失。
写类型
写类型有两种。第一种是Put,第二种是Delete。其实本质上说这两种操作是一种类型的,因为delete操作实际上是value为空的Put操作。
batch
无论是Put操作、Delete操作还是批量操作,在底层都会创建一个batch实例作为数据库操作的最小执行储单元。首先来看一看batch的架构。
在batch中每条数据项都按照上述格式进行编码。每条数据项编码后的第一位是这条数据项的类型。类型分为更新和删除操作。之后就是key的长度,数据项key的内容。如果不是删除操作,则还要加上value的长度,value的内容。
此外,batch还会维护一个size值,该值用于表示数据量的大小。该值为所有key和value长度的累加。以及额外8个字节用来存储一条数据的额外信息。
key值编码
当数据项从batch写入内存数据库时,需要将key值转换。所以,在leveldb内部所有的数据项的key值都是经过内部特殊编码的。这种格式称为internal key。
ineternal key在用户key的基础上,尾部增加了8个字节。用于存储该操作对应的sequence number和类型。
其中,每一个操作都会被赋予一个sequence number。每进行一次操作,sequence number累加1。leveldb的更新和删除操作都是采用append的方式,而不是直接更新原数据。所以对应同一个key,会有多个版本的数据记录。sequence number最大的值就表示最新的记录。
此外,leveldb的snapshort也是基于sequence number实现的,即一个sequence number代表着一个数据库版本。
合并写
leveldb在面对并发写入时做了一个优化。同一时刻,只允许一个写操作把内容写入到日志和内存数据库中。 为了在写入进程比较多的情况下减少日志文件从而增加写入性能采取了将“小写入”合并到“大写入”。
具体流程如下:
第一个获取到写锁的写操作
1、第一个写入操作获取写锁。
2、在当前写入的数据量未达到合并上限而且还有其它写操作pending的情况下。将其它写入操作合并到自身。
3、如果写入操作已经达到上限或者无其它写操作需要pengding的情况下,将所有内容写入文件并写入数据库文件中。
4、通知每一个被合并到的写操作最后的写入结果,释放或移交写锁。
流程如下图所示:
其它写操作
1、等待写锁或者等待被合并
2、如果被合并判断合并是否成功,若成功则等待写入结果。反之则说明获取写锁的写操作已经oversize了,此时,直接从上一个占有写锁的写操作接过写锁并写入
3、如果未被合并,则继续等待合并或者写锁。
原子性
levledb任意一个写操作的原子性是基于日志来实现的。任何一条写操作的所有内容都将作为日志的一条记录写入到日志文件中。
一般有两种异常情况:
1、日志未开始或者写到一半,进程异常退出。这种情况可能存储一个写操作的部分写已经记录到日志文件中了,但仍然有部分写未被记录。这种情况当服务器恢复重启后读到这条日志发现异常会选择直接丢弃或者退出,从而保证了数据库的原子性
2、写日志完成,进程异常退出。这种情况下写入日志成功,但是数据未持久化。当服务器恢复重启后通过redo 日志实现数据写入也从而保证了数据库的原子性。
以上是关于一文搞懂leveldb写操作的主要内容,如果未能解决你的问题,请参考以下文章
图文详解一文全面彻底搞懂HBaseLevelDBRocksDB等NoSQL背后的存储原理:LSM-tree日志结构合并树...