Feign remote call and asynchronous call missing request header problem

Preface

Recently I have been sorting out the projects I have done before, and I think of a problem I encountered before. The scenario is like this. When I submit an order, I need to query the user’s address information and the shopping items checked in the shopping cart. In this case, I Two services need to be called, one is membership service and the other is shopping cart service. Since the user login information is shared across the system (distributed session is used here), when I submit an order, when I call the shopping cart service, the interceptor of the shopping cart service will intercept the request to determine whether the user is logged in. At this time, the request header is lost, causing the shopping cart service interceptor to return that the user is not logged in, but is actually logged in. Another problem is that when asynchronous calls are made, the old request thread is not shared, which leads to the problem that the old request is not available in my business and the null pointer exception is reported.

Problems and solutions for remote calls

In distributed projects, there are roughly two types of sending requests, one is browser access, and the second is remote calls between services and services through OpenFeign. When the browser sends a request, it will bring the information in the request header, so it will not cause the cookie to be lost, so that the user will not judge the abnormal situation of not logged in when the user is actually logged in. Going deep into the source code, it is found that Feign will recreate a request. This request does not have any request header. This request template will traverse the apply method of the request interceptor to enrich the request template.
Insert picture description here
Seeing this place, there is a solution. The solution is, I wrote a feign interceptor, which injected a RequestInterceptor object, which is an interface, I rewrite its apply method, get the request header information in the old request, and put it in this new request template Here, what I update here is the cookie. Take a look at the code:

@Configuration
public class GuliFeignConfig {
    
    

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor() {
    
    

        RequestInterceptor requestInterceptor = new RequestInterceptor() {
    
    
            @Override
            public void apply(RequestTemplate template) {
    
    
                //1、使用RequestContextHolder拿到刚进来的请求数据
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

                if (requestAttributes != null) {
    
    
                    //老请求
                    HttpServletRequest request = requestAttributes.getRequest();

                    if (request != null) {
    
    
                        //2、同步请求头的数据(主要是cookie)
                        //把老请求的cookie值放到新请求上来,进行一个同步
                        String cookie = request.getHeader("Cookie");
                        template.header("Cookie", cookie);
                    }
                }
            }
        };
        return requestInterceptor;
    }
}

In this way, every time you make a feign remote call, you need to come here to enrich the request template and bring cookies, so that the user login information can normally judge the login problem.
This problem is solved. I drew an overall flow chart:
Insert picture description here

Problems and solutions of asynchronous calls

The scene has already been mentioned. I have two query tasks in this service. One is to query the detailed address information of the member in the membership service, and the other is to query the shopping item information selected in the shopping cart in the shopping cart service. Here I did Step optimization, throw these two tasks into the thread pool I created under this module, and let them be processed asynchronously. Here, the asynchronous orchestration function of CompletableFuture is used, so that the throughput will be improved. But here comes the problem. When I get the old request, there is a null pointer problem. Let's take a look at how to get the old request.
Insert picture description here
Let's dive into the source code to see how this RequestContextHolder stores the request data

private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
@Nullable
public static RequestAttributes getRequestAttributes() {
    
    
    RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
    if (attributes == null) {
    
    
        attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
    }

    return attributes;
}

This requestAttributesHolder is actually a ThreadLocal. ThreadLocal is shared within the thread, but it is completely different data across threads. Here I am. When I throw two tasks into the thread pool to create other threads for execution, it can’t be taken To the request information of my main thread, it led to the phenomenon that the obtained value is empty.
This is how I solved it, look at my code

 /**
     * 订单确认页返回需要用的数据
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
    
    

        //构建OrderConfirmVo
        OrderConfirmVo confirmVo = new OrderConfirmVo();

        //获取当前用户登录的信息
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();

        //TODO :获取当前线程请求头信息(解决Feign异步调用丢失请求头问题)
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        //开启第一个异步任务
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
    
    

            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);

            //1、远程查询所有的收获地址列表
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
            confirmVo.setMemberAddressVos(address);
        }, threadPoolExecutor);

        //开启第二个异步任务
        CompletableFuture<Void> cartInfoFuture = CompletableFuture.runAsync(() -> {
    
    

            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);

            //2、远程查询购物车所有选中的购物项
            List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
            confirmVo.setItems(currentCartItems);
            //feign在远程调用之前要构造请求,调用很多的拦截器
        }, threadPoolExecutor).thenRunAsync(() -> {
    
    
            List<OrderItemVo> items = confirmVo.getItems();
            //获取全部商品的id
            List<Long> skuIds = items.stream()
                    .map((itemVo -> itemVo.getSkuId()))
                    .collect(Collectors.toList());

            //远程查询商品库存信息
            R skuHasStock = wmsFeignService.getSkuHasStock(skuIds);
            List<SkuStockVo> skuStockVos = skuHasStock.getData("data", new TypeReference<List<SkuStockVo>>() {
    
    });

            if (skuStockVos != null && skuStockVos.size() > 0) {
    
    
                //将skuStockVos集合转换为map
                Map<Long, Boolean> skuHasStockMap = skuStockVos.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(skuHasStockMap);
            }
        },threadPoolExecutor);

        //3、查询用户积分
        Integer integration = memberResponseVo.getIntegration();
        confirmVo.setIntegration(integration);

        //4、价格数据自动计算

        //TODO 5、防重令牌(防止表单重复提交)
        //为用户设置一个token,三十分钟过期时间(存在redis)
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(USER_ORDER_TOKEN_PREFIX+memberResponseVo.getId(),token,30, TimeUnit.MINUTES);
        confirmVo.setOrderToken(token);


        CompletableFuture.allOf(addressFuture,cartInfoFuture).get();

        return confirmVo;
    }

Insert picture description here
Each thread shares the previous request data. The problem is solved. Let’s draw a picture to see the overall problem
Insert picture description here

Guess you like

Origin blog.csdn.net/MarkusZhang/article/details/107888599