如何解決異步任務(wù)上下文丟失問(wèn)題
解決異步任務(wù)上下文丟失問(wèn)題
- 上下文丟失主要是因?yàn)橹骶€程和子線程的上下文不能共享。
- 可以通過(guò)執(zhí)行異步任務(wù)之前,將主線程上的上下文信息拷貝到子線程上。
自定義TaskDecorator 來(lái)拷貝主線程上的上下文信息到子線程,然后將自定義的 TaskDecorator實(shí)現(xiàn)類 設(shè)置到線程池上。
@Configuration public class ThreadPoolConfig { @Bean(name = "customizeTaskExecutor") public ThreadPoolTaskExecutor threadPoolTaskExecutor(){ ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor(); // 核心線程數(shù)=cpu核心數(shù)+1 poolExecutor.setCorePoolSize(5); // 最大線程數(shù)=cpu核心數(shù)*2 poolExecutor.setMaxPoolSize(8); // 設(shè)置任務(wù)裝飾器 poolExecutor.setTaskDecorator(taskDecorator()); // 任務(wù)被拒絕后,交給調(diào)用線程執(zhí)行 poolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return poolExecutor; } @Bean public TaskDecorator taskDecorator(){ return new TaskDecorator() { @Override public Runnable decorate(Runnable runnable) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); return ()->{ try { RequestContextHolder.setRequestAttributes(requestAttributes); runnable.run(); }finally { RequestContextHolder.resetRequestAttributes(); } }; } }; } }
Fegin異步情況丟失上下文問(wèn)題
在微服務(wù)的開(kāi)發(fā)中,我們經(jīng)常需要服務(wù)之間的調(diào)用,并且為了提高效率使用異步的方式進(jìn)行服務(wù)之間的調(diào)用,在這種異步的調(diào)用情況下會(huì)有一個(gè)嚴(yán)重的問(wèn)題,丟失上文下
通過(guò)以上圖片可以看出異步丟失上下文的原因是不在同一個(gè)線程,所有數(shù)據(jù)不能共享,Wie了解決這個(gè)問(wèn)題,我們就需要把之前線程的請(qǐng)求頭上下文,在次存放到其他線程的請(qǐng)求頭上下文就行,具體實(shí)現(xiàn)如下:
案例:feign異步獲取訂單明細(xì)的案例代碼
/** * 獲取訂單明細(xì)的vo * @return */ @Override public OrderConfirmVo orderConfirm() { MemberResponseVo member = OrderInterceptor.threadLocal.get(); OrderConfirmVo orderConfirmVo = new OrderConfirmVo(); System.out.println("主線程:"+ Thread.currentThread().getId()); //獲取主線程的請(qǐng)求頭信息 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //考慮到效率問(wèn)題 使用異步編排 CompletableFuture<Void> getAddress = CompletableFuture.runAsync(() -> { //子線程中設(shè)置添加主線程的請(qǐng)求頭信息 信息共享 否則遠(yuǎn)程調(diào)用異步處理丟失請(qǐng)求頭信息 RequestContextHolder.setRequestAttributes(requestAttributes); System.out.println("address:"+ Thread.currentThread().getId()); //遠(yuǎn)程獲取地址信息 List<MemberAddressVo> address = memberFeignService.getAddress(member.getId()); orderConfirmVo.setAddress(address); }, executor); CompletableFuture<Void> getItem = CompletableFuture.runAsync(() -> { //子線程中設(shè)置添加主線程的請(qǐng)求頭信息 信息共享 否則遠(yuǎn)程調(diào)用異步處理丟失請(qǐng)求頭信息 RequestContextHolder.setRequestAttributes(requestAttributes); System.out.println("item:"+ Thread.currentThread().getId()); //遠(yuǎn)程獲取購(gòu)物項(xiàng) List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems(); orderConfirmVo.setItems(currentUserCartItems); }, executor).thenRunAsync(()->{ List<OrderItemVo> items = orderConfirmVo.getItems(); //獲取所有商品的id List<String> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList()); List<Long> skuIds = collect.stream().map(item -> { return Long.parseLong(item); }).collect(Collectors.toList()); R<List<SkuHasStockVo>> skusHasStock = wmsFeignService.getSkusHasStock(skuIds); List<SkuHasStockVo> data = skusHasStock.getData(new TypeReference<List<SkuHasStockVo>>() { }); if(data!= null){ Map<Long, Boolean> collect1 = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock)); orderConfirmVo.setStocks(collect1); } },executor); //異步編排完成之后執(zhí)行后續(xù)操作 try { CompletableFuture.allOf(getAddress,getItem).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } orderConfirmVo.setIntegration(member.getIntegration()); orderConfirmVo.setPayPrice(orderConfirmVo.getPayPrice()); orderConfirmVo.setTotal(orderConfirmVo.getTotal()); //TODO 放重處理 生成token令牌儲(chǔ)存在redis String token = UUID.randomUUID().toString().replace("-", ""); orderConfirmVo.setOrderToken(token); redisTemplate.opsForValue().set(OrderConstant.ORDER_TOKEN+member.getId(),token); return orderConfirmVo; }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java類變量和成員變量初始化過(guò)程的應(yīng)用介紹
昨天看了一本叫做《突破程序員基本功的16課》的書(shū),個(gè)人感覺(jué)還可以,主要對(duì)Java的技巧進(jìn)行了一些深入的講解,讓我對(duì)類的初始化和對(duì)象的創(chuàng)建有了新的認(rèn)識(shí)2013-04-04用Maven插件生成Mybatis代碼的實(shí)現(xiàn)方法
本文主要介紹 Maven插件生成Mybatis代碼,現(xiàn)在做開(kāi)發(fā)的朋友有好多用Maven 來(lái)管理代碼,這里給大家舉個(gè)例子,有需要的同學(xué)可以看下2016-07-07基于Java實(shí)現(xiàn)音樂(lè)播放器的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Java編寫(xiě)一個(gè)簡(jiǎn)單的音樂(lè)播放器,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-07-07Java數(shù)據(jù)結(jié)構(gòu)之?dāng)?shù)組(動(dòng)力節(jié)點(diǎn)之Java學(xué)院整理)
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之?dāng)?shù)組(動(dòng)力節(jié)點(diǎn)之Java學(xué)院整理)的相關(guān)資料,包括創(chuàng)建和內(nèi)存分配,數(shù)組封裝后的使用等,需要的朋友參考下吧2017-04-04詳解如何在SpringBoot項(xiàng)目中使用統(tǒng)一返回結(jié)果
在一個(gè)完整的項(xiàng)目中,如果每一個(gè)控制器的方法都返回不同的結(jié)果,那么對(duì)項(xiàng)目的維護(hù)和擴(kuò)展都會(huì)很麻煩。因此,本文為大家準(zhǔn)備了SpringBoot項(xiàng)目中使用統(tǒng)一返回結(jié)果的方法,需要的可以參考一下2022-10-10Java中數(shù)據(jù)庫(kù)常用的兩把鎖之樂(lè)觀鎖和悲觀鎖
這篇文章主要介紹了數(shù)據(jù)庫(kù)常用的兩把鎖之樂(lè)觀鎖和悲觀鎖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Spring中的10種事務(wù)失效的常見(jiàn)場(chǎng)景
這篇文章主要介紹了Spring中的10種事務(wù)失效的常見(jiàn)場(chǎng)景,Spring的聲明式事務(wù)功能更是提供了極其方便的事務(wù)配置方式,配合Spring Boot的自動(dòng)配置,大多數(shù)Spring Boot項(xiàng)目只需要在方法上標(biāo)記@Transactional注解,即可一鍵開(kāi)啟方法的事務(wù)性配置,需要的朋友可以參考下2023-11-11