日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

初探性能优化——2个月到4小时的性能提升

發(fā)布時(shí)間:2025/3/21 编程问答 62 豆豆
生活随笔 收集整理的這篇文章主要介紹了 初探性能优化——2个月到4小时的性能提升 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一直不知道性能優(yōu)化都要做些什么,從哪方面思考,直到最近接手了一個(gè)公司的小項(xiàng)目,可謂麻雀雖小五臟俱全。讓我這個(gè)編程小白學(xué)到了很多性能優(yōu)化的知識,或者說一些思考方式。真的感受到任何一點(diǎn)效率的損失放大一定倍數(shù)時(shí),將會是天文數(shù)字。最初我的程序計(jì)算下來需要跑2個(gè)月才能跑完,經(jīng)過2周不斷地調(diào)整架構(gòu)和細(xì)節(jié),將性能提升到了4小時(shí)完成。

  很多心得體會,希望和大家分享,也希望多多批評指正,共同進(jìn)步。

項(xiàng)目描述

我將公司的項(xiàng)目內(nèi)容抽象,大概是要做這樣一件事情。

  1. 數(shù)據(jù)庫A中有2000萬條用戶數(shù)據(jù)
  2. 將數(shù)據(jù)庫A中的用戶讀出,為每條用戶生成guid,并保存到數(shù)據(jù)庫B中
  3. 同時(shí)在數(shù)據(jù)庫A中生成關(guān)聯(lián)表

項(xiàng)目要求為:

  1. 將用戶存入數(shù)據(jù)庫B的過程需要調(diào)用sdk的注冊接口,不允許直接操作jdbc進(jìn)行插入
  2. 數(shù)據(jù)要求可恢復(fù):再次運(yùn)行要跳過已成功的數(shù)據(jù);出錯(cuò)的數(shù)據(jù)要進(jìn)行持久化以便下次可以選擇恢復(fù)該部分?jǐn)?shù)據(jù)
  3. 數(shù)據(jù)要保證一致性:在不出錯(cuò)的情況下,數(shù)據(jù)庫B的用戶必然一一對應(yīng)數(shù)據(jù)庫A的關(guān)聯(lián)表。如果出錯(cuò),那么正確的數(shù)據(jù)加上記錄下來的出錯(cuò)數(shù)據(jù)后要保證一致性。
  4.?速度要盡可能塊:共2000萬條數(shù)據(jù),在保證正確性的前提下,至多一天內(nèi)完成

?

第一版,面向過程——2個(gè)月

特征:面向過程、單一線程、不可拓展、極度耦合、逐條插入、數(shù)據(jù)不可恢復(fù)

  最初的一版簡直是匯聚了一個(gè)項(xiàng)目的所有缺點(diǎn)。整個(gè)流程就是從A庫讀出一條數(shù)據(jù),立刻做處理,然后調(diào)用接口插入B庫,然后在拼一個(gè)關(guān)聯(lián)表的sql語句,插入A庫。沒有計(jì)數(shù)器,沒有錯(cuò)誤信息處理。這樣下來的代碼最終預(yù)測2000萬條數(shù)據(jù)要處理2個(gè)月。如果中間哪怕一條數(shù)據(jù)出錯(cuò),又要重新再來2個(gè)月。簡直可怕。

  這個(gè)流程圖就等同于廢話,是完全基于面向過程的思想,整個(gè)代碼就是在一個(gè)大main方法里寫的,實(shí)際業(yè)務(wù)流程完全等同于代碼的流程。思考起來簡單,但實(shí)現(xiàn)和維護(hù)起來極為困難,代碼結(jié)構(gòu)冗長混亂。而且?guī)缀跏遣豢蓴U(kuò)展的。暫且不談代碼的設(shè)計(jì)美觀,它的效率如此低下主要有一下幾點(diǎn):

  1. 每一條數(shù)據(jù)的速度受制于整個(gè)鏈條中最慢的一環(huán)。試想假如有一條A庫插入關(guān)聯(lián)表的數(shù)據(jù)卡住了,等待將近1分鐘(夸張了點(diǎn)),那這一分鐘jvm完全就在傻等,它完全可以繼續(xù)進(jìn)行之前的兩步。正如你等待雞蛋煮熟的過程中可以同時(shí)去做其他的事一樣。
  2. 向B庫插入用戶需要調(diào)用sdk(HTTP請求)接口,那每一次調(diào)用都需要建立連接,等待響應(yīng),再釋放鏈接。正如你要給朋友送一箱蘋果,你分成100次每次只送一個(gè),時(shí)間全搭載路上了。

?

第二版,面向?qū)ο蟆?1天

特征:面向?qū)ο?、單一線程、可拓展、略微耦合、批量插入、數(shù)據(jù)可恢復(fù)

?

