美文网首页bug记录
一次ThreadLocal引发的生产bug

一次ThreadLocal引发的生产bug

作者: 修行者12138 | 来源:发表于2021-03-23 18:22 被阅读0次

问题描述

有一个接口,根据ThreadLocal中是否存在某个变量,来执行不同代码分支,存在该变量走A代码分支,否则走B代码分支。
刚上线时,接口正常运行,一段时间后,出现问题,所有请求都走到A分支,但是理论上ThreadLocal不应该存在该变量。
相同前后端代码,测试环境无法复现问题。

问题原因

tomcat的线程池大小是有限的,如果请求数大于tomcat线程池大小,就会出现同一个线程处理不同的请求,那么后面的请求就可能看到前面的请求往ThreadLocal放的脏数据。
之所以运行一段时间后,生产环境问题必现,而测试环境无法复现,是因为生产环境tomcat线程池所有线程的ThreadLocal都存在脏数据,而测试环境没有。

因此,每次请求(http或者websocket)处理完毕后,需要清理ThreadLocal。

问题解决

ThreadLocalUtils工具类

@Slf4j
public class ThreadLocalUtils {
    private ThreadLocalUtils() {

    }

    private static ThreadLocal<HttpServletRequest> request = new ThreadLocal<>();

    private static ThreadLocal<String> string = new ThreadLocal<>();

    private static ThreadLocal<HashMap> map = ThreadLocal.withInitial(() -> new HashMap(16));


    public static void setRequest(HttpServletRequest httpServletRequest){
        request.set(httpServletRequest);
    }

    public static HttpServletRequest getRequest(){
        return request.get();
    }

    public static void setString(String str) {
        string.set(str);
    }

    public static String getString() {
        return string.get();
    }

    public static void setMap(String key, Object value) {
        map.get().put(key, value);
    }

    public static Object getMap(String key) {
        return map.get().get(key);
    }

    public static HashMap getMapAll() {
        return map.get();
    }

    /**
     * tomcat的线程池大小是有限的,如果请求数大于tomcat线程池大小,就会出现同一个线程处理不同的请求,那么后面的请求就可能看到前面的请求往ThreadLocal放的脏数据。
     * 因此,每次请求(http或者websocket)处理完毕,需要清理ThreadLocal
     */
    public static void clear() {
        // 注意:谨慎使用api,仅清理当前线程即可
        request.set(null);
        string.set(null);
        map.get().clear();
        log.info("ThreadLocal clear successfully");
    }
}

在拦截器中清理ThreadLocal

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    // 只拦截http请求,不拦截websocket
    ThreadLocalUtils.clear();
}

websocket相关问题

上述sprintboot拦截器无法拦截websocket请求,因此websocket连接断开时,需要另外清理ThreadLocal。
经测试(springboot版本1.5.3.RELEASE),同一个websocket连接,后端处理客户端发来的消息的线程并不是同一个,客户端主动关闭ws连接时,后端处理onClose的线程也不是处理onOpen时的线程。

因此,后端不能在onClose方法里清理ThreadLocal,要根据业务逻辑,在合适的时机清理ThreadLocal。

相关文章

网友评论

    本文标题:一次ThreadLocal引发的生产bug

    本文链接:https://www.haomeiwen.com/subject/tkihhltx.html