上次在云函数里面整了一个嵌入式的SQL数据库以后爽的连云开发数据库都不想用了。不过有的时候还是需要用到kv存储,那能不能也serverless一把呢?
level 是一个serverless场景下KV存储的一个一个还不错的选择。打包一个层以后直接引用就可以了:
#levelDB.zip#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 'use strict' ;const { Level } = require ('level' )const db = new Level ('/tmp/example' , { valueEncoding : 'json' })exports .main_handler = async (event, context) => { var n = 10000 ,d=Date .now (); for (var i=0 ;i<n;i++){ await db.put (Math .random ().toString (16 ).substring (2 ), Math .random ()) } console .log ("插入" +n+"个记录耗时" +(Date .now ()-d)+"毫秒" ) 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)+"毫秒" ) 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环境环境搭建
1 2 3 4 5 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 运行测试了一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 'use strict' ;const rocksdb = require ('rocksdb' )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以内
1 2 3 4 5 6 7 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是比较香的。