架構(gòu)設(shè)計(jì)

  根據(jù)第一版設(shè)計(jì)的問題,第二版有了一些改進(jìn)。當(dāng)然最明顯的就是從面向過程的思想轉(zhuǎn)變?yōu)槊嫦驅(qū)ο蟆?/p>

  我將整個(gè)過程抽離出來,分配給不同的對象去處理。這樣,我所分配的對象時(shí)這樣的:
  1. 一個(gè)配置對象BatchStrategy。負(fù)責(zé)從配置文件中讀取本次任務(wù)的策略并傳遞給執(zhí)行者,配置包括基礎(chǔ)配置如總條數(shù),每次批量查詢的數(shù)量,每次批量插入的數(shù)量。還有一些數(shù)據(jù)源方面的,如來源表的表名、列名、等,這樣如果換成其他數(shù)據(jù)庫的類似導(dǎo)入,就能供通過配置進(jìn)行拓展了。
  2. 三個(gè)執(zhí)行者:整個(gè)執(zhí)行過程可以分成三個(gè)部分:讀數(shù)據(jù)--處理數(shù)據(jù)--寫數(shù)據(jù),可以分別交給三個(gè)對象Reader,Processor,Writer進(jìn)行。這樣如果某一處邏輯變了,可以單獨(dú)進(jìn)行改變而不影響其他環(huán)節(jié)。
  3. 一個(gè)失敗數(shù)據(jù)處理類:ErrorHandler。這樣每當(dāng)有數(shù)據(jù)出現(xiàn)異常時(shí),便把改數(shù)據(jù)扔給這個(gè)類,在這給類中進(jìn)行寫入日志,或者其他的處理辦法。在一定程度上將失敗數(shù)據(jù)的處理解耦。

  這種設(shè)計(jì)很大程度上解除了耦合,尤其是失敗數(shù)據(jù)的處理基本上完全解耦。但由于整個(gè)執(zhí)行過程仍然是需要有一個(gè)main來分別調(diào)用三個(gè)對象處理任務(wù),因此三者之間還是沒有完全解耦,main部分的邏輯依然是面向過程的思想,比較復(fù)雜。即使把main中執(zhí)行的邏輯抽出一個(gè)service,這個(gè)問題依然沒有解決。

效率問題

?  由于將第一版的逐條插入改為批量插入。其中sdk接口部分是批量傳入一組數(shù)據(jù),減少了http請求的次數(shù)。生成關(guān)聯(lián)表的部分是用了jdbc batch操作,將之前逐條插入的excute改為excuteBatch,效率提升很明顯。這兩部分批量帶來的效率提升,將原本需要兩個(gè)月時(shí)間的代碼,提升到了21天,但依然是天文數(shù)字。

  可以看出,本次效率提升僅僅是在減少http請求次數(shù),優(yōu)化sql的插入邏輯方面做出來努力,但依然沒有解決第一版的一個(gè)致命問題,就是一次循環(huán)的速度依然受制于整個(gè)鏈條中最慢的一環(huán),三者沒有解耦也可以從這一點(diǎn)看出,在其他兩者沒有將工作做完時(shí),就只能傻等,這是效率損失最嚴(yán)重的地方了。

?

第三版,完全解耦(隊(duì)列+多線程)——3天

特征:面向?qū)ο?、多線程、可拓展、完全解耦、批量插入、數(shù)據(jù)可恢復(fù)

?

架構(gòu)設(shè)計(jì)

  該版并沒有代碼實(shí)現(xiàn),但確是過度到下一版的重要思考過程,故記錄在次。這一版本較上一版的重大改進(jìn)之處有兩點(diǎn):隊(duì)列和多線程。

  隊(duì)列:其中隊(duì)列的使用使上一版未完全解耦的執(zhí)行類之間,實(shí)現(xiàn)了完全解耦,將同步過程變?yōu)楫惒?#xff0c;同時(shí)也是多線程能夠使用的前提。Reader做的事就是讀取數(shù)據(jù),并放入隊(duì)列,至于它的下一個(gè)環(huán)節(jié)Processor如何處理隊(duì)列的數(shù)據(jù),它完全不用理會,這時(shí)便可以繼續(xù)讀取數(shù)據(jù)。這便做到了完全解耦,處理隊(duì)列的數(shù)據(jù)也能夠使用多線程了。

  多線程:Processor和Writer所做的事情,就是讀取自身隊(duì)列中的數(shù)據(jù),然后處理。只不過Processor比Writer還承擔(dān)了一個(gè)往下一環(huán)隊(duì)列里放數(shù)據(jù)的過程。此處的隊(duì)列用的是多線程安全隊(duì)列ConcurrentLinkedQueue。因此可以肆無忌憚地使用多線程來執(zhí)行這兩者的任務(wù)。由于各個(gè)環(huán)節(jié)之間的完全解耦,某一環(huán)上的偶爾卡主并不再影響整個(gè)過程的進(jìn)度,所以效率提升不知一兩點(diǎn)。

  還有一點(diǎn)就是數(shù)據(jù)的可恢復(fù)性在這個(gè)設(shè)計(jì)中有了保障,成功過的用戶被保存起來以便再次運(yùn)行不會沖突,失敗的關(guān)聯(lián)表數(shù)據(jù)也被記錄下來,在下次運(yùn)行時(shí)Writer會先將這一部分加入到自己的隊(duì)列里,整個(gè)數(shù)據(jù)的正確性就有了一個(gè)不是特別完善的方案,效率也有了可觀的提升。

