在云函数中使用真正serverless的kv数据

2022-04-29 12:05:34 浏览数 (1)

上次在云函数里面整了一个嵌入式的SQL数据库以后爽的连云开发数据库都不想用了。不过有的时候还是需要用到kv存储,那能不能也serverless一把呢?level就是一个还不错的选择。打包一个层以后直接引用就可以了:

levelDB.zip
代码语言: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是比较香的。

0 人点赞