Node.js使用数据库LevelDB:超高性能kv存储引擎
Node.js
被设计用来做快速高效的网络I/O。它的事件驱动流使其成为一种智能代理的理想选择,通常作为后端系统和前端之间的粘合剂。Node的设计初衷就是为了实现这一目的,但与此同时,它已成功用于构建传统的Web应用程序:一个HTTP服务器,提供为HTML页面或JSON消息响应,并使用数据库存储数据。
尽管其他平台和语言有比较成熟的Web框架并更倾向于使用开源关系型数据库,如MySQL或PostgreSQL,大多数Node Web框架(如Express、Hapi等)并不强制使用任何特定的数据库,甚至根本不强制使用任何类型的数据库。昨天在《浅谈前端异常监控平台实现方案》一文中就提到LevelDB,今天跟大家介绍这个超高性能的Key-Value
数据库LevelDB,同类型的比较流行的有MongoDB,本文暂不介绍MongoDB。
认识LevelDB
LevelDB是Google传奇工程师Jeff Dean和Sanjay Ghemawat开源的一款超高性能Key-Value
存储引擎,以其惊人的读性能和更加惊人的写性能在轻量级NoSql
数据库中鹤立鸡群,此开源项目目前是支持处理十亿级别规模Key-Value
型数据持久性存储的C++ 程序库。在优秀的表现下对于内存的占用也非常小,大量数据都直接存储在磁盘上,可以理解为以空间换取时间。
设计思路
对于普通机械磁盘顺序写的性能要比随机写高很多,而LevelDB的设计思想正是利用了磁盘的这个特性。 LevelDB的数据是存储在磁盘上的,采用LSM-Tree的结构实现。LSM-Tree将磁盘的随机写转化为顺序写,从而大大提高了写速度。为了做到这一点LSM-Tree的思路是将索引树结构拆成一大一小两颗树,较小的一个常驻内存,较大的一个持久化到磁盘,共同维护一个有序的key
空间。写入操作会首先操作内存中的树,随着内存中树的不断变大,会触发与磁盘中树的归并操作,而归并操作本身仅有顺序写。如下图所示:
上图为 LevelDB 整体架构,静态结构主要由六个部分组成:
MemTable(wTable)
:内存数据结构,具体实现是 SkipList。 接受用户的读写请求,新的数据修改会首先在这里写入。Immutable MemTable(rTable)
:当 MemTable 的大小达到设定的阈值时,会变成 Immutable MemTable,只接受读操作,不再接受写操作,后续由后台线程 Flush 到磁盘上。SST Files
:Sorted String Table Files,磁盘数据存储文件。分为 Level0 到 LevelN 多层,每一层包含多个 SST 文件,文件内数据有序。Level0 直接由 Immutable Memtable Flush 得到,其它每一层的数据由上一层进行 Compaction 得到。Manifest Files
:Manifest 文件中记录 SST 文件在不同 Level 的分布,单个 SST 文件的最大、最小 key,以及其他一些 LevelDB 需要的元信息。由于 LevelDB 支持 snapshot,需要维护多版本,因此可能同时存在多个 Manifest 文件。Current File
:由于 Manifest 文件可能存在多个,Current 记录的是当前的 Manifest 文件名。Log Files (WAL)
:用于防止 MemTable 丢数据的日志文件。
粗箭头表示写入数据的流动方向:
- 先写入 MemTable。
- MemTable 的大小达到设定阈值的时候,转换成 Immutable MemTable。
- Immutable Table 由后台线程异步 Flush 到磁盘上,成为 Level0 上的一个 sst 文件。
- 在某些条件下,会触发后台线程对 Level0 ~ LevelN 的文件进行 Compaction。
读操作的流动方向和写操作类似:
- 读 MemTable,如果存在,返回。
- 读 Immutable MemTable,如果存在,返回。
- 按顺序读 Level0 ~ Leveln,如果存在,返回。
- 返回不存在。
上面就是简单的介绍 LevelDB 的设计原理,架构和读写操作的数据流向,因为独特的设计原理,LevelDB很适合查询较少,写操作很多的场景。如果需要进一步了解其设计原理,就需要去学习跳跃表的设计算法,这里就不展开了。
跳跃表是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳跃表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳跃表的插入和删除的工作是比较简单的。
LevelDB特征:
- key和value都是任意长度的字节数组;
- 存储按键排序的数据;
- 提供基本的
put(key, value)
、get(key)
和delete(key)
接口; - 支持批量操作以原子操作进行;
- 可以创建数据全景的snapshot(快照),并允许在快照中查找数据;
- 可以通过前向(或后向)迭代器遍历数据(迭代器会隐含的创建一个snapshot);
- 自动使用Snappy压缩数据;
- 可移植性;
基于以上特性,很多区块链项目都是采用LevelDB来作为数据存储。下面就开始介绍如何在项目中使用LevelDB,本文涉及的代码在项目Pretender-Service。
安装LevelDB
项目是基于Node.js,基础环境就需要自己配置一下。执行一下命令:
npm install level --save 复制代码
接下来还需要安装level-sublevel
,为了方便更好的使用LevelDB,不用自己设计分键空间。
npm install level-sublevel --save 复制代码
还需要为数据生成唯一ID的模块,这里我们安装 cuid
。
npm install cuid --save 复制代码
安装完成后,将在应用程序中使用它,创建一个levelDb的类 ./src/db/levelDb.js
:
添加引用的依赖库
const level = require("level"); const sublevel = require("level-sublevel"); 复制代码
在构造函数中声明两个变量,代码如下:
class LevelDb { constructor(dbPath, options = {}) { this.options = options; this.db = sublevel(level(dbPath, { valueEncoding: "json" })); } } module.exports = LevelDb; 复制代码
在这里,创建了一个导出LevelDB数据库的单例模块。首先需要level模块,并使用它来实例化数据库,并为其提供路径 dbPath
。此路径为 LevelDB
存储数据文件的目录路径。此目录专门用于 LevelDB ,可能在开始时不存在。在这里定义的路径为外部声明的时候传入进来。valueEncoding
定义了使用的值的格式为 json
,支持一下格式:hex
、utf8
、base64
、binary
、ascii
、utf16le
。
添加LevelDB操作方法
现在来为上面定义的类增加方法,常用的方法有 put、get、delete、batch、find。本文只是基本的操作,实际项目需要对数据进行合理的设计。
在构造函数中创建了LevelDB数据库对象this.db
,可以调用其方法。
put
新增或者更新数据。
put(key, value, callback) { if (key && value) { this.db.put(key, value, (error) => { callback(error); }); } else { callback("no key or value"); } } 复制代码
get
获取指定key的数据。
get(key, callback) { if (key) { this.db.get(key, (error, value) => { callback(error, value); }); } else { callback("no key", key); } } 复制代码
delete
删除指定key的数据
delete(key, callback) { if (key) { this.db.del(key, (error) => { callback(error); }); } else { callback("no key"); } } 复制代码
batch
LeveLDB的一个强大功能是,它允许对要自动执行的批处理中的操作进行分组。
batch(arr, callback) { if (Array.isArray(arr)) { var batchList = []; arr.forEach(item); { var listMember = {}; if (item.hasOwnProperty("type")) { listMember.type = item.type; } if (item.hasOwnProperty("key")) { listMember.key = item.key; } if (item.hasOwnProperty("value")) { listMember.value = item.value; } if ( listMember.hasOwnProperty("type") && listMember.hasOwnProperty("key") && listMember.hasOwnProperty("value") ) { batchList.push(listMember); } } if (batchList && batchList.length > 0) { this.db.batch(batchList, (error) => { callback(error, batchList); }); } else { callback("array Membre format error"); } } else { callback("not array"); } } 复制代码
到这里已经完成一个 LevelDB 基础操作类,下面就开始将其应用于项目中。
使用LevelDB
接下来创建一个examples
的文件夹,增加文件index.js
,key-value
数据库存储和关系型数据库有所不一样,关系型数据库每条记录一般都有一个唯一的自增长id,而key-value
数据库需要自己维护一个类似id的唯一key值,这个key值的设计也影响数据查询是否遍历。这里我们只是做一个简单的实例,完整代码如下:
"use strict"; require("../src/utils/logger.js")(2); const { dbConfig } = require("../config"); const LevelDB = require("../src/db/levelDb"); const path = require("path"); const cuid = require("cuid"); const dbHelper = new LevelDB( path.resolve(__dirname, dbConfig.path, dbConfig.folder) ); // 增加用户信息 const administrators = [ { name: "QuintionTang", email: "QuintionTang@gmail.com", password: "123456", id: "ckoyhjqbj0000mzkd1o63e31p", }, { name: "JimGreen", email: "JimGreen@gmail.com", password: "123456", id: "ckoyhjqbk0001mzkdhuq9abo4", }, ]; const keyPrefix = "administrator"; console.info("====>开始插入数据"); const administratorsKeys = []; for (const item of administrators) { const uid = item.id; const keyName = `${keyPrefix}_${uid}`; item.id = uid; dbHelper.put(keyName, item, (error) => { if (error !== null) { administratorsKeys.push(keyName); } }); } console.info("====>开始查找数据"); // 开始查找uid为 ckoyhjqbj0000mzkd1o63e31p 的用户信息 const findUid = "ckoyhjqbj0000mzkd1o63e31p"; const findKeyName = `${keyPrefix}_${findUid}`; dbHelper.find( { prefix: findKeyName, }, (error, result) => { console.info(result); } ); 复制代码
接下来执行实例代码:
cd examples node index.js 复制代码
执行结果如下:
此时查看根目录下 database
就会生成数据库文件。
总结
本文简单介绍了LevelDB的设计原理,在项目中完成LevelDB类的定义,并实现一个简单的数据插入和查询。后续会在项目Pretender-Service完成更加复杂的业务逻辑,实现基本的CURD,敬请关注项目动态。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
开源项目运营新利器:为你的 Gitee 仓库嵌入百度统计
访问数据对开源项目的运营来说是一个非常关键的指标,它能够直观地反映出一个开源项目受欢迎的程度及趋势,以方便作者更全面地了解自己项目的流行度。但目前 Gitee 的访问统计功能仍十分基础,仅对访问仓库的 IP 进行了分日的统计与汇总,无法了解到更详细的访问信息,如分时段访问情况、PV/UV、访问来源等等。 为了满足开源作者们对开源项目的运营需要,Gitee 现已支持在开源仓库中嵌入百度统计。与个人网站嵌入百度统计的功能相同,在 Gitee 的仓库中嵌入百度统计后,你可以了解到仓库的流量分析、来源分析、访问分析等详细数据,帮助开源项目作者更有针对性地、更高效地运营项目。 现在该功能已经在 Gitee 全面上线,只要你拥有自己的开源项目,都可以在「管理」-「仓库设置中」找到该功能,只需五分钟,你就可以将百度统计装进自己的开源项目中。 该功能同时支持独立仓库以及组织、用户名下的多个仓库,点击链接了解更多百度统计在 Gitee 的进阶玩法。
- 下一篇
后Kubernetes时代,每个行业需要定制化的符合自身的云原生战略
本文由谐云CTO苌程撰写,就国内外云原生生态概况做了详细介绍,在国内大厂纷纷构建云原生生态的背景下,谐云认为在各个行业云原生落地需要符合行业属性,具备行业特点。在此基础上,谐云论证了金融、通信、能源行业的云原生体系演进方向,推出了针对金融、通信、能源行业的云原生解决方案,总结了云原生落地实践经验,供大家大家交流分享。 「一、国内外云原生的生态全景图」 1.1CNCF云原生生态全景图 提到云原生,就不得不提到CNCF(Cloud Native Computing Foundation)-云原生行业基金会。CNCF于2015 年7月由Google 牵头成立,隶属于 Linux 基金会,初衷是围绕云原生服务云计算,致力于培育和维护一个厂商中立的开源生态系统,维护和集成开源技术,支持编排容器化微服务架构应用,通过将最前沿的模式民主化,让这些创新为大众所用。 自2016 年 11 月起,CNCF 开始维护 Cloud Native Landscape,汇总流行热门的云原生技术与工具,并加以分类,为企业构建云原生体系提供参考。 截止2021年2月28日,全景图列举了和云原生相关的产品及服务的完整名...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装Docker,最新的服务器搭配容器使用
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池