無鎖多線程代碼的性能調(diào)優(yōu)

000x起始

為了拉近自己與互聯(lián)網(wǎng)公司的思維方式,決定買一本《可伸縮服務(wù)架構(gòu)-框架與中間件》,買回來就看了第一章節(jié),作者以一個(gè)ID生成器作為例子講述了個(gè)非常不錯(cuò)的技術(shù)需求和解決方案,用實(shí)例直觀的說明了可伸縮架構(gòu)的設(shè)計(jì)方式。并且由于ID生成業(yè)務(wù)本身的簡單性質(zhì),整個(gè)項(xiàng)目代碼非常易于理解。但是,我正好注意到作者一句"ID的生成取決于網(wǎng)絡(luò)I/O和CPU的性能,網(wǎng)絡(luò)I/O一般不是瓶頸"。一想這跟我的認(rèn)知不一致啊,一個(gè)號(hào)碼生成器,又沒有密集計(jì)算,又沒有復(fù)雜計(jì)算,cpu成為瓶頸,這不科學(xué)啊。于是就開始查找原先實(shí)現(xiàn)上的一些有性能疑問的地方,開始了這次性能調(diào)優(yōu)的地方。

001x分析

源代碼(https://github.com/cloudatee/vesta-id-generator)里提供了三種鎖的實(shí)現(xiàn),最快的一種工作方式就是無鎖的實(shí)現(xiàn),在我本機(jī)測(cè)試了幾把,其實(shí)挺快了,不過既然懷疑有浪費(fèi)CPU的嫌疑,也值得一看,閱讀代碼后判斷初步出問題的點(diǎn)就是在cas失敗后的重新計(jì)算流程過長。這里需要提一句,ID生成器的業(yè)務(wù)需求十分簡單,不過作者為了達(dá)到可伸縮,容錯(cuò)和分布式做的設(shè)計(jì)確實(shí)又很大的參考價(jià)值,這個(gè)項(xiàng)目幾乎就是一個(gè)可伸縮服務(wù)的最小例子。

010x理想的模型

作者把id生成最核心的部分放在了IdPopulator接口里,而這個(gè)populateId的方法主流程為

- 1.取當(dāng)前時(shí)間戳

- 2.比較上次生成的Id時(shí)間戳

- 3.兩個(gè)時(shí)間戳相同則增加sequnce值

- 4.兩個(gè)時(shí)間戳不同則重置sequnce值

- 5.用cas保存當(dāng)前id

- 6.保存失敗則從1.開始

- 7.保存成功則返回id

查看項(xiàng)目文檔注意到一名叫歷華的開發(fā)已注意到獲取時(shí)間這個(gè)操作可能比較慢,而我則把注意力放在了作者性能測(cè)試結(jié)果中的最大響應(yīng)時(shí)間比較慢這一點(diǎn),我認(rèn)為應(yīng)該是高并發(fā)時(shí)重試次數(shù)過多導(dǎo)致的。所以開始進(jìn)行如下思考,populateId方法其實(shí)只需要保證每次調(diào)用發(fā)出去的id都不同就可以,id中的時(shí)間戳部分的生成其實(shí)可以與它無關(guān),而且在同一個(gè)時(shí)間片中,id的時(shí)間戳總是一致的,不需要反復(fù)取。那如果針對(duì)當(dāng)前時(shí)間片,預(yù)先準(zhǔn)備好一個(gè)queue,把這個(gè)時(shí)間片的最大id生成好并放在里面,那只要不停的pop即可。

011x實(shí)際實(shí)現(xiàn)

實(shí)際寫的時(shí)候發(fā)現(xiàn),既然同一時(shí)間片中的ID的sequnce是連號(hào)的,那也不用放queue里了,用無鎖的方式每次+1后更新即可,應(yīng)該快過在queue里pop。

這樣也省了原本想象的模型里那個(gè)用來填充queue的線程,只需要一個(gè)Timer在每一秒開始時(shí)將當(dāng)前時(shí)間戳和sequnce重置。

100x性能測(cè)試結(jié)果

處理完之后果然平均時(shí)間變?yōu)?.5us,最大響應(yīng)時(shí)間也變?yōu)閭€(gè)位數(shù)的ms,還沒測(cè)試對(duì)rest接口性能的影響,不過至少這個(gè)Id生成服務(wù)就不會(huì)占用過多的cpu了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。