推荐使用高性能数据库leveldb
发布于 8 年前 作者 klausgao 21322 次浏览 来自 分享

缘由

前段时间因为一个项目,使用的是mongodb,数据量大致在75w,当然,垃圾服务器,所以就很卡,各种软优化后仍卡得狗带。 过后痛定思痛,开始寻找解决方案。

高性能

机缘巧合下,读到了这2篇文章,是一个博主介绍了他使用leveldb的好处的。 1、我的vps运行4个站点,512M内存1M带宽下博客依然秒开不卡顿的原因是什么? 2、leveldb高性能nosql数据库在node.js环境下如何使用及实例介绍 简单说,就是很快,而且不占内存。

高难度

当然后来就是通过Google和github来了解leveldb,这段过程几次想放弃了,因为都是写各种基础的,levelupleveldown的,简单说,就是只是实现了基础的引擎而已,而像mongodb这样的完整的数据库实现是没有的(就是说你想用mongodb或mongoose的语法来增删改差是没有的)。 是有一些实现了的,但都用在浏览器端的内存数据库比较多,node的没有。例如pouchdb

转机来了

直到有一天,我捕获了这货,linvodb!! 之后我进行了简单的benchmark,见这里linvodb3:325ms mongodb:966ms marsdb:968ms sqlite: 8658ms 果然碾压了mongodb和sqlite。 至于为什么只测了1000次的循环,因为之前一起测试的marsdb,连1000次也完成不了。后来作者修改了bug后,能完成,耗时大致是linvodb的3倍。

我的经验分享

当然,linvodb相对于mongoose来说,易用性还是差点,特别是基于callback的写法,对于我们进入了es6的大家来说,有点不可忍受。不过我还是解决了这个问题,也很简单。(^_^) 下面说3点做法,直接上代码了:(下面的内容需要对linvodb3有一定的了解,才能明白我说的是什么。) 第1种,callback配合co

function docInsert(obj) {
    return function (cb) {
        Doc.insert(obj, cb);
    }
}

//下面就可以使用
    co(function*() {
        for (let i = 0; i < 1000; i++) {
            yield docInsert(
                {
                    string1: 'LinvoDB' + randomize('A', 20),
                    number1: _.now(),
                    indexID: i
                }
            );
        }
        return res.json({msg: 'success'});
    }).catch(function (err) {
        return res.json({msg: 'err'});
    });

这个写法是比较传统的,但是是一定能用的,特别是对于多参数和一些奇怪的情况,一定是OK的。

第2种,用bluebird快速封装linvodb3的简单函数,配合co。

var bb= require("bluebird");
var docInsert = bb.promisify(Doc.insert, {context: Doc});
//为什么要用bb,因为bb的这个函数能将上下文传进去

//下面就可以使用
    co(function*() {
        for (let i = 0; i < 1000; i++) {
            yield docInsert(
                {
                    string1: 'LinvoDB' + randomize('A', 20),
                    number1: _.now(),
                    indexID: i
                }
            );
        }
        return res.json({msg: 'success'});
    }).catch(function (err) {
        return res.json({msg: 'err'});
    });

这种写法能快速封装简单的函数,但是对于以下这种就无能为力了,还是要回到第一种写法。

Planet.find({}).sort({ planet: 1 }).skip(1).limit(2).exec(function (err, docs) {});

第3种,用Promise封装,然后配合co或直接then。

let docUpdate = function(_id, _key, _value){
                let deferred = Promise.defer();
                Doc.update({_id: _id},
                        {$set: {_key: _value}}, {},
                        function (err, numReplaced) {
                            if (err) {
                                deferred.resolve(-1);
                            }
                            else {
                                deferred.resolve(numReplaced);
                            }
                        });
                return deferred.promise;
};
//下面就可以使用
    co(function*() {
            yield docUpdate('_id__11', 'someKey', 'someValue');
        return res.json({msg: 'success'});
    }).catch(function (err) {
        return res.json({msg: 'err'});
    });

和方法1很类似,新的ES6的写法。

我的GUI

还有一点就是,mongodb有很多win和Linux下的GUI来方便我们查看数据,方便开发。 而对于leveldb,我是找到几个号称能支持leveldb的程序,例如FastoNoSql,但都毫无意外的都打不开linvodb的数据库。 经过分析,问题出在node的引擎leveldown上。标准的leveldb的数据文件是sst,而leveldown作大死的弄了个ldb格式。 再找下去也是无果的,所以我自己用electron写了个能支持node的leveldown的leveldb格式的GUI←_←,能用! electron-linvodb-manager,放出来,献丑了。 preview1 preview2 preview3 preview4 preview5 顺便吐槽一下electron的原生模块编译,坑了我2天才解决。 再吐槽electron的打包体积,都是泪啊!

结束语

