缘由
前段时间因为一个项目,使用的是mongodb,数据量大致在75w,当然,垃圾服务器,所以就很卡,各种软优化后仍卡得狗带。 过后痛定思痛,开始寻找解决方案。
高性能
机缘巧合下,读到了这2篇文章,是一个博主介绍了他使用leveldb的好处的。 1、我的vps运行4个站点,512M内存1M带宽下博客依然秒开不卡顿的原因是什么? 2、leveldb高性能nosql数据库在node.js环境下如何使用及实例介绍 简单说,就是很快,而且不占内存。
高难度
当然后来就是通过Google和github来了解leveldb,这段过程几次想放弃了,因为都是写各种基础的,levelup和leveldown的,简单说,就是只是实现了基础的引擎而已,而像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,放出来,献丑了。 顺便吐槽一下electron的原生模块编译,坑了我2天才解决。 再吐槽electron的打包体积,都是泪啊!
结束语
好了,写到这,终于能愉快的使用高性能的leveldb了!\(☆o☆)/ 另外,希望有兴趣的同学能一起加进来,为leveldb、linvodb的发展多多pr!
想到就增加的话
- 很多用低配置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才能恢复读取数据库。 昨天做了尝试了,看能不能绕过这个问题,但是无果。
co(function*() {
这里面如果有事务操作怎么写?
假设有事务
先select A 如果有A且 符合xx条件 ,再insert B 如果 Binsert成功 再upate A
如果 写多条sql 那实际执行的时候顺序是不是随机的?
@yakczh co 的每一个yield不是并行的,是按照顺序执行的,只有yield [promise1, promise2, promise3]的时候,才是并行的。所以你说的这个执行顺序是没有问题的。如果你要考虑执行出错了事务回滚,就要在co的catch里捕捉错误信息,再自己处理咯。
@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,应该不复杂。