實現輕量級本地分佈式事務

在上家公司時,由於機構 DIY 課程定製需要從固定課程複製,而複製需要調用三個小組的微服務,導致速度緩慢。最終通過id生成器,線程池,CompletionService ,閉鎖實現 web 端調用的併發執行,提速優化同時保證三者之間事務安全, 接下來詳細描述。

三個微服務分別爲 課程創建、講次創建、卷子創建,講次掛在課程上,卷子掛在講次上,關係如下:
image.png
原本的創建是在單線程執行:創建講次 -> 創建課程 -> 創建卷子,
而現在需要併發執行它們,並保證相互間異常回滾。

 

主要的實現步驟爲:

1、由id生成器提前生成好講次id。

2、將生成好的講次id分別傳入 課程複製任務、講次複製任務、講次關聯卷子複製任務。

3、由於最終需要存儲課程id,所以課程創建結果id需要返回給主線程。

4、其中任意一個任務出錯,全部回滾(回滾由寫好的刪除語句執行實現)

 

CompleteService 源碼解析

我們先來詳細講下關鍵類CompleteService,看看它在這裏起到什麼作用,以下對它的唯一實現類ExecutorCompletionService進行源碼分析:

ExecutorCompletionService的構造方法:
構造.jpg

這裏可以看到 ExecutorCompletionService 會將傳入的線程池對象Executor作爲它的成員對象,並且會創建一個名爲completionQueue的LinkedBlockingQueue對象,這個對象的功能是關鍵,咱先接着往下看~

ExecutorCompletionService的執行方法 submit :
submit.jpg

這裏看到接收一個Callable類型的線程執行對象,並且執行

  • 1、調用newTaskFor構造了RunnableFuture對象實際上構造了子類FutureTask,將Callable作爲對象賦值給了它,並且初始化任務狀態爲NEW。
    futureTask.jpg

  • 2、將RunnableFuture轉換爲FutureTask的子類QueueingFuture,並將它交給線程池執行。
    image.png
    這裏看到了上面所說的關鍵:completionQueue,驚不驚喜~ 它默默地待在一個名爲done的方法中

實現類ExecutorCompletionService的獲取方法 take:
image.png
在task方法中,我們再次看到了熟悉的completionQueue,我們都知道Queue的take方法是一個阻塞獲取結果的方法。 這時候我們設想一下,如果上面的done方法是每個任務完成時就執行的,那麼通過take方法,我們將每次都能獲取到最新的線程結果。 如果能想到這點,那麼恭喜你,你已經基本掌握了CompleteService的用法了~ 那麼接下來我們看下done是在什麼時候執行的。

FutureTask的執行方法:
image.png

這裏我們看到執行的兩種結果,成功調用set方法,失敗調用setException方法,分別看下set和setException的實現

  • set方法image.png 可以看到執行成功後會將結果賦值給outcome對象,並會將 state 由 NEW 改爲 NORMAL,接着調用finishCompletion方法。

image.png
可以看到finishCompletion最終調用done,將成功的任務放入Queue中。此時再通過take方法就可以獲取FutureTask,並且通過FutureTask的get方法以上賦值的成功對象outcome。
image.png
image.png

  • setException方法image.png 可以看到執行失敗後會將異常對象賦值給outcome對象,並會將 state 由 NEW 改爲 EXCEPTIONAL。 跟上面一樣,接着執行finishCompletion方法,最終調用done,將失敗的任務放入Queue中。此時再通過take方法獲取FutureTask,並且通過FutureTask的get方法獲取結果時,由於EXCEPTIONAL != NORMAL,此時將拋出異常對象outcome。

 

具體實現

介紹完CompleteService,下面介紹具體實現:

流程圖:
image.png
 

上面爲實現流程圖,具體的實現步驟爲:

1、由id生成器提前生成好講次id。

2、將生成好的講次id分別傳入 課程複製任務、講次複製任務、講次關聯卷子複製任務。

3、創建CountDownLatch對象,初始容量爲2

4、創建線程池,線程數量爲3。(由於線程池在這裏還存在線程異常通訊的作用,因此每次請求新建,在這裏未發揮出線程池真正的效果。在版本二中進行了優化)。

5、創建AtomicBoolean類型命名爲classTypeCreateAlready的變量用於存儲課程創建是否成功的結果,默認爲false。

