上次在云函数里面整了一个嵌入式的SQL数据库以后爽的连云开发数据库都不想用了。不过有的时候还是需要用到kv存储,那能不能也serverless一把呢?level就是一个还不错的选择。打包一个层以后直接引用就可以了:
代码语言:javascript复制'use strict';
const { Level } = require('level')
// Create a database
const db = new Level('/tmp/example', { valueEncoding: 'json' })
exports.main_handler = async (event, context) => {
var n = 10000,d=Date.now();
// Add an entry with key 'a' and value 1
for(var i=0;i<n;i ){
await db.put(Math.random().toString(16).substring(2), Math.random())
}
console.log("插入" n "个记录耗时" (Date.now()-d) "毫秒")
// Add multiple entries
let batch=[];
for(var i=0;i<n;i ){
batch.push({type:'put',key:Math.random().toString(16).substring(9), value:Math.random()})
}
d=Date.now();
await db.batch(batch)
console.log("批量插入" n "个记录耗时" (Date.now()-d) "毫秒")
// Get value of key 'a': 1
d=Date.now();
for(var i=0;i<n;i ){
try{
let v = await db.get(Math.random().toString(16).substring(9));
if(v) console.log("got value:" v)
}catch(e){
if(e.code != "LEVEL_NOT_FOUND")
console.log(e)
}
}
console.log("查询" n "个记录耗时" (Date.now()-d) "毫秒")
return "all done"
};
(纯测试,保存路径用了/tmp/ 实际使用的时候应该挂上CFS)
这个level似乎是纯JS实现,比起通过node-gyp用C实现了关键计算的sqlite,读写性能上并没有太大优势,不过多一个选择还是不错的。以后小应用就可以纯云函数实现小规模提供服务了,小并发的时候性能甚至可能比云数据库服务更好。规模上去的时候再更换存储方案大部分主要的逻辑也能沿用。
facebook的rocksDB 是另一个选择。它和sqlite一样使用了node-gyp本地构建的方式,让人期待了一下它会不会有更好的性能表现。依赖node-gyp的层直接在mac上打包上传到linux服务器上是用不了的,因此使用了docker的linux nodejs环境环境搭建
代码语言:javascript复制echo "cd /usr/src;npm install rocksdb --save">tmp.sh
chmod x tmp.sh
docker run --rm -v "$PWD":/usr/src node:16 /usr/src/tmp.sh
zip -r rocksdb.zip node_modules
rm -rf node_modules tmp.sh package.json package-lock.json
这样就得到了一个layer,超过10M无法上传上来,需要的自己生成一下。
按照leveldown的api运行测试了一下:
代码语言:javascript复制'use strict';
const rocksdb = require('rocksdb')
// Create a database
const db = new rocksdb("/tmp/rocksdb")
async function openDB(){
return new Promise(res=>{
db.open({createIfMissing:true},()=>{
console.log("db opened");
res();
})
})
}
async function closeDB(){
return new Promise(res=>{
db.close(function (err) {
console.log("db closed")
res()
})
})
}
exports.main_handler = async (event, context) => {
await openDB();
var n = 200,d=Date.now();
for(var i=0;i<n;i ){
db.put(Math.random().toString(16).substring(2), Math.random(),{'sync':true},()=>{})
}
console.log("同步插入" n "个记录耗时" (Date.now()-d) "毫秒(同步插入太多DB就崩溃了,并且会干扰后面的异步操作,不推荐)");
await closeDB().then(openDB);//重新打开一次数据库来消除同步操作的干扰
d=Date.now();
for(var i=0;i<n;i ){
await new Promise(res=>{
db.put(Math.random().toString(16).substring(2), Math.random(),()=>{res()})
})
}
console.log("异步步插入" n "个记录耗时" (Date.now()-d) "毫秒(会受到前面同步插入的干扰,需要重新打开一次DB来测试)")
n=10000
let batch = db.batch();
for(var i=0;i<n;i ){
batch.put(Math.random().toString(16).substring(9),Math.random())
}
d=Date.now();
await new Promise(res=>{batch.write(res)})
console.log("批量插入" n "个记录耗时" (Date.now()-d) "毫秒")
d=Date.now();
for(var i=0;i<n;i ){
let v = await new Promise(res=>{db.get(Math.random().toString(16).substring(9),(err,value)=>{
if(err == null)
res(value)
else
res()
})})
if(v) console.log("got value " v)
}
console.log("查询" n "个记录耗时" (Date.now()-d) "毫秒")
await closeDB()
return "all done"
};
除了性能不咋地,数据量上去一点还很容易挂掉,可能使用的姿势还不大对?
还有一些更简单的jsonDB类小玩具,比如lowdb(这个是pure ESM 包,引用的时候要注意一下),jsondb,simple-json-db等,使用简单又各有特色,小数据量玩玩应该都不错。
本来还有一个选择的,BerkeleyDB据说也很香,但是尝试打包一个layer的时候发现接近120M,无法压缩到layer要求的50M以内
代码语言:javascript复制echo "cd /usr/src">tmp.sh
echo "npm init -y ">>tmp.sh
echo "npm install berkeleydb --save">>tmp.sh
chmod x tmp.sh
docker run --rm -v "$PWD":/usr/src node:11 /usr/src/tmp.sh
zip -q -r berkeleydb_node11.zip node_modules
rm -rf node_modules package-lock.json package.json tmp.sh
将来有更多需求的时候再尝试用其他的方式把它打包进来用用吧。
最后,还是觉得就嵌入式数据库而言,sqlite是比较香的。