淺析SpringBoot微服務(wù)中異步調(diào)用數(shù)據(jù)提交數(shù)據(jù)庫的問題
前言:
1.前面基于Springboot的單體項目介紹已經(jīng)完結(jié)了,至于項目中的其他功能實現(xiàn)我這里就不打算介紹了,因為涉及的知識點不難,而且都是簡單的CRUD操作,假如有興趣的話可以私信我我再看看要不要寫幾篇文章做個介紹。
2.完成上一階段的學(xué)習(xí),我就投入到了微服務(wù)的學(xué)習(xí)當(dāng)中,所用教程為B站上面黑馬的微服務(wù)教程。由于我的記性不是很好,所以對于新事物的學(xué)習(xí)我比較喜歡做筆記以加強理解,在這里我會將筆記的重點內(nèi)容做個總結(jié)發(fā)布到“微服務(wù)學(xué)習(xí)”筆記欄目中。我是趙四,一名有追求的程序員,希望大家能多多支持,能給我點個關(guān)注就更好了。
一: 同步&異步
1.同步與異步的概念
在進行問題探討之前,我們有必要先了解一下什么是同步什么是異步。先來個官方點的說法:同步和異步關(guān)注的是消息通信機制 (synchronous communication/ asynchronous communication)。同步,就是調(diào)用某個東西是,調(diào)用方得等待這個調(diào)用返回結(jié)果才能繼續(xù)往后執(zhí)行。異步,和同步相反 調(diào)用方不會理解得到結(jié)果,而是在調(diào)用發(fā)出后調(diào)用者可用繼續(xù)執(zhí)行后續(xù)操作,被調(diào)用者通過狀體來通知調(diào)用者,或者通過回掉函數(shù)來處理這個調(diào)用。
可能你會就得有點懵?下面我們舉個簡單點的例子:就好像你去買水果,發(fā)現(xiàn)水果賣完了,這時候水果還在來的路上,你選擇等待,直到水果到了你買完才離開,這就是同步;而你知道水果賣完了,你跟店家說你要買什么然后店家到時候給你送貨上門,你只是跟店家說了一句之后便離開去干其他事情了,這就是異步。
2.同步方法調(diào)用&異步方法調(diào)用
前面介紹完同步和異步的概念之后,我們要把它代入到我們的代碼世界里面,在代碼世界里面,同步和異步一般體現(xiàn)在方法調(diào)用和http請求(ajax發(fā)送異步請求)上面,這里主要介紹方法調(diào)用。
2.1:同步方法調(diào)用
所謂同步方法調(diào)用,就是一個方法A調(diào)用方法B之后,方法A必須要等待方法B執(zhí)行完才能繼續(xù)執(zhí)行,要是方法B沒執(zhí)行完方法A就必須一直等待,見下圖:
2.2:異步方法調(diào)用
異步方法調(diào)用指的是當(dāng)方法A調(diào)用方法B之后,方法A不需要等待方法B執(zhí)行完畢再去干別的事,方法A只需要發(fā)起調(diào)用請求之后便繼續(xù)執(zhí)行自己的程序,方法B在另外一個線程里面執(zhí)行,兩者互不干擾,見下圖:
二:問題引入
1.功能需求
程序中我要實現(xiàn)的功能是作者發(fā)布文章之后線程A完成文章的保存工作,且在線程A里面要開啟異步調(diào)用線程B實現(xiàn)文章的審核功能。部分代碼如下
@Override @Async //表明這是一個異步方法 public void AutoScanTextAndImage(Integer id) throws TencentCloudSDKException { log.info("開始進行文章審核..."); WmNews wmNews = wmNewsService.getById(id); if(wmNews == null) { throw new RuntimeException("WmAutoScanServiceImpl-文章信息不存在"); } if(wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())) { //1.提取文章文本及圖片 Map<String,Object> map = getTextAndImages(wmNews); //2.檢測文本 //2.1提取文本 String content = ((StringBuilder) map.get("content")).toString(); //2.2調(diào)用騰訊云進行文本檢測 Boolean THandleResult = handleTextScan(content, wmNews); if(!THandleResult) return; //3.檢測圖片 //3.1提取圖片 List<String> imageUrl = (List<String>) map.get("images"); //3.2調(diào)用騰訊云對圖片進行檢測 Boolean IHandleresult = handleImageScan(imageUrl, wmNews); if(!IHandleresult) return; //4,審核成功 //4.1保存文章 log.info("檢測到文章無違規(guī)內(nèi)容"); ResponseResult responseResult = saveAppArticle(wmNews); if(!responseResult.getCode().equals(200)) { throw new RuntimeException("WmAutoScanServiceImpl-文章審核,保存文章失敗"); } //4.2回填article_id wmNews.setArticleId((Long) responseResult.getData()); wmNews.setStatus(WmNews.Status.PUBLISHED.getCode()); wmNews.setReason("審核成功"); wmNewsService.updateById(wmNews); } }
@Autowired private WmAutoScanService wmAutoScanService; /** * 提交文章 * @param dto * @return */ @Override public ResponseResult submitNews(WmNewsDto dto) throws TencentCloudSDKException { //1.參數(shù)校驗 if(dto == null || dto.getContent().length() == 0) { return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID); } //2.保存或修改文章 //2.1屬性拷貝 WmNews wmNews = new WmNews(); BeanUtils.copyProperties(dto,wmNews); //2.2設(shè)置封面圖片 if(dto.getImages() != null && dto.getImages().size() != 0) { String images = StringUtils.join(dto.getImages(), ","); wmNews.setImages(images); } //2.3封面類型為自動 if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) { wmNews.setType(null); } saveOrUpdateWmNews(wmNews); //3.判斷是否為草稿 if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())) { //直接保存結(jié)束 return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS); } //4.不是草稿 //4.1保存文章圖片素材與文章關(guān)系 //4.1.1提取圖片素材列表 List<String> imagesList = getImagesList(dto); //4.1.2保存 saveRelatedImages(imagesList,wmNews.getId(),WemediaConstants.WM_CONTENT_REFERENCE); //4.2保存封面圖片和文章關(guān)系 saveRelatedCover(dto,imagesList,wmNews); //5.審核文章(異步調(diào)用) wmAutoScanService.AutoScanTextAndImage(wmNews.getId()); return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS); }
2.問題引出
代碼看著沒有什么問題,但是運行起來之后發(fā)現(xiàn)出現(xiàn)以下錯誤:
這是手動拋出的異常,拋出位置為:
WmNews wmNews = wmNewsService.getById(id); if(wmNews == null) { throw new RuntimeException("WmAutoScanServiceImpl-文章信息不存在"); }
可以看到查詢出的文章對象為空。
3.問題剖析
既然這里出現(xiàn)空對象,那么是不是因為沒有進行數(shù)據(jù)插入呢?查看前面的日志信息,可以看到確實插入了一條數(shù)據(jù):
接下來進行的操作時查詢該條數(shù)據(jù),查看MySQL日志信息:
可以看到查出的數(shù)據(jù)為空,那么為什么會出現(xiàn)這樣的情況呢?要注意的是,這里開啟了異步方法調(diào)用,這時候線程A是負(fù)責(zé)將數(shù)據(jù)保存的,而線程B是負(fù)責(zé)對文章進行審核的,而且線程A開啟了事務(wù)支持,會不會是因為這兩個方法都被認(rèn)為是同一個事務(wù)所以事務(wù)還沒結(jié)束線程B查詢不到數(shù)據(jù)呢?這應(yīng)該是不可能的,因為要想實現(xiàn)jdbc事務(wù), 就必須是在同一個連接對象中操作,而我們可以看到,在進行查詢操作時候是創(chuàng)建了一個新的連接的:
那這時候只有一種可能,就是由于A開啟了事務(wù),這時候B線程是異步執(zhí)行的,只要線程A還沒有執(zhí)行完畢,數(shù)據(jù)就不會被提交到數(shù)據(jù)庫中,這時候線程B嘗試去數(shù)據(jù)庫中獲取該數(shù)據(jù)顯然是獲取不到的。
三:問題解決
有了以上假設(shè),實踐是檢驗真理的唯一標(biāo)準(zhǔn),下面通過調(diào)試來檢驗,首先在線程A上加上一句日志打印信息,看看線程B執(zhí)行時線程A是否執(zhí)行完畢(關(guān)系到數(shù)據(jù)時候已經(jīng)提交)。通過斷點進行調(diào)試:
可以看到這時候是能夠獲取到數(shù)據(jù)的,那么假如我在查詢之前讓線程休眠0.5秒呢?
可以看到這時候成功獲取到數(shù)據(jù),說明就是跟數(shù)據(jù)提交時間有關(guān)。
相關(guān)文章
SpringBoot實現(xiàn)多數(shù)據(jù)源配置的示例詳解
這篇文章主要為大家詳細(xì)介紹了SpringBoot實現(xiàn)多數(shù)據(jù)源配置的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12SpringBoot如何進行業(yè)務(wù)校驗實例詳解
這篇文章主要給大家介紹了關(guān)于SpringBoot如何進行業(yè)務(wù)校驗的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-01-01SpringBoot使用Prometheus實現(xiàn)監(jiān)控
在當(dāng)今的軟件開發(fā)世界中,監(jiān)控是至關(guān)重要的一部分,本文主要介紹了如何在Spring Boot應(yīng)用程序中使用Prometheus進行監(jiān)控,以幫助大家更好地理解和管理您的應(yīng)用程序,有需要的可以參考下2023-10-10SpringBoot打包發(fā)布到linux上(centos 7)的步驟
這篇文章主要介紹了SpringBoot打包發(fā)布到linux上(centos 7)的步驟,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2020-12-12