針對使用非塊運行和塊運行並發壓測對比
該文使用源碼地址:地址
為什麼會有這個實驗
由於cnode上的一篇提問 node.js單線程,是不是就不用消息隊列了啊?
我當時的回答是舉個例子,你要在數據表查是否有同名的,沒同名則插入。因為node無法按整個請求的sql塊作為執行單元,而是按sql條作為了執行單元,那麼按上面的來說兩個select會先後到達,而第一條insert並沒有插入,導致第二個select查詢為空,還會繼續插入用戶。(如果表小可能不會出現,表大必現)而消息隊列則相當於是已將請求的整個sql塊作為執行單元,即完成了整個請求的select和insert,才會執行下一個請求的select和insert。
這個回答是根據我以往在node遇到的坑,來回答的。貌似很有道理,畢竟我們平時執行node都是按條作為非同步的最小單元執行的。
但是事後我想,那為什麼不能將node塊做為順序執行單位呢?沒錯,它確實可以
遂在當天晚上做了一個簡單的塊運行庫,簡單測試了一下,感覺好像是實現了,但由於業務繁忙(產品需求激增),所以一直沒有時間去做優化和針對資料庫的實際的測試!遂於周末來測試一下。
我這邊實現了三套針對資料庫的並發測試- 樂觀鎖抗並發
- 事務加樂觀鎖抗並發
- 塊執行加樂觀鎖抗並發
主要代碼如下
樂觀鎖抗並發
async function sqlCommon(sqlCommonName = sqlCommon){ let conn; try{ conn = await getConn(); let testSelete = await queryPromise( conn, SELECT * FROM `test` WHERE `name`= ? AND `version`=?, [getName(sqlCount),sqlCount] ); if(testSelete.length>0) { let testRes = await queryPromise( conn, UPDATE `test` SET `name`= ?,`version`=?+1 WHERE `version`=?, [getName(sqlCount+1),sqlCount,sqlCount] ); if(testRes.affectedRows>0) { sqlCount++; } else { console.log(`${sqlCommonName} failed:${sqlCount}`) } } else { console.log(`${sqlCommonName} failed:${sqlCount}`) } conn.release(); } catch(e){ console.log(`${sqlCommonName} failed:${sqlCount}`) conn.release(); // console.log(e.stack) }}
事務加樂觀鎖抗並發
async function sqlTrans(){ let conn; try{ conn = await getConn(); await queryPromise(conn,SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;) beginTransaction(conn); let testSelete = await queryPromise( conn, SELECT * FROM `test` WHERE `name`= ? AND `version`=?, [getName(sqlCount),sqlCount] ); if(testSelete.length>0) { let testRes = await queryPromise( conn, UPDATE `test` SET `name`= ?,`version`=?+1 WHERE `version`=?, [getName(sqlCount+1),sqlCount,sqlCount] ); if(testRes.affectedRows>0) { sqlCount++; } else { console.log(`sqlTrans failed:${sqlCount}`) rollback(conn); } } else { console.log(`sqlTrans failed:${sqlCount}`) } commit(conn); conn.release(); } catch(e){ console.log(`sqlTrans failed:${sqlCount}`) // console.log(e.stack);//這裡會爆出很多事務鎖錯誤 rollback(conn); conn.release(); }}
塊執行加樂觀鎖抗並發(僅僅是將樂觀鎖,放如塊執行的某個渠道,改造很簡單)
樂觀鎖需要的包地址 樂觀鎖需要的倉庫地址
function sqlBlock(){ return new Promise ((reslove,rejected)=>{ BlockRun.run(sqlBlockChannel1,async ()=>{ await sqlCommon(sqlBlock); reslove(true) },3000); });}
主服務入口
http.createServer( async (request, response) => { try { let pathname = url.parse(request.url).pathname; //console.log(`url:http://127.0.0.1:${port}{$pathname}`) let showText = test; switch (pathname) { case /clear: await sqlClear(); showText = clear; break; case /common: await sqlCommon(); showText = common; break; case /trans: await sqlTrans(); showText = trans; break; case /block: await sqlBlock(); showText = block; break; } response.writeHead(200, {Content-Type: text/html}); response.write(showText); response.end(); } catch(e) { console.log(e.stack) } }).listen(port);
其他代碼請看
其他代碼文件地址
運行結果
樂觀鎖抗並發
ab
failed
res
事務加樂觀鎖抗並發(和樂觀幾乎一致,有時事務結果好一個兩個)
ab
failed
res
塊執行加樂觀鎖抗並發
ab
failed (一開始看到沒有數據失敗,感覺還挺神奇的)
res (看來神奇是必然的,嘿嘿)
到這裡有人會說了,你這個ab壓力太小了,當然沒什麼了,其實我想說,主要還是數據和樂觀鎖結果太難看了,我要照顧一下。
大家想看塊執行的牛逼之處就讓大家看個痛快塊執行加樂觀鎖抗並發(並發升級版本)
ab 直接上 -n 10000 -c 100
failed (怎麼還沒更新失敗?神奇?)
res (看來神奇又是必然的,嘿嘿)
最後
還有誰不服!簡直就是並發小神奇啊!如果是個人建站抗並發的話足夠了!無須事務照樣抗並發,性能杠杠的!
對結果有疑問的同學可以自行測試,注意兩點:- 測試前要 curl http://127.0.0.1:8088/clear 保證資料庫沒有被之前測試污染
- sql和全部代碼都在此處
- 測試前要 npm install
- 此次測試使用的塊執行庫是block-run的1.0.8版本: 塊執行包地址 塊執行倉庫地址
- 補充一下,如果要涉及數據回滾,最好在塊執行中要加上事務,塊執行高效的原因其實就在於保證成功率,這裡沒有測加事務的塊執行的版本,我相信加事務的塊執行的效率還是會比非塊執行的事務版本要高,有興趣的可以測試一下
推薦閱讀:
※你用 Node.js 寫過哪些大型/複雜的應用?碰到什麼難點?
※Web 前端 IDE 用的都是什麼啊?
※如何在docker里部署nodejs?
※為什麼nodejs不給每一個.js文件以獨立的上下文來避免作用域被污染?
※如何評價node-fibers?