淺析SpringBoot微服務中異步調用數(shù)據(jù)提交數(shù)據(jù)庫的問題
前言:
1.前面基于Springboot的單體項目介紹已經(jīng)完結了,至于項目中的其他功能實現(xiàn)我這里就不打算介紹了,因為涉及的知識點不難,而且都是簡單的CRUD操作,假如有興趣的話可以私信我我再看看要不要寫幾篇文章做個介紹。
2.完成上一階段的學習,我就投入到了微服務的學習當中,所用教程為B站上面黑馬的微服務教程。由于我的記性不是很好,所以對于新事物的學習我比較喜歡做筆記以加強理解,在這里我會將筆記的重點內容做個總結發(fā)布到“微服務學習”筆記欄目中。我是趙四,一名有追求的程序員,希望大家能多多支持,能給我點個關注就更好了。
一: 同步&異步
1.同步與異步的概念
在進行問題探討之前,我們有必要先了解一下什么是同步什么是異步。先來個官方點的說法:同步和異步關注的是消息通信機制 (synchronous communication/ asynchronous communication)。同步,就是調用某個東西是,調用方得等待這個調用返回結果才能繼續(xù)往后執(zhí)行。異步,和同步相反 調用方不會理解得到結果,而是在調用發(fā)出后調用者可用繼續(xù)執(zhí)行后續(xù)操作,被調用者通過狀體來通知調用者,或者通過回掉函數(shù)來處理這個調用。
可能你會就得有點懵?下面我們舉個簡單點的例子:就好像你去買水果,發(fā)現(xiàn)水果賣完了,這時候水果還在來的路上,你選擇等待,直到水果到了你買完才離開,這就是同步;而你知道水果賣完了,你跟店家說你要買什么然后店家到時候給你送貨上門,你只是跟店家說了一句之后便離開去干其他事情了,這就是異步。
2.同步方法調用&異步方法調用
前面介紹完同步和異步的概念之后,我們要把它代入到我們的代碼世界里面,在代碼世界里面,同步和異步一般體現(xiàn)在方法調用和http請求(ajax發(fā)送異步請求)上面,這里主要介紹方法調用。
2.1:同步方法調用
所謂同步方法調用,就是一個方法A調用方法B之后,方法A必須要等待方法B執(zhí)行完才能繼續(xù)執(zhí)行,要是方法B沒執(zhí)行完方法A就必須一直等待,見下圖:

2.2:異步方法調用
異步方法調用指的是當方法A調用方法B之后,方法A不需要等待方法B執(zhí)行完畢再去干別的事,方法A只需要發(fā)起調用請求之后便繼續(xù)執(zhí)行自己的程序,方法B在另外一個線程里面執(zhí)行,兩者互不干擾,見下圖:

二:問題引入
1.功能需求
程序中我要實現(xiàn)的功能是作者發(fā)布文章之后線程A完成文章的保存工作,且在線程A里面要開啟異步調用線程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調用騰訊云進行文本檢測
Boolean THandleResult = handleTextScan(content, wmNews);
if(!THandleResult) return;
//3.檢測圖片
//3.1提取圖片
List<String> imageUrl = (List<String>) map.get("images");
//3.2調用騰訊云對圖片進行檢測
Boolean IHandleresult = handleImageScan(imageUrl, wmNews);
if(!IHandleresult) return;
//4,審核成功
//4.1保存文章
log.info("檢測到文章無違規(guī)內容");
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設置封面圖片
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())) {
//直接保存結束
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
//4.不是草稿
//4.1保存文章圖片素材與文章關系
//4.1.1提取圖片素材列表
List<String> imagesList = getImagesList(dto);
//4.1.2保存
saveRelatedImages(imagesList,wmNews.getId(),WemediaConstants.WM_CONTENT_REFERENCE);
//4.2保存封面圖片和文章關系
saveRelatedCover(dto,imagesList,wmNews);
//5.審核文章(異步調用)
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)這樣的情況呢?要注意的是,這里開啟了異步方法調用,這時候線程A是負責將數(shù)據(jù)保存的,而線程B是負責對文章進行審核的,而且線程A開啟了事務支持,會不會是因為這兩個方法都被認為是同一個事務所以事務還沒結束線程B查詢不到數(shù)據(jù)呢?這應該是不可能的,因為要想實現(xiàn)jdbc事務, 就必須是在同一個連接對象中操作,而我們可以看到,在進行查詢操作時候是創(chuàng)建了一個新的連接的:

那這時候只有一種可能,就是由于A開啟了事務,這時候B線程是異步執(zhí)行的,只要線程A還沒有執(zhí)行完畢,數(shù)據(jù)就不會被提交到數(shù)據(jù)庫中,這時候線程B嘗試去數(shù)據(jù)庫中獲取該數(shù)據(jù)顯然是獲取不到的。
三:問題解決
有了以上假設,實踐是檢驗真理的唯一標準,下面通過調試來檢驗,首先在線程A上加上一句日志打印信息,看看線程B執(zhí)行時線程A是否執(zhí)行完畢(關系到數(shù)據(jù)時候已經(jīng)提交)。通過斷點進行調試:

可以看到這時候是能夠獲取到數(shù)據(jù)的,那么假如我在查詢之前讓線程休眠0.5秒呢?

可以看到這時候成功獲取到數(shù)據(jù),說明就是跟數(shù)據(jù)提交時間有關。
相關文章
SpringBoot實現(xiàn)多數(shù)據(jù)源配置的示例詳解
這篇文章主要為大家詳細介紹了SpringBoot實現(xiàn)多數(shù)據(jù)源配置的相關知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-12-12
SpringBoot使用Prometheus實現(xiàn)監(jiān)控
在當今的軟件開發(fā)世界中,監(jiān)控是至關重要的一部分,本文主要介紹了如何在Spring Boot應用程序中使用Prometheus進行監(jiān)控,以幫助大家更好地理解和管理您的應用程序,有需要的可以參考下2023-10-10
SpringBoot打包發(fā)布到linux上(centos 7)的步驟
這篇文章主要介紹了SpringBoot打包發(fā)布到linux上(centos 7)的步驟,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2020-12-12