效率問題

  雖然效率從21天提升到了3天,但我們還要思考一些問題。實(shí)際在執(zhí)行的過程中發(fā)現(xiàn),Writer所完成的數(shù)據(jù)總是緊跟在Processor之后。這就說明Processor的處理速度要慢于Writer,因?yàn)镻rocessor插入數(shù)據(jù)庫之前還要走一段注冊用戶的業(yè)務(wù)邏輯。這就有個(gè)問題,當(dāng)上一環(huán)的速度慢過下一環(huán)時(shí),還有必要進(jìn)行批量的操作么?答案是不需要的。試想一下,如果你在生產(chǎn)線上,你的上一環(huán)2秒鐘處理一個(gè)零件,而你的速度是1秒鐘一個(gè)。這時(shí)即使你的批量處理速度更快,從系統(tǒng)最優(yōu)的角度考慮,你也應(yīng)該來一個(gè)零件就馬上處理,而不是等積攢到100個(gè)再批量處理。

  還有一個(gè)問題是,我們從未考慮過Reader的性能。實(shí)際上我用的是limit操作來批量讀取數(shù)據(jù)庫,而mysql的limit是先全表查再截取,當(dāng)起始位置很大時(shí),就會越來越慢。0-1000萬還算輕松,但1000萬到2000萬簡直是“寸步難行”。所以最終效率的瓶頸反而落到了讀庫操作上。

?

第四版,高度抽象(一鍵啟動(dòng))——4小時(shí)

特征:面向接口、多線程、可拓展、完全解耦、批量或逐條插入、數(shù)據(jù)可恢復(fù)、優(yōu)化查詢的limit操作

架構(gòu)的思考

  優(yōu)雅的代碼應(yīng)該是整潔而美妙,不應(yīng)是冗長而復(fù)雜的。這一版將會設(shè)計(jì)出簡潔度如第一版,而性能和拓展性超越所有版本的架構(gòu)。

  通過總結(jié)前三版特征,我發(fā)現(xiàn)不論是Reader,Processor,Writer,都有共同的特征:啟動(dòng)任務(wù)、處理任務(wù)、結(jié)束任務(wù)。而Reader和Processor又有一個(gè)共同的可以向下一道工序傳遞數(shù)據(jù)通知下一道工序數(shù)據(jù)傳遞結(jié)束的功能。他們就像生產(chǎn)線上的一個(gè)個(gè)工序,相互關(guān)聯(lián)而又各自獨(dú)立地運(yùn)行著。每一道工序都可以啟動(dòng),瘋狂地處理任務(wù),直到上一道工序通知結(jié)束為止。而第一個(gè)發(fā)起通知結(jié)束的便是Reader,之后便一個(gè)通知下一個(gè),直到整個(gè)工序停止,這個(gè)過程就是美妙的。

  

  因此我們可以將這三者都看做是Job,除了Reader外又都有與上一道工序交互的能力(其實(shí)Reader的上一道工序就是數(shù)據(jù)庫),因此便有了如下的接口設(shè)計(jì)。

  

1 /** 2 * 工作步驟接口. 3 */ 4 public interface Job { 5 void init(); 6 void start(); 7 void stop(); 8 void finish(); 9 }

1 /**2 * 可交互的(傳入,通知結(jié)束).3 */4 public interface Interactive<T> {5 6 /**7 * 開放與外界交互的通道8 */9 void openInteract(); 10 11 /** 12 * 接收外界傳來的數(shù)據(jù) 13 * @param t 14 */ 15 void receive(T t); 16 17 /** 18 * 關(guān)閉交互的通道 19 */ 20 void closeInteract(); 21 22 /** 23 * 是否處于可交互的狀態(tài) 24 * @return true可交互的 false不可交互的活已關(guān)閉交互狀態(tài) 25 */ 26 boolean isInteractive(); 27 28 }

  有了這樣的接口設(shè)計(jì),不論實(shí)現(xiàn)類具體怎么寫,主方法已經(jīng)可以寫出了,變得異常整潔有序。

  只提煉主干部分,去掉了一些細(xì)枝末節(jié),如日志輸出、時(shí)間記錄等。

