寫在之前
最近,進行了不少代碼的優(yōu)化的部分熊杨。免得以后忘記,在這里分享給大家盗舰,希望對大家能有點微不足道的幫助晶府!
正文內(nèi)容
JSON對象解析的過程中要盡量避免多次解析的情況;如非必要钻趋,盡量減少JSON對象的反復(fù)解析川陆。
使用Redis或數(shù)據(jù)庫查詢時,如果是上下文有邏輯關(guān)系的代碼蛮位,盡量避免反復(fù)使用同一查詢较沪,原則是:能少查一次就少查一次。查詢結(jié)果建議都要進行一定的非空或其它異常判斷等等失仁。
在進行業(yè)務(wù)邏輯的計算和IO讀寫操作時购对,建議分別使用不同的線程骡苞。例如:業(yè)務(wù)邏輯的計算可以使用CPU密集型線程池贴见;而IO操作可以使用IO型線程霜定。RxJava是一個不錯的選擇工具辖所,值得嘗試!
-
使用重試邏輯時,不要太暴力。
如下圖肆糕,Redis有可能會出現(xiàn)超時的情況浑玛,這里的業(yè)務(wù)又比較重要,所以有必要加上重試邏輯,而加的重試邏輯又太過暴力了俄周。
這樣可能得不到想要的效果排龄,反而會加重Redis的負(fù)擔(dān),建議加上適當(dāng)?shù)耐nD時間。如下圖:
-
查詢數(shù)據(jù)庫或Redis等等時流译,應(yīng)該盡可能給予返回值驹马,前后代碼有先后依賴關(guān)系時,應(yīng)該給予必要的邏輯判斷。盡量不要像下面這樣阎肝,insert進去數(shù)據(jù)庫之后就什么不管了。因為insert操作會可能失敗的,一旦這里失敗引镊,其它有依賴的地方就會產(chǎn)生問題,代碼要盡量能考慮到失敗時的處理篮条。
-
應(yīng)當(dāng)盡量避免大量Redis Key同時(分毫不差的)失效弟头。Redis是單線程模型的,如果大量對象同時失效涉茧,后續(xù)的請求可能會頻繁出現(xiàn)請求超時的問題赴恨。如下圖:
優(yōu)化方案是:可以在失效時間的后邊加上所能容許的隨機時間。如下圖:
Redis要盡量使用池的方式伴栓,應(yīng)該避免使用直連的方式伦连。直接的方式每次都會建立一次TCP連接,而池的方式可以減少TCP連接次數(shù)钳垮,減少TCP握手時間惑淳,提高響應(yīng)速度。 如此類推饺窿,凡是建立對象比較耗時的地方歧焦,都可以適當(dāng)考慮對象池技術(shù)。推薦Common-pool2工具肚医,使用起來較簡單绢馍,不用自己實現(xiàn)。
-
如果使用雙重檢查的方式實現(xiàn)單例時肠套,記得加上volatile關(guān)鍵字舰涌。因為由于編譯器可能會重排序我們的代碼,會導(dǎo)致雙重檢查的編譯結(jié)果和源碼不一致你稚,多線程調(diào)用時也可能會有線程安全問題瓷耙,而volatile可以幫忙我們避免編譯器的重排序。
使用volatile時應(yīng)該注意入宦,該關(guān)鍵字只能保證數(shù)據(jù)的可見性哺徊。只有在狀態(tài)真正獨立于程序內(nèi)其他內(nèi)容時才能使用 volatile 。 推薦閱讀這篇關(guān)于volatile的文章:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
-
對于頻繁操作Redis時乾闰,如果各個操作之間沒有先后聯(lián)系落追,又不用考慮及時返回的結(jié)果,應(yīng)當(dāng)盡量使用pipline的方式來提高效率涯肩。如下圖的使用方式:
如下圖轿钠,可以使用pipline的方式提高效率:
pipline能 幫忙我們批量處理命令巢钓,一次性返回操作結(jié)果,減少TCP交互次數(shù)疗垛,提高效率症汹。但是,pipline的方式不能過度使用贷腕,pipline會把Redis的操作結(jié)果緩存到內(nèi)存中背镇,然后一次性返回給客戶端,pipline的方式依賴內(nèi)存的限制泽裳,操作結(jié)果集盡量不要過大瞒斩。
-
使用Redis的鎖機制的時候,應(yīng)當(dāng)仔細(xì)考慮使用的范圍和方式涮总。
如下圖胸囱,此代碼使用key的存在與否和是否為1來保證原子性操作。但是瀑梗,此代碼仍然不能保證多線程安全問題烹笔。假如有兩個線程A和B,同時進入了isExist的邏輯抛丽,而A線程獲得CPU的資源較早谤职,處理的速度較快,A線程走完整個方法的邏輯時铺纽,B線程仍然在原始位置柬帕。此時哟忍,B線程才獲取CPU資源走下面的邏輯狡门,就會出現(xiàn)數(shù)據(jù)重復(fù)插入的問題。
a. 優(yōu)化的方式是可以把ret=xxx這行代碼移到if(!isExist...)上面锅很,而if語句里面的查詢等操作也可以提到if的上面其馏,盡可能的減少此原子操作的時間。
b. 也可以使用Redis樂觀鎖的方式來保證原子性操作爆安。關(guān)于Redis樂觀鎖(CAS)的實現(xiàn)方式叛复,請自行Google!
-
使用Kafka時扔仓,生產(chǎn)者和消費者建議使用批量的方式來提高吞吐量褐奥,而批量失敗的后果也要進行考慮,批量失敗對結(jié)果的影響肯定要比單一生產(chǎn)或消費大很多翘簇。不是特別建議使用下面的這種方式撬码,雖然程序可以正常跑,但是每次遇到Kafka隊列里有大量數(shù)據(jù)積累時都是加機器的方式解決版保。其實完全可以從代碼角度進行優(yōu)化呜笑,減少機器的使用夫否,提高單機的CPU和內(nèi)存使用率。
-
使用線程池時要注意選擇適當(dāng)?shù)木芙^策略叫胁。
如下圖凰慈,handle方法是在死循環(huán)中被調(diào)用的,所以創(chuàng)建Runnable任務(wù)的速度是非惩斩欤快的微谓,優(yōu)化之前的代碼是沒有拒絕策略的,也就是使用的默認(rèn)策略输钩。下圖是優(yōu)化過的代碼堰酿,之前是沒有第一塊紅色區(qū)域的代碼的。
如下圖张足,從JDK源碼中可以看出触创,線程池處理不了的任務(wù)是放在無界隊列(LinkedBlockingQueue的size=Integer.MAX_VALUE)中的,而這樣就有一個很大的問題为牍。如果消費的速度跟不上哼绑,內(nèi)存中就積累了大量的Task,內(nèi)存使用率會急速上高碉咆。所以為線程池任務(wù)的存放選擇一個合適的拒絕策略就很有必要了抖韩。
如本問題的第一張圖中的第一塊代碼,如果發(fā)生拒絕疫铜,會執(zhí)行executor.getQueue().put(r);
而put是阻塞式的茂浮,會一直等待Queue中有可用空間為止,而被阻塞的線程就是放入Task的線程壳咕,也就是調(diào)用handle的線程席揽,這樣就不會導(dǎo)致Queue無限增大了。
寫在之后
寫著寫著就中午了谓厘,該吃飯了... 下次接著分享... 歡迎批評指正幌羞,交流學(xué)習(xí)!