背景:
最近项目有个批量导入的需求需要优化——当导入数量过大时,后端对导入数据的校验和数据库操作时间过长,会导致前端请求超时,用户体验也极差。所以要将数据校验等耗时操作进行异步处理。此处异步处理采用的是多线程的方式实现,然后就有了以下的问题。
一、多线程服务调用导致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);
}
}
}
相关文章
暂无评论...