1 public static void main(String[] args) {2 3 Job reader = Reader.getInstance();4 Job processor = Processor.getInstance();5 Job writer = Writer.getInstance();6 7 reader.init();8 processor.init();9 writer.init(); 10 11 start(reader, processor, processor, processor, writer, writer); 12 13 } 14 15 private static void start(Job... jobs){ 16 for (Job job:jobs) { 17 Thread thread = new Thread(new Runnable() { 18 @Override 19 public void run() { 20 job.start(); 21 } 22 }); 23 thread.start(); 24 } 25 }

?

接下來就是具體實(shí)現(xiàn)類的問題了,這里實(shí)現(xiàn)類主要實(shí)現(xiàn)的是三個(gè)功能:

  1. 接收上一環(huán)的數(shù)據(jù):屬于Interactive接口的receive方法的實(shí)現(xiàn),基于之前的設(shè)計(jì),即是對象中有一個(gè)ConcurrentLinkedQueue類型的屬性,用來接收上一環(huán)傳來的數(shù)據(jù)。
  2. 處理數(shù)據(jù)并傳遞給下一環(huán):在每一個(gè)(有下一環(huán)的)對象屬性中,放入下一環(huán)的對象。如Reader中要有Processor對象,Processor要有Writer,一旦有數(shù)據(jù)需要加入下一環(huán)的隊(duì)列,調(diào)用其receiive方法即可。
  3. 告訴下一環(huán)我結(jié)束了:本任務(wù)結(jié)束時(shí),調(diào)用下一環(huán)對象的closeInteractive方法。而每個(gè)對象判斷自身結(jié)束的方法視情況而定,比如Reader結(jié)束的條件是批量讀取的數(shù)據(jù)超過了一開始設(shè)置的total,說明數(shù)據(jù)讀取完畢,可以結(jié)束。而Processor結(jié)束的條件是,它被上一環(huán)通知了結(jié)束,并且從自己的隊(duì)列中poll不出東西了,證明應(yīng)該結(jié)束,結(jié)束后再通知下一環(huán)節(jié)。這樣整個(gè)工序就安全有序地退出了。不過由于是多線程,所以Processor不能貿(mào)然通知Writer結(jié)束信號,需要在Processor內(nèi)部弄一個(gè)計(jì)數(shù)器,只有計(jì)數(shù)器達(dá)到預(yù)期的數(shù)量的那個(gè)線程的Processor,才能發(fā)起結(jié)束通知。

效率問題:

  正如上一版提出的,Processor的處理速度要慢于Writer,所以Writer并不需要用batch去處理數(shù)據(jù)的插入,該成逐條插入反而是提高性能的一種方式。

  大數(shù)據(jù)量limit操作十分耗時(shí),由于測試部分只是在幾百萬條測試,所以還是大大低估了效率的損失。在幾百萬條可以說每一次limit的讀取都寸步難行。考慮到這個(gè)問題,我選去了唯一一個(gè)有索引并且稍稍易于排序的字段“用戶的手機(jī)號”,(不想吐槽它們設(shè)計(jì)表的時(shí)候居然沒有自增id。。。),每次全表將手機(jī)號排序,再limit查詢。查詢之后將最后一條的手機(jī)號保存起來,成為當(dāng)前讀取的最后一條數(shù)據(jù)的一個(gè)標(biāo)識。下次再limit操作就可以從這個(gè)手機(jī)號之后開始查詢了。這樣每次查詢不論從哪里開始,速度都是一樣的。雖然前面部分的數(shù)據(jù)速度與之前的方案相比慢了不少,但卻完美解決了大數(shù)據(jù)量limit操作的超長等待時(shí)間,預(yù)防了危險(xiǎn)的發(fā)生。

至此,項(xiàng)目架構(gòu)再次簡潔起來,但同第一版相比,已經(jīng)不是同一級別的簡潔了。

?

?

關(guān)于繼續(xù)優(yōu)化的思考

1. Reader部分是單線程在處理,由于讀取是從數(shù)據(jù)庫中,并不是隊(duì)列中,因此設(shè)計(jì)成多線程有些麻煩,但并不是不可,這里是優(yōu)化點(diǎn)

2. 日志部分占有很大一部分比例,2000萬條讀、處理、寫就要有至少6000萬次日志輸出。如果設(shè)計(jì)成異步處理,效率會提升不少。

這就是我本次項(xiàng)目優(yōu)化的心得體會,還望各位大神予以指點(diǎn)。因?yàn)榇a是公司為了避嫌,就不發(fā)到github了,感興趣的大神可以私聊。

?

from:?https://www.cnblogs.com/flashsun/p/7744466.html

《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的初探性能优化——2个月到4小时的性能提升的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。