<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>

          榨干服務器:一次慘無人道的性能優(yōu)化

          共 4286字,需瀏覽 9分鐘

           ·

          2022-07-09 14:05

          背景

          做過2B類系統(tǒng)的同學都知道,2B系統(tǒng)最惡心的操作就是什么都喜歡批量,這不,我最近就遇到了一個惡心的需求——50個用戶同時每人導入1萬條單據(jù),每個單據(jù)七八十個字段,請給我優(yōu)化。

          Excel導入技術選型

          說起Excel導入的需求,很多同學都做過,也很熟悉,這里面用到的技術就是POI系列了。

          但是,原生的POI很難用,需要自己去調(diào)用POI的API解析Excel,每換一個模板,你都要寫一堆重復而又無意義的代碼。

          所以,后面出現(xiàn)了EasyPOI,它基于原生POI做了一層封裝,使用注解即可幫助你自動解析Excel到你的Java對象。

          EasyPOI雖然好用,但是數(shù)據(jù)量特別大之后呢,會時不時的來個內(nèi)存溢出,甚是煩惱。

          所以,后面某里又做了一些封裝,搞出來個EasyExcel,它可以配置成不會內(nèi)存溢出,但是解析速度會有所下降。

          如果要扣技術細節(jié)的話,就是DOM解析和SAX解析的區(qū)別,DOM解析是把整個Excel加載到內(nèi)存一次性解析出所有數(shù)據(jù),針對大Excel內(nèi)存不夠用就OOM了,而SAX解析可以支持逐行解析,所以SAX解析操作得當?shù)脑捠遣粫霈F(xiàn)內(nèi)存溢出的。

          因此,經(jīng)過評估,我們系統(tǒng)的目標是每天500萬單量,這里面導入的需求非常大,為了穩(wěn)定性考慮,我們最后選擇使用EasyExcel來作為Excel導入的技術選型。

          導入設計

          我們以前也做過一些系統(tǒng),它們都是把導入的需求跟正常的業(yè)務需求耦合在一起的,這樣就會出現(xiàn)一個非常嚴重的問題:一損俱損,當大導入來臨的時候,往往系統(tǒng)特別卡。

          導入請求同其它的請求一樣只能打到一臺機器上處理,這個導入請求打到哪臺機器哪臺機器倒霉,其它同樣打到這臺機器的請求就會受到影響,因為導入占用了大量的資源,不管是CPU還是內(nèi)存,通常是內(nèi)存。

          還有一個很操蛋的問題,一旦業(yè)務受到影響,往往只能通過加內(nèi)存來解決,4G不行上8G,8G不行上16G,而且,是所有的機器都要同步調(diào)大內(nèi)存,而實際上導入請求可能也就幾個請求,導致浪費了大量的資源,大量的機器成本。

          另外,我們導入的每條數(shù)據(jù)有七八十個字段,且在處理的過程中需要寫數(shù)據(jù)庫、寫ES、寫日志等多項操作,所以每條數(shù)據(jù)的處理速度是比較慢的,我們按50ms算(實際比50ms還長),那1萬條數(shù)據(jù)光處理耗時就需要 10000 * 50 / 1000 = 500秒,接近10分鐘的樣子,這個速度是無論如何都接受不了的。

          所以,我一直在思考,有沒有什么方法既可以縮減成本,又可以加快導入請求的處理速度,同時,還能營造良好的用戶體驗?

          經(jīng)過苦思冥想,還真被我想出來一種方案:獨立出來一個導入服務,把它做成通用服務。

          導入服務只負責接收請求,接收完請求直接告訴前端收到了請求,結(jié)果后面再通知。

          然后,解析Excel,解析完一條不做其它處理直接就把它扔到Kafka中,下游的服務去消費,消費完了,再發(fā)一條消息給Kafka告訴導入服務這條數(shù)據(jù)的處理結(jié)果,導入服務檢測到所有行數(shù)都收到了反饋,再通知前端這次導入完成了。(前端輪詢)

          如上圖所示,我們以導入XXX為例描述下整個流程:

          1. 前端發(fā)起導入XXX的請求;

          2. 后端導入服務接收到請求之后立即返回,告訴前端收到了請求;

          3. 導入服務每解析一條數(shù)據(jù)就寫入一行數(shù)據(jù)到數(shù)據(jù)庫,同時發(fā)送該數(shù)據(jù)到Kafka的XXX_IMPORT分區(qū);

          4. 處理服務的多個實例從XXX_IMPORT的不同分區(qū)拉取數(shù)據(jù)并處理,這里的處理可能涉及數(shù)據(jù)合規(guī)性檢查,調(diào)用其他服務補齊數(shù)據(jù),寫數(shù)據(jù)庫,寫ES,寫日志等;

          5. 待一條數(shù)據(jù)處理完成后給Kafka的IMPORT_RESULT發(fā)送消息說這條數(shù)據(jù)處理完了,或成功或失敗,失敗需要有失敗原因;

          6. 導入服務的多個實例從IMPORT_RESULT中拉取數(shù)據(jù),更新數(shù)據(jù)庫中每條數(shù)據(jù)的處理結(jié)果;

          7. 前端輪詢的接口在某一次請求的時候發(fā)現(xiàn)這次導入全部完成了,告訴用戶導入成功;

          8. 用戶可以在頁面上查看導入失敗的記錄并下載;

          這就是整個導入的過程,下面就開始了踩坑之旅,你準備好了嗎?

          初步測試

          經(jīng)過上面的設計,我們測試導入1萬條數(shù)據(jù)只需要20秒,比之前預估的10分鐘快了不止一星半點。

          但是,我們發(fā)現(xiàn)一個很嚴重的問題,當我們導入數(shù)據(jù)的時候,查詢界面卡到爆,需要等待10秒的樣子查詢界面才能刷出來,從表象來看,是導入影響了查詢。

          初步懷疑

          因為我們查詢只走了ES,所以,初步懷疑是ES的資源不夠。

          但是,當我們查看ES的監(jiān)控時發(fā)現(xiàn),ES的CPU和內(nèi)存都還很充足,并沒有什么問題。

          然后,我們又仔細檢查了代碼,也沒有發(fā)現(xiàn)明顯的問題,而且服務本身的CPU、內(nèi)存、帶寬也沒有發(fā)現(xiàn)明顯的問題。

          真的神奇了,完全沒有了任何思路。

          而且,我們的日志也是寫ES的,日志的量比導入的量還更大,查日志的時候也沒有發(fā)現(xiàn)卡過。

          所以,我想,直接通過Kibana查詢數(shù)據(jù)試試。

          說干就干,在導入的同時,在Kibana上查詢數(shù)據(jù),并沒有發(fā)現(xiàn)卡,結(jié)果顯示只需要幾毫秒數(shù)據(jù)就查出來了,更多的耗時是在網(wǎng)絡傳輸上,但是整體也就1秒左右數(shù)據(jù)就刷出來了。

          因此,可以排除是ES本身的問題,肯定還是我們的代碼問題。

          此時,我做了個簡單的測試,我把查詢和導入的處理服務分開,發(fā)現(xiàn)也不卡,秒級返回。

          答案已經(jīng)快要浮出水面了,一定是導入處理的時候把ES的連接池資源占用完了,導致查詢的時候拿不到連接,所以,需要等待。

          通過查看源碼,最終發(fā)現(xiàn)ES的連接數(shù)是在RestClientBuilder類中寫死的,DEFAULT_MAX_CONN_PER_ROUTE=10,DEFAULT_MAX_CONN_TOTAL=30,每個路由最大10,總連接數(shù)最大30,而且更操蛋的是,這兩個配置是寫死在代碼里面的,沒有參數(shù)可以配置,只能通過修改代碼來實現(xiàn)了。

          這里也可以做個簡單的估算,我們的處理服務部署了4臺機器,每臺機器一共可以建立30條連接,4臺機器就是120條連接,導入一萬單如果平均分配,每條連接需要處理 10000 / 120 = 83條數(shù)據(jù),每條數(shù)據(jù)處理100ms(上面用的50ms,都是估值)就是8.3秒,所以,查詢的時候需要等待10秒左右,比較合理。

          直接把這兩個參數(shù)調(diào)大10倍到100和300,(關注公號彤哥讀源碼一起學習一起浪)再部署服務,測試發(fā)現(xiàn)導入的同時,查詢也正常了。

          接下來,我們又測試了50個用戶同時導入1萬單,也就是并發(fā)導入50萬單,按1萬單20秒來算,總共耗時應該在 50*20=1000秒/60=16分鐘,但是,測試發(fā)現(xiàn)需要耗時30分鐘以上,這次瓶頸又在哪里呢?

          再次懷疑

          我們之前的壓測都是基于單用戶1萬單來測試的,當時的服務器配置是導入服務4臺機器,處理服務4臺機器,根據(jù)上面我們的架構(gòu)圖,按理說導入服務和處理服務都是可以無限擴展的,只要加機器,性能就能上去。

          所以,首先,我們把處理服務的機器加到了25臺(我們基于k8s,擴容非常方便,改個數(shù)字的事),跑一下50萬單,發(fā)現(xiàn)沒有任何效果,還是30分鐘以上。

          然后,我們把導入服務的機器也加到25臺,跑了一下50萬單,同樣地,發(fā)現(xiàn)也沒有任何效果,此時,有點懷疑人生了。

          通過查看各組件的監(jiān)控,發(fā)現(xiàn),此時導入服務的數(shù)據(jù)庫有個指標叫做IOPS,已經(jīng)達到了5000,并且持續(xù)的在5000左右,IOPS是什么呢?

          它表示一秒讀寫IO多少次,跟TPS/QPS差不多,說明MySQL一秒與磁盤的交互次數(shù),一般來說,5000已經(jīng)是非常高的了。

          目前來看,瓶頸可能在這里,再次查看這個MySQL實例的配置,發(fā)現(xiàn)它使用的是超高IO,實際上還是普通的硬盤,想著如果換成SSD會不會好點呢。

          說干就干,聯(lián)系運維重新購買一個磁盤是SSD的MySQL實例。

          切換配置,重新跑50萬單,這次的時間果然降下來了,只需要16分鐘了,接近降了一半。

          所以,SSD還是要快不少的,查看監(jiān)控,當我們導入50萬單的時候,SSD的MySQL的IOPS能夠達到12000左右,快了一倍多。

          后面,我們把處理服務的MySQL磁盤也換成SSD,時間再次下降到了8分鐘左右。

          你以為到這里就結(jié)束了嘛(關注公號彤哥讀源碼一起學習一起浪)?

          思考

          上面我們說了,根據(jù)之前的架構(gòu)圖,導入服務和處理服務是可以無限擴展的,而且我們已經(jīng)分別加到了25臺機器,但是性能并沒有達到理想的情況,讓我們來計算一下。

          假設瓶頸全部在MySQL,對于導入服務,我們一條數(shù)據(jù)大概要跟MySQL交互4次,整個Excel分成頭表和行表,第一條數(shù)據(jù)是插入頭表,后面的數(shù)據(jù)是更新頭表、插入行表,等處理完了會更新頭表、更新行表,所以按12000的IOPS來算的話,MySQL會消耗我們 500000 * 4 / 12000 / 60= 2.7分鐘,同樣地,處理服務也差不多,處理服務還會去寫ES,但處理服務沒有頭表,所以時間也按2.7分鐘算,但是這兩個服務本質(zhì)上是并行的,沒有任何關系,所以總的時間應該可以控制在4分鐘以內(nèi),因此,我們還有4分鐘的優(yōu)化空間。

          再優(yōu)化

          經(jīng)過一系列排查,我們發(fā)現(xiàn)Kafka有個參數(shù)叫做kafka.listener.concurrency,處理服務設置的是20,而這個Topic的分區(qū)是50,也就是說實際上我們25臺機器只使用了2.5臺機器來處理Kafka中的消息(猜測)。

          找到了問題點,就很好辦了,先把這個參數(shù)調(diào)整成2,保持分區(qū)數(shù)不變,再次測試,果然時間降下來了,到5分鐘了,后面經(jīng)過一系列調(diào)整測試,發(fā)現(xiàn)分區(qū)數(shù)是100,concurrency是4的時候效率是最高的,最快可以達到4分半的樣子。

          至此,整個優(yōu)化過程告一段落。

          總結(jié)

          現(xiàn)在我們來總結(jié)一下一共優(yōu)化了哪些地方:

          1. 導入Excel技術選型為EasyExcel,確實非常不錯,從來沒出現(xiàn)過OOM;

          2. 導入架構(gòu)設計修改為異步方式處理,參考秒殺架構(gòu);

          3. Elasticsearch連接數(shù)調(diào)整為每個路由100,最大連接數(shù)300;

          4. MySQL磁盤更換為SSD;

          5. Kafka優(yōu)化分區(qū)數(shù)和kafka.listener.concurrency參數(shù);

          另外,還有很多其它小問題,限于篇幅和記憶,無法一一講出來。

          后期規(guī)劃

          通過這次優(yōu)化,我們也發(fā)現(xiàn)了當數(shù)據(jù)量足夠大的時候,瓶頸還是在存儲這塊,所以,是不是優(yōu)化存儲這塊,性能還可以進一步提升呢?

          答案是肯定的,比如,有以下的一些思路:

          1. 導入服務和處理服務都修改為分庫分表,不同的Excel落入不同的庫中,減輕單庫壓力;

          2. 寫MySQL修改為批量操作,減少IO次數(shù);

          3. 導入服務使用Redis來記錄,而不是MySQL;

          但是,這次要不要把這些都試一遍呢,其實沒有必要,通過這次壓測,我們至少能做到心里有數(shù)就可以了,真的等到量達到了那個級別,再去優(yōu)化也不遲。

          瀏覽 32
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  豆花视频在线看成人网站 | 无码在线免费观看视频 | 免费在线成人网 | 狼人香蕉28视频在线 | 欧美一级日韩三级 |