先說標(biāo)題中的億級(jí)網(wǎng)關(guān),我的角度時(shí)用戶量是億級(jí)琼掠,訪問量按小時(shí)算是時(shí)百億級(jí)別以上拒垃,主要是api網(wǎng)關(guān),也做了老程序php反向代理瓷蛙,區(qū)別點(diǎn)為是否存在頁面悼瓮,是否需要通配。
我們定位為:高性能艰猬,高可用横堡,可擴(kuò)展,可管理冠桃,可治理命贴,安全的網(wǎng)關(guān)產(chǎn)品,作為唯品會(huì)所有流量的入口
網(wǎng)關(guān)價(jià)值:所有中心化控制點(diǎn)都可以統(tǒng)一控制
我們底層技術(shù)選型從servlet到akka到netty,servlet就跳過胸蛛,底層性能模型差太多
線程模型使用netty的boss worker污茵,目前所有業(yè)務(wù)的處理由于不會(huì)阻塞住worker線程,都跑在worker上葬项。
目前性能數(shù)據(jù)為在12核的機(jī)器上泞当,1k payload大小的請求http請求,后端服務(wù)為rpc為9萬 http為7萬多民珍。瓶頸在cpu上
線程模型襟士,我們進(jìn)一步合并了后端請求調(diào)用的woker線程池跟接受請求線程池,考慮連接池可以讓一個(gè)請求的worker線程處理嚷量,跟發(fā)送到后端的請求線程處理在一個(gè)worker上陋桂,這樣性能會(huì)提高8%-10%,因?yàn)樯倭艘淮吻袚Q津肛。
比如說是http到http章喉, 后端http的話就是一個(gè)連接池,拿連接的時(shí)候身坐,怎么判斷拿連接就是當(dāng)前線程所在的連接秸脱,很多連接就可能分在不同的worker上,需要一個(gè)標(biāo)志位部蛇,這個(gè)標(biāo)志位可能可以選擇線程ID或者是線程特點(diǎn)的一個(gè)東西去做一個(gè)標(biāo)志位摊唇,優(yōu)先拿這個(gè),如果沒有我再去拿其他的涯鲁。也看你的模型巷查,http這種非雙工到后端會(huì)需要連接數(shù),可以均勻的分在這個(gè)24個(gè)模塊線程上. 如果是rpc抹腿,可能一兩條連接打天下岛请,就需要盡量不切換或者多創(chuàng)建連接的代價(jià)達(dá)到不切換。
插件化方式需要不在worker線程上運(yùn)行警绩,做到隔離問題不影響崇败,包含類的隔離與線程池隔離,目前想采用共享線程池+獨(dú)立線程池分配模式肩祥,先使用共享后室,沒有可用則獨(dú)立使用一個(gè)小的線程池。
netty這塊來調(diào)優(yōu)實(shí)踐
除了worker線程切換的優(yōu)化混狠,線程安全與共享的方式做了一些事岸霹,可以讓非安全的代碼在線程內(nèi)共享,類似threadlocal将饺,但我們用的netty的fastthreadlocal,更快更好贡避,底層用的是數(shù)組痛黎,而且減少了對象的創(chuàng)建,服用了對象對gc也更友好
netty里比如stringbuild等都這么共享使用贸桶,我們代碼用到也是直接參考netty的用法舅逸。
netty有一個(gè)平臺(tái)類,可以直接使用皇筛,由于我們是jdk7琉历,用這個(gè)netty copy了jdk8采有的一些類,比如hashmap的優(yōu)化水醋,可以直接用旗笔,包括包含mpsc這類
整個(gè)網(wǎng)關(guān)不會(huì)出現(xiàn)任何一處有鎖或者有卡的問題,日志也是全異步拄踪,如果一定有鎖蝇恶,比如說我涉及到一些多個(gè)線程去做共享狀態(tài)改變,那我們就有準(zhǔn)確性的問題惶桐,要求必須準(zhǔn)確撮弧,盡量通cas的方式來搞定,而不是說強(qiáng)行加鎖姚糊,是不會(huì)做一個(gè)強(qiáng)行加鎖贿衍,通常用自旋方式來把這一塊解決掉,整個(gè)性能是比較好的救恨。
使用netty的native epoll 性能更優(yōu)贸辈,只需要很簡單的更改幾個(gè)類名就可以。
這塊性能話我們測下來其實(shí)是有一個(gè)提升的肠槽,就是CPU會(huì)稍微低一點(diǎn)點(diǎn)擎淤,性能沒有太明顯,就CPU稍微比平時(shí)負(fù)載會(huì)低一點(diǎn)秸仙。
bytebuf的問題嘴拢。netty在就是官方文檔當(dāng)時(shí)有幾種對比,但根據(jù)你使用場景可以自己跑一下寂纪,比如你需要解包并且轉(zhuǎn)換所有包內(nèi)容席吴,那么堆外不一定有優(yōu)勢。我們是混合場景弊攘,選擇堆外抢腐。
boss線程數(shù)根據(jù)端口數(shù)決定姑曙,我們只監(jiān)聽一個(gè)襟交,不需要設(shè)置cpu的一半,走這個(gè)到處都有的最佳實(shí)踐伤靠。當(dāng)然如果你設(shè)置一半捣域,其實(shí)也不會(huì)用到啼染,這個(gè)可以看netty源碼邏輯。
就netty我我們目前的話大部分都是說 拿到完整的http做業(yè)務(wù)處理焕梅,但是我們其實(shí)有很多可以前置迹鹅,不需要解析完整的http。比如說贞言,我們的通常是一次請求斜棚,或者是我們的一些非法請求,包括限流防刷這種非法的教練该窗,我們其實(shí)只需要根據(jù)ITP的一個(gè)請求函和請求頭弟蚀,我們就可以做一個(gè)判斷,并不需要把它完整地拆包酗失,我們就可以提前判斷义钉。這塊呢就需要我們把netty的這塊http的解析重寫,netty本身對http是一個(gè)狀態(tài)機(jī)的方式解析规肴。
就說我們通常在做優(yōu)化的時(shí)候捶闸,你到底優(yōu)化哪個(gè)代碼,是去一段段扣代碼還是怎么弄拖刃,我覺得還是不要扣代碼删壮,扣代碼這個(gè)價(jià)值點(diǎn)很難找,就是說你可能覺得這塊可能是不是怎么樣更好序调,我是利用一個(gè)string拼醉锅,然后我如果換一個(gè)方式是不是性能更好?可能效果并不是那么明顯发绢。
還是盡量用工具硬耍,用工具從系統(tǒng)底層到整個(gè)jvm部分最好是能貫通起來,貫通起來也方便于排查問題边酒。整個(gè)如果把這塊是從系統(tǒng)底層到j(luò)vm層经柴,整個(gè)通了之后,你排查問題的速度也比較快墩朦,然后定位問題比較快坯认。
比如說火焰圖,我們跑其實(shí)是會(huì)跑到火焰圖數(shù)據(jù)的氓涣。我們性能測試是會(huì)看一下跑出來的是不是符合我們的預(yù)期牛哺。第一個(gè)就是說系統(tǒng)函數(shù)底層的調(diào)動(dòng)是否符合我們的預(yù)期,系統(tǒng)上的一個(gè)開銷劳吠,然后到j(luò)vm方法棧上的一個(gè)消耗引润,這塊比如說比較簡單的作用,谷歌的火焰圖這個(gè)工具痒玩,netfilx火焰圖的生成淳附,jvm上就得采樣议慰,就你可以設(shè)置采樣頻率,奴曙,采樣下來之后會(huì)拿會(huì)拿到一個(gè)詳細(xì)的一個(gè)圖别凹。
就會(huì)看到我們整個(gè)網(wǎng)關(guān)跑出來一個(gè)結(jié)果,當(dāng)然這里面又有細(xì)節(jié)洽糟,怎么看火焰圖的問題了炉菲,快速識(shí)別一些性能問題。
下面還有java 自帶坤溃。最近不是這個(gè)jmc比較火嘛颁督,就說這jmc開源之后把團(tuán)隊(duì)給裁了净宵。就是jmc這塊自帶的一個(gè)分析工具竣蹦,他其實(shí)也會(huì)到一個(gè)消耗,是整個(gè)的一個(gè)方法調(diào)用屿储,你會(huì)看到哪一塊消耗CPU比較多昭灵,但JMC的話是覺得沒有火焰圖這么比較直觀點(diǎn)吠裆,看你分析哪些問題,各有特點(diǎn)烂完∈愿恚火焰圖可能就更多的在系統(tǒng)函數(shù)上,jvm都可以做到就是整個(gè)都可以看到整個(gè)抠蚣。
剩下就說我在寫代碼的時(shí)候祝旷,比如說一個(gè)選擇,比如說上周的一個(gè)例子嘶窄,就是我們在做這種base64的一個(gè)轉(zhuǎn)碼的過程當(dāng)中怀跛,我們可能一些特殊需求需要轉(zhuǎn),那我現(xiàn)在有這么多工具類柄冲,那我到底用哪個(gè)工具的性能更好呢吻谋?我們通常做法是寫一個(gè)單元測試來跑一下、測一下现横,但是這種單元測試很不可靠漓拾,因?yàn)檫@種單元測試跟最終你去jvm運(yùn)行跑代碼其實(shí)區(qū)別還是很大,首先他會(huì)沒有去做編譯優(yōu)化戒祠,有很多問題骇两。
java官方也是推薦直接基準(zhǔn)測試,用基準(zhǔn)測試來測它到底性能怎么樣姜盈,但是這種基準(zhǔn)測試寫的話低千,最好去把官方的demo讀一遍,它里面其實(shí)有很多坑贩据,比如說你很容易寫一個(gè)for循環(huán)在里面栋操,for循環(huán)還很容易被優(yōu)化掉。 還有就是你解決測試的時(shí)候饱亮,其實(shí)最后值并不返回矾芙,他去跑的時(shí)候就會(huì)把優(yōu)化掉,
系統(tǒng)就會(huì)去跑一塊代碼近上,就認(rèn)為最后這個(gè)值根本沒有用到剔宪,沒用到優(yōu)化就全部蓋掉,全部蓋掉之后壹无,跑出來你會(huì)發(fā)現(xiàn)系統(tǒng)數(shù)據(jù)很好葱绒,但是實(shí)際并不是這個(gè)樣子。所以的話這塊有很多坑斗锭,寫他之前最好去把官方的demo通讀一遍地淀,會(huì)避免很多東西,他的demo也比較詳盡岖是,避免很多錯(cuò)誤的一個(gè)寫法帮毁。我后面附了一個(gè)日常的demo吧,這可能就是上周我們跑64的一個(gè)demo寫的豺撑。比如剛才的坑烈疚,就說明這樣去運(yùn)行的時(shí)候,其實(shí)并沒有返回就不會(huì)處理聪轿,它其實(shí)提供一種方式上處理爷肝,就是你把它消費(fèi)掉,這塊的話會(huì)跑一個(gè)性能數(shù)據(jù)出來陆错,最終結(jié)果會(huì)是這樣的一個(gè)情況灯抛。 這樣話就會(huì)看到那到底選擇哪一個(gè)比較好,比如說我們每秒的調(diào)動(dòng)次數(shù)音瓷,那可能下來發(fā)現(xiàn)確實(shí)JDK8自帶的這個(gè)性能比較好一點(diǎn)牧愁。
下面聊到這個(gè)GC優(yōu)化與排查。Gc優(yōu)化外莲,其實(shí)對網(wǎng)關(guān)來說很難容忍這個(gè)gc問題猪半,因?yàn)樘貏e是高并發(fā)的情況下,比如說在上做促銷的時(shí)候偷线,流量很大的情況下磨确,由于網(wǎng)關(guān)發(fā)生gc導(dǎo)致整個(gè)超時(shí)了是很大一個(gè)問題,所以我們在gc上盡量控制頻率声邦,我們大概現(xiàn)在的情況下是我們能控制到是說一個(gè)月一次cms gc乏奥,但是要想網(wǎng)關(guān)其實(shí)流量很大,每天都有流量過來亥曹,這種大促邓了、更別說這種搶購之類導(dǎo)致的一些流量恨诱。所以我們在這塊做了很多一些優(yōu)化,就是整個(gè)控制它的一個(gè)頻率骗炉,后面會(huì)講到照宝。
然后的話是我們一些常見的踩坑的問題,比如說我們會(huì)遇到這種熔斷句葵、指標(biāo)統(tǒng)計(jì)上的一個(gè)踩坑厕鹃。這可能這一塊的話,比如說熔斷分方法的熔斷和服務(wù)器的乍丈。大家都知道壟斷會(huì)要寫這個(gè)計(jì)數(shù)器剂碴,計(jì)數(shù)器我們可能要統(tǒng)計(jì)容納多少秒內(nèi)失敗次數(shù)等于多少的話,比如說我定義是十秒內(nèi)失敗次數(shù)——請求數(shù)(就有幾個(gè)維度)請求十次轻专,十秒內(nèi)連續(xù)失敗忆矛,或者按幾個(gè)維度來吧,失敗率是60%请垛,我就認(rèn)為這個(gè)服務(wù)不好洪碳,應(yīng)該壟斷。
這樣的話就會(huì)導(dǎo)致我們其實(shí)會(huì)去統(tǒng)計(jì)很多這種指標(biāo)叼屠,這種指標(biāo)就會(huì)帶來一個(gè)問題瞳腌,就是說我們的統(tǒng)計(jì)量太大。比如說我們當(dāng)時(shí)是API網(wǎng)關(guān)其實(shí)就是一個(gè)一個(gè)API沒有什么問題镜雨,但我們現(xiàn)在反向代理就會(huì)帶來這個(gè)問題嫂侍,因?yàn)榉聪虼砩婕耙粋€(gè)通配,后面的這個(gè)方法很多荚坞,所以這個(gè)帶來問題就是熔斷上統(tǒng)計(jì)會(huì)導(dǎo)致我們內(nèi)存的一個(gè)爆掉挑宠。指標(biāo)統(tǒng)計(jì)這個(gè)容量上,也是反向代理的時(shí)候也會(huì)遇到一個(gè)問題颓影,因?yàn)槲覀兤鋵?shí)請求的url是很多的各淀,無數(shù)的url過來,我們?nèi)ソy(tǒng)計(jì)的話诡挂,最后遇到這個(gè)問題碎浇,我們通常會(huì)統(tǒng)計(jì)1秒鐘、10秒鐘這種各種的一個(gè)指標(biāo)璃俗,或者說1分鐘5分鐘奴璃,這種都會(huì)帶來一個(gè)問題。
包括這塊順帶再說一下kafka城豁,包括我們用到kafka苟穆,這個(gè)坑是跳不過去的,原生代碼里面自帶的這個(gè)采樣,采樣的話他會(huì)統(tǒng)計(jì)1分鐘5分鐘15分鐘雳旅,但是我們網(wǎng)關(guān)流量很大的情況下跟磨,這個(gè)根據(jù)jvm的一個(gè)原理,就是說我肯定首先對象進(jìn)入這個(gè)年輕帶攒盈,他年輕帶里面就是倒騰幾次抵拘,就老年代。流量很大沦童,我們這塊這ygc就會(huì)特別頻繁,他就很快進(jìn)入老年代叹话,但很快進(jìn)入老年代偷遗,就會(huì)帶來這個(gè)問題。
那就就相當(dāng)于它的統(tǒng)計(jì)指標(biāo)全部在老年代堆積驼壶。堆積之后氏豌,關(guān)鍵是它采樣就說,可能我采樣指標(biāo)是1分鐘5分鐘15分鐘热凹,old區(qū)直線增長泵喘,就會(huì)觸發(fā)我們的cms gc,這塊我們最后的一個(gè)時(shí)間我們就直接把這塊代碼干掉般妙,因?yàn)槲覀兤鋵?shí)沒有辦法統(tǒng)計(jì)資料纪铺,不用統(tǒng)計(jì)指標(biāo),我們不想它這個(gè)給我們帶來的問題
這塊也是old區(qū)的一個(gè)問題碟渺,就是說我們當(dāng)時(shí)之前是15天左右發(fā)生一次鲜锚,上線之后發(fā)現(xiàn)每天穩(wěn)定增長400兆,就去排查這個(gè)問題苫拍,反查發(fā)現(xiàn)kafka的一個(gè)問題芜繁,反查之后發(fā)現(xiàn)里面有數(shù)據(jù)結(jié)構(gòu)里面有個(gè)跳表,我們反查了一下代碼發(fā)現(xiàn)只有kafka在用绒极,然后這塊就跟蹤到kafka骏令,因?yàn)橐呀?jīng)找到這個(gè)到底是誰關(guān)聯(lián)它。這個(gè)反查到了之后發(fā)現(xiàn)是kafka的問題垄提,我們做了一個(gè)空實(shí)驗(yàn)之后榔袋,發(fā)現(xiàn)這個(gè)其實(shí)問題就解決掉,就回到我們原先那個(gè)頻率上铡俐。
說到這個(gè)日志摘昌,我們剛才說的日志其實(shí)是權(quán)益部的一個(gè)輸出日志,這塊我們是按log4j2的一個(gè)方式處理高蜂,就是二點(diǎn)幾的版本聪黎,但我們在用的時(shí)候2.6還沒出來,但是后面2.6出來是很推薦大家試一下,因?yàn)檫@塊還做什么gc優(yōu)化稿饰,它號(hào)稱是減少了很多gc問題锦秒,我們測下來也是基本上gc耗時(shí)也會(huì)有明顯的一個(gè)降低。
這一塊的話是說我們當(dāng)時(shí)上線的時(shí)候喉镰,我們其實(shí)沒有設(shè)cms gc到底什么時(shí)候觸發(fā)的旅择,比如說都是區(qū),可能通常大家會(huì)設(shè)置到說75%就觸發(fā)了侣姆,然后不設(shè)置生真,我們覺得這塊讓jvm去做可能更好一點(diǎn),就說我們不知道什么時(shí)候觸發(fā)是合理值捺宗,既然我們不知道就讓jvm來自己來做自己來做動(dòng)態(tài)調(diào)整柱蟀。但發(fā)現(xiàn)一個(gè)問題,就是現(xiàn)網(wǎng)上的資料都說蚜厉,包括我們?nèi)プx這個(gè)幾本jvm的書长已,他們也都會(huì)說,這個(gè)JDK你不設(shè)的話昼牛,它默認(rèn)是68%處發(fā)這個(gè)cms到我線上上線之后發(fā)現(xiàn)并不是什么术瓮,我們92%才觸發(fā),我們就看一下我們jdk的一個(gè)版本贰健,我們版本是7胞四,我們就拿了一個(gè)open jdk的原代碼,就算了一下伶椿,算出來撬讽,確實(shí)是92%出發(fā)。
這塊我舉這個(gè)例子意思說悬垃,看到這種問題游昼,其實(shí)通過這個(gè)代碼是很容易找的,我們直接可以對你的jdk版本拉下來之后去找尝蠕,并不需要去相信說我去查各種資料烘豌,發(fā)現(xiàn)沒有,到底是多少觸發(fā)看彼,這塊直接代碼里面很明顯廊佩,其實(shí)也不復(fù)雜,就是雖然說C++這種代碼靖榕,那我們其實(shí)只是從去找一些這種配置的話标锄,就很很容易快速識(shí)別的。代碼里面是沒有秘密的茁计。
還有一個(gè)問題就是說高io導(dǎo)致jvm停頓的問題料皇,這種測試會(huì)發(fā)現(xiàn)一個(gè)問題,說我們在連續(xù)壓測大概四五個(gè)小時(shí),就會(huì)發(fā)現(xiàn)我們的有些四個(gè)9的數(shù)據(jù)践剂,可能不上網(wǎng)看鬼譬,四個(gè)9的數(shù)據(jù),可能會(huì)出現(xiàn)有一秒增加逊脯,其實(shí)我們大部分時(shí)間都是在一毫秒的一個(gè)范圍优质,但是發(fā)現(xiàn)有四個(gè)9會(huì)有一種一秒的特別長的一個(gè)時(shí)間,覺得這可能是一個(gè)問題军洼,我們線上會(huì)這種其實(shí)不太能接受的巩螃,我們線上基本上時(shí)間都是經(jīng)過網(wǎng)絡(luò)的一個(gè)調(diào)動(dòng)后端返回這個(gè)時(shí)間,請求發(fā)起進(jìn)來出去的時(shí)間大概都在一兩毫秒匕争。所以覺得這塊我們是不能接受這一點(diǎn)避乏,然后就去排查問題,就發(fā)現(xiàn)這樣一個(gè)問題說高io導(dǎo)致jvm的停頓
一個(gè)是把gc日志并不直接放磁盤上汗捡,放在這里面放內(nèi)存文件系統(tǒng)上淑际,然后必須加上這個(gè)參數(shù)畏纲,因?yàn)閖vm在運(yùn)行扇住,它會(huì)輸出、關(guān)聯(lián)操作好幾個(gè)文件盗胀,它有一個(gè)文件是你用一些jvm的命令做一些分析用的
后面我們就舉例子來講就做網(wǎng)關(guān)的時(shí)候艘蹋,因?yàn)樽鲞@種對質(zhì)量要求比較高一點(diǎn)。因?yàn)槲覀冞@個(gè)承載的是一線流量票灰,其實(shí)影響是非常大的女阀,對性能也是要求很高,而且又涉及到我們這個(gè)改動(dòng)很頻繁屑迂。各種業(yè)務(wù)方可能有些需求點(diǎn)在我們這我們要去為他們做一些定制化的一些開發(fā)浸策。當(dāng)然可以用插件化來做,但是這塊其實(shí)有很多改動(dòng)惹盼,所以就舉一些場景庸汗,到底應(yīng)該怎么做?
比如說讓你來寫網(wǎng)關(guān)的代碼你應(yīng)該考慮哪些點(diǎn)手报?比如說我們通常做一些抽象蚯舱,我們會(huì)用到一些通用技計(jì)數(shù)器,我們以這個(gè)場景來講掩蛤,我們要去做一些考慮枉昏。這也是拿我們平時(shí)遇到一個(gè)點(diǎn)來發(fā)散的講,就說網(wǎng)關(guān)到底哪些問題揍鸟?
我們通常會(huì)用一個(gè)通用計(jì)數(shù)器這種東西兄裂,因?yàn)槲覀儠?huì)做很多計(jì)數(shù),比如說我們限流,后端的服務(wù):我們要保護(hù)后端服務(wù)我們來做限流懦窘,再比如說我們要做防刷前翎,我們可能要根據(jù)ip或者根據(jù)session各個(gè)維度來做防刷,做成保護(hù)安全上的一個(gè)東西畅涂,或者有一些技術(shù)上的一些需求港华,比如說是熔斷,它其實(shí)也是一個(gè)技術(shù)上的需求午衰,這種技術(shù)啊我怎么來做的立宜?
可能想比較簡單,它就Java自帶的這個(gè)原子類的這種cs臊岸,我就直接上面加就可以橙数。其實(shí)也會(huì)有問題,原子類它的維度比較單一帅戒。來看這樣一個(gè)實(shí)現(xiàn)灯帮,其實(shí)它這里面就有一個(gè)是計(jì)數(shù)的一個(gè)概念,還有一個(gè)時(shí)間的概念逻住,這兩個(gè)概念我們就需要用時(shí)輪的方式來實(shí)現(xiàn)钟哥。那時(shí)間輪的方式就會(huì)帶來一個(gè)問題,用時(shí)間輪的話我就要有一個(gè)清晰的概念在里面瞎访,包括時(shí)間輪怎么快速去查找腻贰,比如說你看時(shí)間輪列表的話,性能上一個(gè)問題扒秸,包括考慮我用時(shí)間輪怎么去避免產(chǎn)生GC或者一系列的問題播演。
我們這塊要實(shí)現(xiàn)一個(gè)滑動(dòng)窗口的一個(gè)時(shí)間輪。那就首先第一個(gè)就是選擇伴奥,到底是提前清理的話写烤,就使用時(shí)清理,提前清理怎么去清理這樣一個(gè)問題拾徙。我們的最后選擇是說我們使用時(shí)清理洲炊。
這樣一個(gè)統(tǒng)計(jì)的話,那么實(shí)現(xiàn)上首先用數(shù)組去實(shí)現(xiàn)锣吼,做對應(yīng)上的一個(gè)映射选浑,數(shù)組里面的對象,但是這就涉及到我們要怎么去清理它玄叠?我們并不我們?nèi)绻苯蛹右话焰i去清理古徒,比如說我們每次去鎖住,它就是每個(gè)線程都會(huì)遇到這個(gè)鎖读恃,這個(gè)性能會(huì)降很低隧膘。所以我們是用一個(gè)自旋鎖的方式代态,因?yàn)槲覀兤鋵?shí)是不停的請求進(jìn)來,我們?nèi)プ孕涂梢粤苏畛裕孕幌戮涂梢员囊桑@塊時(shí)間的話性能是比較好的。
然后這塊還有一個(gè)問題萨驶,就是說那我們那JDK8在后面供應(yīng)對象是明顯的比前面這個(gè)cas一個(gè)原生對象性能高很多了歉摧,但為什么不用后面這個(gè),這就是一個(gè)內(nèi)存上的問題腔呜。比如說用前面說用度量工具測叁温,測完之后發(fā)現(xiàn)前面完全滿足我們的需求,因?yàn)楹竺娴脑挻_實(shí)性能會(huì)好核畴,但是后面的內(nèi)存又會(huì)高很多膝但。大概在60萬的對象,后面是45兆谤草,前面是一點(diǎn)幾兆跟束,就這種內(nèi)存上的一個(gè)對比,我們可能內(nèi)存上接受不了這個(gè)丑孩。
包括這種對象冀宴,我用完之后我要清理掉,對象清理掉之后嚎杨,它就會(huì)帶來gc問題花鹅,我這個(gè)對象已經(jīng)在新生代里面倒騰了幾次氧腰,因?yàn)榱髁亢艽蠓阏悖萘亢艽蟮臅r(shí)候,我就不停的ygc古拴。ygc幾次我就進(jìn)老年帶箩帚,那去的時(shí)候又會(huì)帶來老年代這個(gè)對象增加,清理掉黄痪,解除這個(gè)關(guān)聯(lián)關(guān)系之后紧帕,老一代又會(huì)直線增加,我們想控制1月一次的cms這個(gè)目標(biāo)給它很難達(dá)成了桅打。所以這塊就是我們的作用這樣一個(gè)需求就有很多點(diǎn)需要考慮是嗜。
有相關(guān)的一些點(diǎn)考慮到之后,我們還要把它做成一個(gè)抽象工具挺尾,就是說要用的話鹅搪,我們直接調(diào)就可以做,這些考慮點(diǎn)全部在里面遭铺,而并不是說我們每個(gè)人去實(shí)現(xiàn)一套丽柿,
再說一個(gè)非常有意思的一個(gè)GC場景恢准,其實(shí)我們有些技術(shù)場景,我們會(huì)用到這種lru的方式我們?nèi)ソy(tǒng)計(jì)甫题,因?yàn)椴粔蛴媚倏稹1热缯f我們要統(tǒng)計(jì)一些東西,我們只能用最近最常使用的一個(gè)算法去做坠非,但是這就會(huì)帶來幾個(gè)問題敏沉。我們?nèi)绻阉O(shè)置很大,準(zhǔn)確性倒是挺高的炎码,準(zhǔn)確性提高了之后赦抖,GC問題就來了,如果我設(shè)小了辅肾,但是又準(zhǔn)確性不夠队萤,那我多小合適了,這個(gè)需要根據(jù)流量來矫钓。
這個(gè)問題呢可以進(jìn)一步抽象要尔,其實(shí)會(huì)帶來很多問題,就是說那這個(gè)問題進(jìn)一步抽象到說所有能熬過ygc到了老年帶新娜,并且把它反復(fù)創(chuàng)建問題解除赵辕,其實(shí)都會(huì)有這個(gè)老年帶增長的一個(gè)問題。
比如說我們可能像廣泛的一些日常這種使用概龄,如你在開發(fā)日常代碼的話还惠,肯定會(huì)涉及到連接池,這是一個(gè)比較常見的例子私杜。就說你的連接池會(huì)設(shè)有一個(gè)共享連接蚕键,共享連接的話,幾次ygc后也會(huì)進(jìn)入老年帶衰粹。進(jìn)入老年帶之后锣光,會(huì)有配置一些參數(shù)去把這些共享連接給關(guān)掉動(dòng)態(tài)變化數(shù)量,檢查閑置連接啥的铝耻,會(huì)清理掉誊爹,這個(gè)對象就在老年帶里待著了。但是在老年帶里的連接數(shù)發(fā)現(xiàn)不夠又會(huì)去創(chuàng)建它瓢捉,創(chuàng)建之后就會(huì)频丘,于是這個(gè)隨著流量不停的在老年帶里面創(chuàng)建對象,就會(huì)帶來這個(gè)問題泡态。
最后一點(diǎn)呢就是說我們寫了這么多代碼之后搂漠,發(fā)現(xiàn)一些規(guī)律性的東西,盡可能復(fù)用對象兽赁,盡快釋放對象状答。
如我們在創(chuàng)建一個(gè)對象的話冷守,能復(fù)用盡量復(fù)用,比如說我們用了一些計(jì)數(shù)器惊科,我們移除的時(shí)候拍摇,其實(shí)放在一個(gè)隊(duì)列上,我們要?jiǎng)?chuàng)建什么首先從隊(duì)列上去取馆截。隊(duì)列沒有充活,我們再用一個(gè)就是減少這種垃圾對象地方創(chuàng)建。
netty而且還是很有參考價(jià)值蜡娶,如果你把源碼過一遍的話混卵,可能很有很多想象不到的點(diǎn),會(huì)對你有些價(jià)值窖张。
盡量狀態(tài)無鎖幕随,如果一定狀態(tài),先走共享宿接。先走線程內(nèi)共享赘淮,就是模塊內(nèi)的一個(gè)共享,模塊的共享之后睦霎,如果說發(fā)現(xiàn)還是解決不了問題梢卸,那你沒辦法,你只能cas副女,那cas蛤高,我盡量選擇小的一把鎖,怎么小怎么來碑幅,而并不是我加一把大的讀寫鎖或者怎么樣戴陡,我可能做一個(gè)循環(huán)做一個(gè)自選鎖來做。
最后一點(diǎn)枕赵,這里比較重要猜欺,就是說在寫作代碼位隶,因?yàn)榫W(wǎng)關(guān)的代碼這塊拷窜,入口流量,如果有問題會(huì)影響很嚴(yán)重涧黄。當(dāng)然我們通過一些發(fā)布的方式可以避免篮昧,但這塊的話就是說你在寫的話,盡量了解你代碼你寫的每一行代碼背后的邏輯笋妥。里面內(nèi)部到底是怎么運(yùn)行懊昨,再用這種基準(zhǔn)測試去度量,度量你的代碼到底有多少的性能損耗春宣,到底是怎樣的
我的這塊分享就完了酵颁!