如何解决异步任务上下文丢失问题
作者:勿语&
在多线程编程中,异步任务可能会导致上下文信息丢失,为了解决这个问题,可以在执行异步任务前,通过自定义TaskDecorator拷贝主线程的上下文至子线程,这样可以确保上下文在异步执行过程中得以保留,将定制的TaskDecorator设置至线程池,可以有效地解决上下文丢失问题
解决异步任务上下文丢失问题
- 上下文丢失主要是因为主线程和子线程的上下文不能共享。
- 可以通过执行异步任务之前,将主线程上的上下文信息拷贝到子线程上。
自定义TaskDecorator 来拷贝主线程上的上下文信息到子线程,然后将自定义的 TaskDecorator实现类 设置到线程池上。
@Configuration public class ThreadPoolConfig { @Bean(name = "customizeTaskExecutor") public ThreadPoolTaskExecutor threadPoolTaskExecutor(){ ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor(); // 核心线程数=cpu核心数+1 poolExecutor.setCorePoolSize(5); // 最大线程数=cpu核心数*2 poolExecutor.setMaxPoolSize(8); // 设置任务装饰器 poolExecutor.setTaskDecorator(taskDecorator()); // 任务被拒绝后,交给调用线程执行 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异步情况丢失上下文问题
在微服务的开发中,我们经常需要服务之间的调用,并且为了提高效率使用异步的方式进行服务之间的调用,在这种异步的调用情况下会有一个严重的问题,丢失上文下
通过以上图片可以看出异步丢失上下文的原因是不在同一个线程,所有数据不能共享,Wie了解决这个问题,我们就需要把之前线程的请求头上下文,在次存放到其他线程的请求头上下文就行,具体实现如下:
案例:feign异步获取订单明细的案例代码
/** * 获取订单明细的vo * @return */ @Override public OrderConfirmVo orderConfirm() { MemberResponseVo member = OrderInterceptor.threadLocal.get(); OrderConfirmVo orderConfirmVo = new OrderConfirmVo(); System.out.println("主线程:"+ Thread.currentThread().getId()); //获取主线程的请求头信息 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //考虑到效率问题 使用异步编排 CompletableFuture<Void> getAddress = CompletableFuture.runAsync(() -> { //子线程中设置添加主线程的请求头信息 信息共享 否则远程调用异步处理丢失请求头信息 RequestContextHolder.setRequestAttributes(requestAttributes); System.out.println("address:"+ Thread.currentThread().getId()); //远程获取地址信息 List<MemberAddressVo> address = memberFeignService.getAddress(member.getId()); orderConfirmVo.setAddress(address); }, executor); CompletableFuture<Void> getItem = CompletableFuture.runAsync(() -> { //子线程中设置添加主线程的请求头信息 信息共享 否则远程调用异步处理丢失请求头信息 RequestContextHolder.setRequestAttributes(requestAttributes); System.out.println("item:"+ Thread.currentThread().getId()); //远程获取购物项 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); //异步编排完成之后执行后续操作 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令牌储存在redis String token = UUID.randomUUID().toString().replace("-", ""); orderConfirmVo.setOrderToken(token); redisTemplate.opsForValue().set(OrderConstant.ORDER_TOKEN+member.getId(),token); return orderConfirmVo; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。