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