好了,写到这,终于能愉快的使用高性能的leveldb了!\(☆o☆)/ 另外,希望有兴趣的同学能一起加进来,为leveldb、linvodb的发展多多pr!

想到就增加的话

  1. 很多用低配置VPS和Expressjs的同学,安利一个基于leveldb的sessionStorage:level-session-store。 用法很简单,上代码:
var session = require('express-session');
var LevelStore = require('level-session-store')(session);

app.use(session({
    secret: 'yoxlol',
    name: 'unme8',
    httpOnly: true,
    cookie: {maxAge: 10 * 60 * 1000},
    resave: false,
    saveUninitialized: false,
    store: new LevelStore(path.join(__dirname, 'levelSession.db'))
}));

2.LevelDB的性质是单进程单线程的,所以对于已经打开的leveldb数据库,是被锁定的,别的进程无法访问。所以对于我们开发来说,就比较蛋疼了。例如我们的web程序在运行中,这时需要查看数据库里的变化,就要停止程序(比如node的expressjs),然后才能用我的GUI打开数据库,查看数据。看完就要先关闭我的GUI,再重启PM2,express才能恢复读取数据库。 昨天做了尝试了,看能不能绕过这个问题,但是无果。

22 回复

顶! 只是对于kv类型的数据库,如何通过value来反查key不太明白,谁能解释一下吗

@wusuopu 呃为什么会有那么奇葩的需求?

co(function*() { 这里面如果有事务操作怎么写? 假设有事务

先select A 如果有A且 符合xx条件 ,再insert B 如果 Binsert成功 再upate A

如果 写多条sql 那实际执行的时候顺序是不是随机的?

@yakczh co 的每一个yield不是并行的,是按照顺序执行的,只有yield [promise1, promise2, promise3]的时候,才是并行的。所以你说的这个执行顺序是没有问题的。如果你要考虑执行出错了事务回滚,就要在co的catch里捕捉错误信息,再自己处理咯。

@klausgao 就比如上面引用的那个帖子,使用leveldb来存储博客内容,key是文章id,value包含文章标题、文章正文等内容 如果想要搜索标题包含某些内容的文章时,该如何操作比较方便呢

@wusuopu 明天回你,手机打字不方便。另外你对key-value 系统理解错了

electron的原生模块编译 我愣是没有成功,请问楼主是怎么解决的? 谢谢!

<div data-reactid=".0.1.0.5.2.0.0"><p data-reactid=".0.1.0.5.2.0.0.$0"><span data-reactid=".0.1.0.5.2.0.0.$0.0">mark</span></p></div>

@151263 哈哈这个真心是技术活,我在leveldown的issue里写了个方法,你可以参考一下。 https://github.com/Level/leveldown/issues/190 拉到最下面就是了。 另外那条帖子的方法都可以参考一下,各人的情况各不同,都有可能成功的。

@DevinXian @wusuopu key-value存储你可以认为就是存了一个json,类似与这样

{
	"_id":"_id11111",
	"title":"title1",
	"content":"content1"
}

而对于noSql这类数据库,为什么能高性能,其实所有的trick就是用空间换时间,就是如何建立索引。 所以,分析好各种查询,建立相应的索引,而不单单是对_id进行索引,才能快 快 快。而传统的sql普遍进行全文扫描。

LinvoDB is based on NeDB, the most significant core change is that it uses LevelUP as a back-end, meaning it doesn't have to keep the whole dataset in memory. LinvoDB also can do a query entirely by indexes, meaning it doesn't have to scan the full database on a query. 

而如果要做标题的关键字搜索,我们其实都习惯了mssqlserver mysql这些的like了,其实内部也是有个全文索引引擎在工作的。 leveldb也不例外,也需要相应的全文索引的实现。 我github了一下,找到以下这2货: https://github.com/eugeneware/levels https://github.com/fergiemcdowall/search-index 可以实现。看了sdk,应该不复杂。

@klausgao 感谢回复,等有时间也研究下

@klausgao 兄弟弄个mac版本,或者linux版本

@caipeng-hrv linux可以有,但是mac就搞不了了,实在是穷人啊! 求告诉具体的Linux (x86/x86_64)?

@caipeng-hrv 另外我用centos7.1编译,应该没问题吧?要编译leveldown和electron。

@klausgao Linux(x86_64) ,不知道,不过我这用的是ubuntu14.04

@caipeng-hrv 周末回家再编译了,公司垃圾网络,下了几次都下不成

@coDelesLie 已编译好了linux版本,请到我的github去下载吧。https://github.com/klausgao/electron-linvodb-manager

@caipeng-hrv 已编译好了linux版本,请到我的github去下载吧。https://github.com/klausgao/electron-linvodb-manager

改了些bug,请之前下载的重新下载哦

回到顶部