多线程环境下feign服务调用token失效以及事务管理问题及解决

1年前 (2023) 程序员胖胖胖虎阿
146 0 0

背景:

最近项目有个批量导入的需求需要优化——当导入数量过大时,后端对导入数据的校验和数据库操作时间过长,会导致前端请求超时,用户体验也极差。所以要将数据校验等耗时操作进行异步处理。此处异步处理采用的是多线程的方式实现,然后就有了以下的问题。

一、多线程服务调用导致token失效

        在多线程开发过程中,主线程的上下文信息默认并不会继承到子线程中,所以需要手动设置,一些特殊的请求头信息(如:token信息),可能无法直接通过上下文获得,此时就需要用到InheritableThreadLocal:可继承的线程级全局变量。

开可在controller层获取请求头信息,并赋值到 InheritableThreadLocal中,此处可以封装一个工具类来操作InheritableThreadLocal。

           将token信息放到共享类中后,需要在Feign配置类中(实现feign.RequestInterceptor)配置服务调用时需要附带的信息,代码示例已给出。至此问题就解决了。

二、多线程开发中事务的优雅处理

        在多线程开发中,Spring自带的事务注解的方式无法对子线程的数据库操作进行回滚。但在Spring提供了手动控制的事务的优雅的结局方案:org.springframework.transaction.support.TransactionTemplate;

此类可以优雅的控制事务提交操作。为方便起见,事务维护的代码已整合到MyService的异步处理中。

<!--Spring手动事务提交jar-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.3.12</version>
</dependency>

<!--feign  pom-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-feign</artifactId>
  <version>1.4.7.RELEASE</version>
</dependency>

以上提到的具体相关代码如下,如有建议或问题,欢迎指出:

线程工具类

package com.example.zwstudyspringbot.util;

import java.util.HashMap;
import java.util.Map;

/**
 * 定义多线程的共享工具类——可继承父线程数据的线程变量
 */
public class ThreadLocalUtil {

    /**
     * 定义线程变量
     */
    private static final InheritableThreadLocal<Map<String,String>> headerMap =
            new InheritableThreadLocal<Map<String,String>>(){
                @Override
                protected Map<String, String> initialValue() {
                    return new HashMap<>();
                }
            };

    /**
     * 获取所有共享变量
     * @return 共享Map
     */
    public static Map<String,String> get(){
        return headerMap.get();
    }

    /**
     * 获取共享变量指定key的value值
     * @param key key
     * @return
     */
    public static String get(String key){
        return headerMap.get().get(key);
    }

    /**
     * 设置共享变量值
     * @param key
     * @param value
     */
    public static void set(String key,String value){
        headerMap.get().put(key,value);
    }

}

Controller类

package com.example.zwstudyspringbot.api;

import com.example.zwstudyspringbot.service.MyService;
import com.example.zwstudyspringbot.util.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Map;
import java.util.Objects;

@RestController
@RequestMapping("right")
public class MyWebAPI {


    @Autowired
    private MyService myService;
    
    public String impNum(@RequestBody Map map, HttpServletRequest httpServletRequest){
        //获取请求头信息
        Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String name = headerNames.nextElement();
            if (Objects.equals(name,"token")){
                //将用户授权token放在线程共享类中,以便多线程开发时供子线程使用
                ThreadLocalUtil.set(name,httpServletRequest.getHeader(name));
            }
        }
        return myService.handle();
    }
}

Service类:

package com.example.zwstudyspringbot.service;

import com.example.zwstudyspringbot.util.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyService {


    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    @Autowired
    private TransactionTemplate transactionTemplate;

    public String handle(){

        //非耗时操作
        //step1...
        //step2...
        //共享主线程请求上下文————防止子线程RequestAttributes为空
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //获取主线程的token
        String token = ThreadLocalUtil.get("token");

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //共享主线程请求上下文信息————防止子线程RequestAttributes为空
                RequestContextHolder.setRequestAttributes(requestAttributes);
                //将主线程的token更新到子线程全局变量中
                ThreadLocalUtil.set("token",token);
                //耗时操作
                //step1.。。

                //事务操作
                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
                        //事务处理
                        //事务1
                        //事务2
                    }
                });
            }
        });
        return "ok";
    }
}

feign配置类

package com.example.zwstudyspringbot.config;

import com.example.zwstudyspringbot.util.ThreadLocalUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Configurable
public class FeignConfig implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes requestAttributes =(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null){
            HttpServletRequest request = requestAttributes.getRequest();
            String token = request.getHeader("token");
            //如果token为空,则可能是多线程调用,去线程共享类去尝试获取token
            if (StringUtils.isBlank(token)){
                token = ThreadLocalUtil.get("token");
            }
            requestTemplate.header("token",token);
        }
    }
}

相关文章

暂无评论

暂无评论...