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

          性能提升了200%!

          共 7052字,需瀏覽 15分鐘

           ·

          2021-08-05 17:00

          最近不少運營同事找到我說:咱們的數(shù)據(jù)校對系統(tǒng)越來越慢了,要過很久才會顯示出校對結(jié)果,你能不能快速優(yōu)化一下呢?我:好的,我先了解下業(yè)務(wù),后續(xù)優(yōu)化下。

          優(yōu)化背景

          由于這個數(shù)據(jù)校對系統(tǒng)最初不是我開發(fā)的,我了解了下數(shù)據(jù)校對系統(tǒng)的業(yè)務(wù),整體來說,數(shù)據(jù)校對系統(tǒng)的業(yè)務(wù)還是比較簡單的。用戶通過商城提交訂單后,會在訂單微服務(wù)中生成訂單信息,保存在訂單數(shù)據(jù)庫中。訂單微服務(wù)會調(diào)用庫存微服務(wù)的接口,扣減商品的庫存數(shù)量,并且會將每筆訂單扣減庫存的記錄保存在庫存數(shù)據(jù)庫中。為了防止用戶提交訂單后沒有扣減庫存,或者重復(fù)扣減庫存,數(shù)據(jù)校對系統(tǒng)每天會校驗訂單中提交的商品數(shù)量與扣減的庫存數(shù)量是否一致,并且會將校對的結(jié)果信息保存到數(shù)據(jù)校對信息表中。

          數(shù)據(jù)校對系統(tǒng)的總體流程為:先查詢訂單記錄,然后在查詢庫存的扣減記錄,然后對比訂單和庫存扣減記錄,然后將校對的結(jié)果信息保存到數(shù)據(jù)校對信息表中,整體流程如下所示。

          為了能夠讓大家更好的了解數(shù)據(jù)校對系統(tǒng)對于訂單和庫存的校對業(yè)務(wù),我將代碼精簡了下,核心業(yè)務(wù)邏輯代碼如下所示。

          //檢測是否存在未對賬訂單
          checkOrders = checkOrders();
          while(checkOrders != null){
              //查詢未校對的訂單信息
              hasNoOrders = getHasNoOrders();
              //查詢未校對的庫存記錄
              hasNoStock = getHasNoStock();
              //校對數(shù)據(jù)并返回結(jié)果
              checkResult = checkData(hasNoOrders, hasNoStock);
              //將結(jié)果信息保存到數(shù)據(jù)校對信息表中
              saveCheckResult(checkResult);
              //檢測是否存在未對賬訂單
              checkOrders = checkOrders();
          }

          好了,上述就是系統(tǒng)優(yōu)化的背景,想必看到這里,很多小伙伴應(yīng)該知道問題出在哪里了。我們繼續(xù)往下看。

          問題分析

          雖然很多小伙伴應(yīng)該已經(jīng)知道系統(tǒng)性能低下的問題所在了,這里,我們就一起詳細分析下校對系統(tǒng)性能低下的原因。

          既然運營的同事說數(shù)據(jù)校對系統(tǒng)越來越慢了,我們首先要做的就是找到系統(tǒng)的性能瓶頸所在。據(jù)了解,目前的數(shù)據(jù)對賬系統(tǒng),由于訂單記錄和庫存扣減記錄數(shù)據(jù)量巨大,所以查詢未校對的訂單信息的方法getHasNoOrders()和查詢?yōu)樾Φ膸齑嬗涗浀姆椒╣etHasNoStock()相對來說比較慢。并且在數(shù)據(jù)校對系統(tǒng)中,校對訂單和庫存記錄的方法是單線程執(zhí)行的,我們可以簡單畫一個時間抽線圖,如下所示。

          由圖可以看出,以單線程的方式getHasNoOrders()方法和getHasNoStock()方法耗費了大量的時間,這兩個方法本身在邏輯上就是兩個獨立的方法,并且這兩個方法沒有先后的執(zhí)行的順序依賴。那這兩個方法能不能并行執(zhí)行呢?很顯然是可以的。那我們把getHasNoOrders()方法和getHasNoStock()方法分別放到兩個不同的線程中,優(yōu)化下系統(tǒng)的性能,整體流程如下所示。

          優(yōu)化后,我們將getHasNoOrders()方法放到線程1中執(zhí)行,getHasNoStock()方法放到線程2中執(zhí)行,checkData()方法和saveCheckResult()方法發(fā)放到線程3中執(zhí)行,優(yōu)化后的系統(tǒng)性能相比優(yōu)化前的系統(tǒng)性能幾乎提升了一倍,優(yōu)化效果相對來說還是比較明顯的。

          說到這里,大家應(yīng)該應(yīng)該知道具體怎么優(yōu)化了吧?好,我們繼續(xù)往下看!

          解決方案

          解決問題的思路有了,接下來,我們看看如何使用代碼實現(xiàn)我們上面分析的解決問題的思路。這里,我們可以分別開啟兩個線程執(zhí)行g(shù)etHasNoOrders()方法和getHasNoStock()方法,在主線程中執(zhí)行checkData()方法和saveCheckResult()方法。這里需要注意的是:主線程需要等待兩個子線程執(zhí)行完畢之后再執(zhí)行checkData()方法和saveCheckResult()方法。 為了實現(xiàn)這個功能,我們可以使用Thread類中join()方法,有關(guān)Thread類中join()方法的具體說明,小伙伴們可以參見《一個工作了7年的朋友去面試竟然被Thread類虐的體無完膚》一文。這里,具體的邏輯就是在主線程中調(diào)用兩個子線程的join()方法實現(xiàn)阻塞等待,當兩個子線程執(zhí)行完畢退出時,調(diào)用兩個子線程join()方法的主線程會被喚醒,從而執(zhí)行主線程中的checkData()方法和saveCheckResult()方法。大體代碼如下所示。

          //檢測是否存在未對賬訂單
          checkOrders = checkOrders();
          while(checkOrders != null){
              Thread t1 = new Thread(()->{
                  //查詢未校對的訂單信息
                  hasNoOrders = getHasNoOrders();
              });
              t1.start();
              Thread t2 = new Thread(()->{
                 //查詢未校對的庫存記錄
                 hasNoStock = getHasNoStock();
              });
               t2.start();
              //阻塞主線程,等待線程t1和線程t2執(zhí)行完畢
              t1.join();
              t2.join();
              //校對數(shù)據(jù)并返回結(jié)果
              checkResult = checkData(hasNoOrders, hasNoStock);
              //將結(jié)果信息保存到數(shù)據(jù)校對信息表中
              saveCheckResult(checkResult);
              //檢測是否存在未對賬訂單
              checkOrders = checkOrders();
          }

          至此,我們基本上能夠解決問題了。但是,還有沒有進一步優(yōu)化的空間呢?我們進一步往下看。

          進一步優(yōu)化

          通過上面對系統(tǒng)優(yōu)化,基本能夠達成我們的優(yōu)化目標,但是上面的解決方案存在著不足的地方,那就是在while循環(huán)里每次都要新建兩個線程分別執(zhí)行g(shù)etHasNoOrders()方法和getHasNoStock()方法,了解Java多線程的小伙伴們應(yīng)該都知道,在Java中創(chuàng)建線程可是個非常耗時的操作。所以,最好是能夠?qū)?chuàng)建出來的線程反復(fù)使用。這里,估計很多小伙伴都會想到使用線程池,沒錯,我們可以使用線程池進一步優(yōu)化上面的代碼。

          遇到新的問題

          不過在使用線程池進一步優(yōu)化時,我們會遇到一個問題,就是主線程如何等待子線程中的結(jié)果數(shù)據(jù)呢?說直白點就是:主線程如何知道子線程中的getHasNoOrders()方法和getHasNoStock()方法執(zhí)行完了? 由于在之前的代碼中我們是在主線程中調(diào)用子線程的join()方法等待子線程執(zhí)行完畢,獲取到子線程執(zhí)行的結(jié)果后,繼續(xù)執(zhí)行主線程的邏輯。但是如果使用了線程池的話,線程池中的線程根本不會退出,此時,我們無法使用線程的join()方法等待線程執(zhí)行完畢。

          所以,主線程如何知道子線程中的getHasNoOrders()方法和getHasNoStock()方法執(zhí)行完了? 這個問題就成了關(guān)鍵的突破點。這里,我們使用線程池進一步優(yōu)化的代碼如下所示。

          //檢測是否存在未對賬訂單
          checkOrders = checkOrders();
          //創(chuàng)建線程池
          Executor executor =  Executors.newFixedThreadPool(2);
          while(checkOrders != null){
              executor.execute(()->{
                  //查詢未校對的訂單信息
                  hasNoOrders = getHasNoOrders();
              });
              executor.execute(()->{
                 //查詢未校對的庫存記錄
                 hasNoStock = getHasNoStock();
              });
              
              /**如何知道子線程中的getHasNoOrders()方法和getHasNoStock()方法執(zhí)行完了成為關(guān)鍵**/
              
              //校對數(shù)據(jù)并返回結(jié)果
              checkResult = checkData(hasNoOrders, hasNoStock);
              //將結(jié)果信息保存到數(shù)據(jù)校對信息表中
              saveCheckResult(checkResult);
              //檢測是否存在未對賬訂單
              checkOrders = checkOrders();
          }

          那么,如何解決這個問題呢?我們繼續(xù)往下看。

          新的解決方案

          相信細心的小伙伴們能夠看出,整個業(yè)務(wù)的場景就是:一個線程需要等待其他兩個線程的邏輯執(zhí)行完畢后再執(zhí)行。在Java的并發(fā)類庫中,為我們提供了一個能夠在這種場景下使用的類庫,那就是CountDownLatch類。

          使用CountDownLatch類優(yōu)化我們程序的具體做法就是:在程序的while()循環(huán)中首先創(chuàng)建一個CountDownLatch對象,計數(shù)器的值初始化為2。分別在hasNoOrders = getHasNoOrders();代碼和hasNoStock = getHasNoStock();代碼的后面調(diào)用latch.countDown()方法使得計數(shù)器的值分別減1。在主線程中調(diào)用latch.await()方法,等待計數(shù)器的值變?yōu)?,繼續(xù)往下執(zhí)行。這樣,就能夠完美解決我們遇到的問題了。優(yōu)化后的代碼如下所示。

          //檢測是否存在未對賬訂單
          checkOrders = checkOrders();
          //創(chuàng)建線程池
          Executor executor =  Executors.newFixedThreadPool(2);
          while(checkOrders != null){
              CountDownLatch latch = new CountDownLatch(2);
              executor.execute(()->{
                  //查詢未校對的訂單信息
                  hasNoOrders = getHasNoOrders();
                  latch.countDown();
              });
              executor.execute(()->{
                 //查詢未校對的庫存記錄
                 hasNoStock = getHasNoStock();
                 latch.countDown();
              });
              
              //等待子線程的邏輯執(zhí)行完畢
              latch.await();
           
              //校對數(shù)據(jù)并返回結(jié)果
              checkResult = checkData(hasNoOrders, hasNoStock);
              //將結(jié)果信息保存到數(shù)據(jù)校對信息表中
              saveCheckResult(checkResult);
              //檢測是否存在未對賬訂單
              checkOrders = checkOrders();
          }

          至此,我們就完成了系統(tǒng)的優(yōu)化工作。

          總結(jié)與思考

          這次系統(tǒng)性能的優(yōu)化,主要是將單線程執(zhí)行的數(shù)據(jù)校對業(yè)務(wù),優(yōu)化成使用多線程執(zhí)行。在平時的工作過程中,我們需要認真思考,找到系統(tǒng)性能瓶頸所在,找出在邏輯上不相干,并且沒有先后順序的業(yè)務(wù)邏輯,將其放到不同的線程中執(zhí)行,能夠大大提供系統(tǒng)的性能。

          這次,對于系統(tǒng)的優(yōu)化,我們最終使用線程池來執(zhí)行比較耗時的查詢訂單與查詢庫存記錄的操作,并且在主線程中等待線程池中的線程邏輯執(zhí)行完畢后再執(zhí)行主線程的后續(xù)業(yè)務(wù)邏輯。這種場景,使用Java中提供的CountDownLatch類再合適不過了。這里,再強調(diào)一下:CountDownLatch主要的使用場景就是一個線程等待多個線程執(zhí)行完畢后再執(zhí)行。如下圖所示。

          這里,也進一步提醒了我們:如果想學(xué)好并發(fā)編程,熟練的掌握Java中提供的并發(fā)類庫是我們必須要做到的。

          最后,給大家一個思考題:其實,上面的代碼不是最優(yōu)的,你有更好的優(yōu)化方法嗎?歡迎各位小伙伴在文末留言討論呀!

          好了,今天就到這兒吧,我是冰河,我們下期見~~

          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  五月天婷婷综合久久 | 91视频啊啊啊 | 一区操逼 | 凹凸福利视频 | 日韩国产区 |