1.feign远程调用会丢失请求头信息
有时候feign远程调用发现接口方法中调用了获取session数据结果为null,检查来检查去发现是请求头中的Cookie丢失了,也就没有sessionId(jessionId),发送请求时没携带cookie,找不到服务器中session数据。原因是Feign调用会创建新的请求,请求模板里没有初始请求携带的头信息,需要添加拦截器增强feign,即添加拦截器用来同步初始请求头信息到feign请求模板去。原因是代理创建了新的请求,不再是初始的请求,需要添加拦截器增强feign,即添加一个拦截器来同步初始请求的请求头信息,核心原理是利用ThreadLocal同线程变量共享。
package com.atguigu.gulimall.order.config;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* User: ldj
* Date: 2022/10/12
* Time: 21:35
* Description:
* feign远程调用会丢失请求头信息,原因是代理创建了新的请求,不再是初始的请求,
* 需要添加拦截器增强feign,即添加一个拦截器来同步初始请求的请求头信息
*/
@Configuration
public class MyFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
//获取到最初请求的参数
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest initiallyRequest = Objects.requireNonNull(attributes).getRequest();
//新请求同步了最初请求头数据的Cookie
String cookie = initiallyRequest.getHeader("Cookie");
template.header("Cookie", cookie);
};
}
}
2.feign异步调用会丢失请求上下文信息
结合上面,ThreadLocal同线程变量共享,异步走不再是同一条线程,异步脱离主线程,不同线程之间ThreadLocal无法再共享数据。解决方法是在走分支线程前,将主线程之前的请求数据同步到分支线程
//示例代码,CompletableFuture做异步编排(多线程方式)
/**
* 1.涉及复杂多路查询,需要使用线程池方式实现异步调用方案
* 2.feign异步调用会丢失请求上下文信息,原因是异步脱离主线程,不同线程ThreadLocal无法共享数据
* 解决方法是在走分支线程前,将主线程的数据同步到分支线程
*/
@Override
public OrderConfirmVO getConfirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVO orderConfirmVO = new OrderConfirmVO();
MemberRespVO memberRespVO = LoginUserInterceptor.threadLocal.get();
System.out.println("主线程=====>" + Thread.currentThread().getId());
//主线程请求数据
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//1.远程查询用户的收获地址列表,选用默认收获地址,会员id从拦截器获取
CompletableFuture<Void> getHarvestAddressFuture = CompletableFuture.runAsync(() -> {
System.out.println("分支线程1=====>" + Thread.currentThread().getId());
//同步主线程的数据
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVO> harvestAddress = memberFeignService.getHarvestAddress(memberRespVO.getId());
orderConfirmVO.setAddress(harvestAddress);
}, executor);
//2.远程查询购物车选中的购物项
CompletableFuture<Void> getChosenCartItemsFuture = CompletableFuture.runAsync(() -> {
System.out.println("分支线程2=====>" + Thread.currentThread().getId());
List<OrderItemVO> chosenCartItems = cartFeignService.getChosenCartItems();
orderConfirmVO.setItems(chosenCartItems);
}, executor);
//阻塞等待所有线程完成
CompletableFuture.allOf(getHarvestAddressFuture, getChosenCartItemsFuture).get();
return orderConfirmVO;
}
说明:1和2其实是一个意思
3.本地事务注解 @Transactional不能控制远程服务事务回滚,最后会导致数据不一致
例子:假设有一个下单操作,步骤是确认订单后,先保存订单,后Feign远程调用接扣库存会出现可能情况
1.远程调用接口扣库存,如果出现网络抖动,抛出超时异常,保存订单会回滚,但是扣库存是成功; 2.一旦远程调用接口扣库存成功,后面代码再抛异常,扣库存无法回滚。