6、主線程實現描述:
主線程創建3個子線程後,將調用completeService的take方法阻塞,直到返回結果FutureTask:

  • get得到課程id結果,此時說明創建成功,設置classTypeCreateAlready爲true,作爲“講次創建任務” 和 “卷子創建任務” 判斷課程是否創建成功的依據。
  • get到異常,此時說明有任務創建失敗,catch異常,並執行executerServce.shutDown(),接着將子任務線程中拋出的具體的異常信息返回給接入方。 此時幾個任務中,未發生異常的任務將通過executerServce.isShutDown()判斷是否已有任務產生異常,有則進行回滾。

7、課程創建子線程的實現描述:

  • 創建失敗,此時直接catch異常,並根據任務是否已創建判斷是否執行回滾操作,接着拋出異常,此時該異常將在主線程take到本FutureTask,並執行get()方法後拋出,之後主線程如“6、主線程實現描述#get到異常”中所說,主線程執行executerServce.shutDown(),“講次創建任務” 和 “卷子創建任務” 將根據executerServce.isShutDown()判斷爲true後進行回滾。
  • 創建成功,將通過countDownLatch.await()阻塞直到 “講次創建任務” 和 “卷子創建任務” 都執行完countDownLatch.countDown()操作,countDownLatch.await()阻塞解除後將根據executerServce.isShutDown()判斷主線程是否異常(即“講次創建任務” 和 “卷子創建任務”是否有出現異常),有則進行回滾,沒有結束。

8、講次創建子線程的實現描述:

  • 創建失敗,此時直接catch異常,並根據任務是否已創建判斷是否執行回滾操作,執行countDownLatch.countDown()之後拋出異常,此時該異常將在主線程take到本FutureTask,並執行get()方法後拋出,之後主線程如“6、主線程實現描述#get到異常”中所說,主線程執行executerServce.shutDown(),“課程創建任務” 和 “卷子創建任務” 將根據executerServce.isShutDown()判斷爲true後進行回滾。
  • 創建成功,將輪詢classTypeCreateAlready判斷課程創建是否已經成功,否的話繼續根據executerServce.isShutDown()判斷主線程是否異常(即其餘兩個子線程是否有出現異常),executerServce.isShutDown()爲true進行回滾,否的話繼續根據classTypeCreateAlready判斷課程創建是否成功,直至classTypeCreateAlready爲true或executerServce.isShutDown()爲true。 image.png

9、卷子創建子線程的實現描述:

  • 創建失敗,此時直接catch異常,並根據任務是否已創建判斷是否執行回滾操作,執行countDownLatch.countDown()之後拋出異常,此時該異常將在主線程take到本FutureTask,並執行get()方法後拋出,之後主線程如“6、主線程實現描述#get到異常”中所說,主線程執行executerServce.shutDown(),“課程創建任務” 和 “講次創建任務” 將根據executerServce.isShutDown()判斷爲true後進行回滾。
  • 創建成功,將輪詢classTypeCreateAlready判斷課程創建是否已經成功,否的話繼續根據executerServce.isShutDown()判斷主線程是否異常(即其餘兩個子線程是否有出現異常),executerServce.isShutDown()爲true進行回滾,否的話繼續根據classTypeCreateAlready判斷課程創建是否成功,直至classTypeCreateAlready爲true或executerServce.isShutDown()爲true。

10、課程創建線程執行完畢後休眠1毫秒,是爲了防止課程創建阻塞在countDownLatch.await()時,“講次創建任務” 或 “卷子創建任務” 還在執行並接着產生異常,此時課程創建流程執行executorService.isShutdown()一定要確保是在主線程take到FutureTask,並調用get()方法後,因此留出一秒主線程執行等待時間。

 

總結

以上即爲課程創建本地分佈式事務的實現,CompleteService是一個非常不錯的併發類,當需要任務多線程執行,並且需要對多線程結果進行彙總時,將灰常有效。 而且還能實現只要其中一個線程異常,立即結束主流程並返回結果,你心動了麼~

上述的實現中存在弊端,線程池未發揮真正的作用,之後進行了一輪優化版本。 之所以還寫這個原始版本是因爲當中用到了較多的併發類,能起到較好的學習效果。 如果哪位小夥伴知道如何優化,請在留言中告訴我哦,然後可以提出自己的思路。

之後將帶來改善版本,請拭目以待~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章