<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          祖?zhèn)鞔a如何優(yōu)化性能?

          共 4424字,需瀏覽 9分鐘

           ·

          2022-05-31 16:26

          Hollis的新書(shū)限時(shí)折扣中,一本深入講解Java基礎(chǔ)的干貨筆記!

          為了新朋友能快速進(jìn)入場(chǎng)景,再描述一遍這個(gè)項(xiàng)目的背景,這個(gè)項(xiàng)目是一個(gè)自研的Dubbo注冊(cè)中心,上一張架構(gòu)圖

          • Consumer 和 Provider 的服務(wù)發(fā)現(xiàn)請(qǐng)求(注冊(cè)、注銷、訂閱)都發(fā)給 Agent,由它全權(quán)代理
          • Registry 和 Agent 保持 Grpc 長(zhǎng)鏈接,長(zhǎng)鏈接的目的主要是 Provider 方有變更時(shí),能及時(shí)推送給相應(yīng)的 Consumer。為了保證數(shù)據(jù)的正確性,做了推拉結(jié)合的機(jī)制,Agent 會(huì)每隔一段時(shí)間去 Registry 拉取訂閱的服務(wù)列表
          • Agent 和業(yè)務(wù)服務(wù)部署在同一臺(tái)機(jī)器上,類似 Service Mesh 的思路,盡量減少對(duì)業(yè)務(wù)的入侵,這樣就能快速的迭代了

          這里的Registry就是今天的主角,熟悉Dubbo的朋友可以把它當(dāng)做是一個(gè)zookeeper,不熟悉的朋友可以就把它當(dāng)做是一個(gè)Web應(yīng)用,提供了注冊(cè)、注銷、訂閱接口,雖然它是用Go寫(xiě)的,但本文和Go本身關(guān)系不大,也會(huì)用一些偽代碼來(lái)示意,所以也可以放心大膽地看下去。

          一定要做性能優(yōu)化嗎

          在做性能優(yōu)化之前,我們得回答幾個(gè)問(wèn)題,性能優(yōu)化帶來(lái)的收益是什么?為什么一定要做優(yōu)化性能?不優(yōu)化行不行?

          性能優(yōu)化無(wú)非有兩個(gè)目的:

          • 減少資源消耗,降低成本
          • 提高系統(tǒng)穩(wěn)定性

          如果只是為了降低成本,最好做之前估算一下大概能降低多少成本,如果吭哧吭哧干了大半個(gè)月,結(jié)果只省下了一丁點(diǎn)的資源,那是得不償失的。

          回到這個(gè)注冊(cè)中心,為什么要做性能優(yōu)化呢?

          Dubbo應(yīng)用啟動(dòng)時(shí),會(huì)向注冊(cè)中心發(fā)起注冊(cè),如果注冊(cè)失敗,則會(huì)阻塞應(yīng)用的啟動(dòng)。

          起初這個(gè)項(xiàng)目問(wèn)題并不大,因?yàn)榻尤氲膽?yīng)用并不多,而當(dāng)我接手項(xiàng)目時(shí),接入的應(yīng)用越來(lái)越多。

          話分兩頭,另一邊集團(tuán)也在逐漸使用容器替代虛擬機(jī)和物理機(jī),在高峰期會(huì)用擴(kuò)容的方式來(lái)抗住流量高峰,快速擴(kuò)容就要求服務(wù)能在短時(shí)間內(nèi)大量啟動(dòng),無(wú)疑對(duì)注冊(cè)中心是一個(gè)大的考驗(yàn)。

          而導(dǎo)致這次優(yōu)化的直接導(dǎo)火索是集團(tuán)內(nèi)的一次演練,他們發(fā)現(xiàn)一個(gè)配置中心的啟動(dòng)依賴,性能達(dá)不到標(biāo)準(zhǔn)而導(dǎo)致擴(kuò)容失敗,于是復(fù)盤(pán)下來(lái),所有的啟動(dòng)依賴必須達(dá)到一定的性能要求,而這個(gè)標(biāo)準(zhǔn)被定為1000qps。

          于是就有了本文。

          指標(biāo)度量

          如果不能度量,就沒(méi)法優(yōu)化。

          首先是把幾個(gè)核心接口加上metric,主要是請(qǐng)求量、耗時(shí)(p99 / p95 / p90)、錯(cuò)誤請(qǐng)求量,無(wú)論是哪個(gè)項(xiàng)目,這點(diǎn)算是基本的了,如果沒(méi)加,得好好反思了。

          其次對(duì)項(xiàng)目進(jìn)行一次壓測(cè),不知道現(xiàn)在的性能,后面的優(yōu)化也無(wú)法證明其效果了。

          以注冊(cè)接口為例,當(dāng)時(shí)注冊(cè)的性能大概是40qps,記住這個(gè)值,看我們是如何一步一步達(dá)到1000qps的。

          壓測(cè)成功的請(qǐng)求標(biāo)準(zhǔn)是:p99耗時(shí)在1秒以內(nèi),且無(wú)報(bào)錯(cuò)。

          瓶頸在哪里

          性能優(yōu)化的最關(guān)鍵之處在于找到瓶頸在哪,否則就是無(wú)頭蒼蠅,到處瞎碰。

          注冊(cè)接口到底干了什么呢?我這里畫(huà)個(gè)簡(jiǎn)圖

          • 整個(gè)流程加鎖,防止并發(fā)操作
          • Create App和Create Cluster是創(chuàng)建應(yīng)用和集群,只會(huì)在應(yīng)用第一次創(chuàng)建,如果創(chuàng)建過(guò)就直接跳過(guò)
          • Insert Endpoint是插入注冊(cè)數(shù)據(jù),即ip和port
          • 系統(tǒng)的底層存儲(chǔ)是基于MySQL,Lock和UnLock也是基于MySQL實(shí)現(xiàn)的悲觀鎖

          從這個(gè)流程圖就能看出來(lái),瓶頸大概率在鎖上,這是個(gè)悲觀鎖,而且粒度是App,把整個(gè)流程鎖住,同一時(shí)刻相同應(yīng)用的請(qǐng)只允許一個(gè)通過(guò),可想而知性能有多差。

          至于MySQL如何實(shí)現(xiàn)一個(gè)悲觀鎖,我相信你會(huì)的,所以我就不展開(kāi)。

          為了證明猜想,我用了一個(gè)非常笨但很有效的方法,在每一個(gè)關(guān)鍵節(jié)點(diǎn)執(zhí)行之后,記錄下耗時(shí),最后打印到日志里,這樣就能一眼看出到底哪里慢,果然最慢的就是加鎖。

          鎖優(yōu)化

          在優(yōu)化鎖之前,我們先搞清楚為什么要加鎖,在我反復(fù)測(cè)試,讀代碼,看文檔之后,發(fā)現(xiàn)事情其實(shí)很簡(jiǎn)單,這個(gè)鎖是為了防止App、Cluster、Endpoint重復(fù)寫(xiě)入。

          為什么防止重復(fù)寫(xiě)入要這么折騰呢?一個(gè)數(shù)據(jù)庫(kù)的唯一索引不就搞定了?這無(wú)法考證,但現(xiàn)狀就是這樣,如何破解呢?

          • 首先是看這些表能否加唯一索引,有則盡量加上
          • 其次數(shù)據(jù)庫(kù)悲觀鎖能否換成Redis的樂(lè)觀鎖?

          這個(gè)其實(shí)是可以的,原因在于客戶端具有重試機(jī)制,如果并發(fā)沖突了,則發(fā)起重試,我們堵這個(gè)概率很小。

          上面兩條優(yōu)化下來(lái)只解決了部分問(wèn)題,還有的表實(shí)在無(wú)法添加唯一索引,比如這里App、Cluster由于一些特殊原因無(wú)法添加唯一索引,他們發(fā)生沖突的概率很高,同一個(gè)集群發(fā)布時(shí),很可能是100臺(tái)機(jī)器同時(shí)拉起,只有一臺(tái)成功,剩余99臺(tái)在創(chuàng)建App或者Cluster時(shí)被鎖擋住了,發(fā)起重試,重試又可能沖突,大家都陷入了無(wú)限重試,最終超時(shí),我們的服務(wù)也可能被重試流量打垮。

          這該怎么辦?這時(shí)我想起了剛學(xué)Java時(shí)練習(xí)寫(xiě)單例模式中,有個(gè)叫「雙重校驗(yàn)鎖」的東西,我們看代碼

          public?class?Singleton?{
          ????private?static?volatile?Singleton?instance?=?null;
          ????private?Singleton()?{
          ????}
          ????private?static?Singleton?getInstance()?{
          ????????if?(instance?==?null)?{
          ????????????synchronized?(Singleton.class)?{
          ????????????????if?(instance?==?null)?{
          ????????????????????instance?=?new?Singleton();
          ????????????????}
          ????????????}
          ????????}
          ????????
          ????????return?instance;
          ????}
          }

          再結(jié)合我們的場(chǎng)景,App和Cluster只在創(chuàng)建時(shí)需要保證唯一性,后續(xù)都是先查詢,如果存在就不需要再執(zhí)行插入,我們寫(xiě)出偽代碼

          app?=?DB.get("app_name")
          if?app?==?null?{
          ????redis.lock()
          ????app?=?DB.get("app_name")
          ????if?app?==?null?{
          ????????app?=?DB.instert("app_name")
          ????}
          ????redis.unlock()
          }

          是不是和雙重校驗(yàn)鎖一模一樣?為什么這樣會(huì)性能更高呢?因?yàn)锳pp和Cluster的特性是只在第一次時(shí)插入,真正需要鎖住的概率很小,就拿擴(kuò)容的場(chǎng)景來(lái)說(shuō),必然不會(huì)走到鎖的邏輯,只有應(yīng)用初次創(chuàng)建時(shí)才會(huì)真正被Lock。

          性能優(yōu)化有一點(diǎn)是很重要的,就是我們要去優(yōu)化執(zhí)行頻率非常高的場(chǎng)景,這樣收益才高,如果執(zhí)行的頻率很低,那么我們是可以選擇性放棄的。

          經(jīng)過(guò)這輪優(yōu)化,注冊(cè)的性能從40qps提升到了430qps,10倍的提升。

          讀走緩存

          經(jīng)過(guò)上一輪的優(yōu)化,我們還有個(gè)結(jié)論能得出來(lái),一個(gè)應(yīng)用或集群的基本信息基本不會(huì)變化,于是我在想,是否可以讀取這些信息時(shí)直接走Redis緩存呢?

          于是將信息基本不變的對(duì)象加上了緩存,再測(cè)試,發(fā)現(xiàn)qps從430提升到了440,提升不是很多,但蒼蠅再小,好歹是塊肉。

          CPU優(yōu)化

          上一輪的優(yōu)化效果不理想,但在壓測(cè)時(shí)注意到了一個(gè)問(wèn)題,我發(fā)現(xiàn)Registry的CPU降低的很厲害,感覺(jué)瓶頸從鎖轉(zhuǎn)移到了CPU。說(shuō)到CPU,這好辦啊,上火焰圖,Go自帶的pprof就能干。

          可以清楚地看到是ParseUrl占用了太多的CPU,這里簡(jiǎn)單科普下,Dubbo傳參很多是靠URL傳參的,注冊(cè)中心拿到Dubbo的URL,需要去解析其中的參數(shù),比如ip、port等信息就存在于URL之中。

          一開(kāi)始拿到這個(gè)CPU profile的結(jié)果是有點(diǎn)難受的,因?yàn)镻arseUrl是封裝的標(biāo)準(zhǔn)包里的URL解析方法,想要寫(xiě)一個(gè)比它還高效的,基本可以勸退。

          但還是順騰摸瓜,看看哪里調(diào)用了這個(gè)方法。不看不知道,一看嚇一跳,原來(lái)一個(gè)請(qǐng)求里的URL,會(huì)執(zhí)行過(guò)程中多次解析URL,為啥代碼會(huì)這么寫(xiě)?可能是其中邏輯太復(fù)雜,一層一層的嵌套,但各個(gè)方法之間的傳參又不統(tǒng)一,所以帶來(lái)了這么糟糕的寫(xiě)法,

          這種情況怎么辦呢?

          • 重構(gòu),把URL的解析統(tǒng)一放在一個(gè)地方,后續(xù)傳參就傳解析后的結(jié)果,不需要重復(fù)解析
          • 對(duì)URL解析的方法,以每次請(qǐng)求的會(huì)話為粒度加一層緩存,保證只解析一次

          我選擇了第二種方式,因?yàn)檫@樣對(duì)代碼的改動(dòng)小,畢竟我剛接手這么龐大、混亂的代碼,最好能不動(dòng)就不動(dòng),能少動(dòng)就少動(dòng)。

          而且這種方式我很熟悉,在Dubbo的源碼中就有這樣的處理,Dubbo在反序列化時(shí),如果是重復(fù)的對(duì)象,則直接走緩存而不是再去構(gòu)造一遍,代碼位于org.apache.dubbo.common.utils.PojoUtils#generalize

          截取一點(diǎn)感受下

          private?static?Object?generalize(Object?pojo,?Map?history)?{
          ????...
          ????Object?o?=?history.get(pojo);
          ????if?(o?!=?null)?{
          ????????return?o;
          ????}
          ????history.put(pojo,?pojo);
          ????...
          }

          根據(jù)這個(gè)思路,把ParseUrl改成帶cache的模式

          func?parseUrl(url,?cache)?{
          ????if?cache.get(url)?!=?null?{
          ????????return?cache.get(url)
          ????}
          ????u?=?parseUrl0(url)
          ????cache.put(url,?u)
          ????return?u
          }

          因?yàn)槭菚?huì)話級(jí)別的緩存,所以每個(gè)會(huì)話會(huì)new一個(gè)cache,這樣能保證一個(gè)會(huì)話中對(duì)相同的url只解析一次。

          可以看下這次優(yōu)化的成果,qps直接到1100,達(dá)到目標(biāo)~

          最后說(shuō)兩句

          可能有人看完就要噴了,這哪是性能優(yōu)化?這分明是填坑!對(duì),你說(shuō)的沒(méi)錯(cuò),只不過(guò)這坑是別人挖的。

          本文就以一種最小的代價(jià)來(lái)搞定對(duì)祖?zhèn)鞔a的性能優(yōu)化,當(dāng)然并不是鼓勵(lì)大家都去取巧,這項(xiàng)目我也正在重構(gòu),只是每個(gè)階段都有不同的解法,比如老板要求你2周內(nèi)接手一個(gè)新項(xiàng)目,并完成性能優(yōu)化上線,重構(gòu)是不可能的。

          希望通過(guò)本文你能學(xué)到一些性能優(yōu)化的基本知識(shí),從為什么要做的拷問(wèn)出發(fā),建立度量體系,找出瓶頸,一步一步進(jìn)行優(yōu)化,根據(jù)數(shù)據(jù)反饋及時(shí)調(diào)整優(yōu)化方向。



          我的新書(shū)《深入理解Java核心技術(shù)》已經(jīng)上市了,上市后一直蟬聯(lián)京東暢銷榜中,目前正在6折優(yōu)惠中,想要入手的朋友千萬(wàn)不要錯(cuò)過(guò)哦~長(zhǎng)按二維碼即可購(gòu)買(mǎi)~


          長(zhǎng)按掃碼享受6折優(yōu)惠




          往期推薦

          消息隊(duì)列原理和選型:Kafka、RocketMQ 、RabbitMQ 和 ActiveMQ


          技術(shù)總監(jiān)被開(kāi)除了....


          重鎊!虛擬機(jī)巨頭 VMware 將被收購(gòu)




          有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號(hào)


          好文章,我在看??

          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久草香蕉视频 | 亚洲少妇婷婷 | 天天干,夜夜操 | www.色色撸 | 大香蕉久久在线 |