針對使用非塊運行和塊運行並發壓測對比

該文使用源碼地址:地址

為什麼會有這個實驗

由於cnode上的一篇提問 node.js單線程,是不是就不用消息隊列了啊?

我當時的回答是

舉個例子,你要在數據表查是否有同名的,沒同名則插入。因為node無法按整個請求的sql塊作為執行單元,而是按sql條作為了執行單元,那麼按上面的來說兩個select會先後到達,而第一條insert並沒有插入,導致第二個select查詢為空,還會繼續插入用戶。(如果表小可能不會出現,表大必現)而消息隊列則相當於是已將請求的整個sql塊作為執行單元,即完成了整個請求的select和insert,才會執行下一個請求的select和insert。

這個回答是根據我以往在node遇到的坑,來回答的。貌似很有道理,畢竟我們平時執行node都是按條作為非同步的最小單元執行的。

但是事後我想,那為什麼不能將node塊做為順序執行單位呢?

沒錯,它確實可以

遂在當天晚上做了一個簡單的塊運行庫,簡單測試了一下,感覺好像是實現了,但由於業務繁忙(產品需求激增),所以一直沒有時間去做優化和針對資料庫的實際的測試!遂於周末來測試一下。

我這邊實現了三套針對資料庫的並發測試

  1. 樂觀鎖抗並發
  2. 事務加樂觀鎖抗並發
  3. 塊執行加樂觀鎖抗並發

主要代碼如下

樂觀鎖抗並發

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 (看來神奇又是必然的,嘿嘿)

最後

還有誰不服!簡直就是並發小神奇啊!如果是個人建站抗並發的話足夠了!無須事務照樣抗並發,性能杠杠的!

對結果有疑問的同學可以自行測試,注意兩點:

  1. 測試前要 curl 127.0.0.1:8088/clear 保證資料庫沒有被之前測試污染
  2. sql和全部代碼都在此處
  3. 測試前要 npm install
  4. 此次測試使用的塊執行庫是block-run的1.0.8版本: 塊執行包地址 塊執行倉庫地址
  5. 補充一下,如果要涉及數據回滾,最好在塊執行中要加上事務,塊執行高效的原因其實就在於保證成功率,這裡沒有測加事務的塊執行的版本,我相信加事務的塊執行的效率還是會比非塊執行的事務版本要高,有興趣的可以測試一下

推薦閱讀:

你用 Node.js 寫過哪些大型/複雜的應用?碰到什麼難點?
Web 前端 IDE 用的都是什麼啊?
如何在docker里部署nodejs?
為什麼nodejs不給每一個.js文件以獨立的上下文來避免作用域被污染?
如何評價node-fibers?

TAG:Nodejs | MySQL | 並發 |