账号登录后,用户不主动退出账号,在使用过程中,session/token登录信息失效,需要重新登录

账号登录后,用户不主动退出账号,在使用过程中,session/token登录信息失效,需要重新登录

这个是一个线上问题,排查的过程。
在pc端,用户登录后,用户不主动退出账号,在使用过程中,有一定几率会提示需要登录信息失效。token失效的情况,

问题现象:

1、初始现象:登录后,无论是否关闭浏览器,鉴权session不定期被重置失效时间,重置时间不固定;
2、进一步排查,连接redis主节点并执行monitor后,发现对应session修改来自于sys和cms两个项目
3、具体现象:当A账号被登录,B账号登录或则注销时,有一定几率A账号的session被修改。其中登录出现的概率最大;
4、进一步缩减涉及服务范围,只部署鉴权,等少量服务,仍然存在被修改的情况;

问题解决办法:

1、检查代码,发现里面有多个拦截器和过滤器对session有更新时间操作对最后一个执行的过滤器(AuthFilter)中,完成chain.doFilter(request, response);后增加Threadlocal.remove()操作后解决问题:

chain.doFilter(request, response);
CurrentAuthUserManager.removeCurrentAuthUser();
sessionManager.removeCurrentSession();
//其中removeCurrentAuthUser()和removeCurrentSession()分别执行的是userid和sessionid的threadlocal.remove()操作

问题原因:

1、ThreadLocal源码set()/get()方法

public void set(T value) {
      Thread t = Thread.currentThread();
      ThreadLocalMap map = getMap(t);
      if (map != null)
          map.set(this, value);
      else
          createMap(t, value);
  }

  public T get() {
      Thread t = Thread.currentThread();
      ThreadLocalMap map = getMap(t);
      if (map != null) {
          ThreadLocalMap.Entry e = map.getEntry(this);
          if (e != null) {
              @SuppressWarnings("unchecked")
              T result = (T)e.value;
              return result;
          }
      }
      return setInitialValue();
  }

  ThreadLocalMap getMap(Thread t) {
      return t.threadLocals;
  }

即ThreadLocal获取的ThreadLocalMap其实都是Thread的私有成员变量,并通过ThreadLocalMap的get(key)和set(key)方式获取对应值,其中key为Thread对象。通过这种方式获取的线程的私有成员。

//Thread源码中,ThreadLocalMap为Thread成员
ThreadLocal.ThreadLocalMap threadLocals = null;

带来的问题问题是:如果Thread不销毁,那么ThreadLocal .set()的值将一直存在

2、springboot内置tomcat的线程池Springboot内置tomcat默认为线程池模式,默认线程池配置:

maxThreads:处理的最大并发请求数,默认值200
minSpareThreads:最小线程数始终保持运行,默认值10

根据minSpareThreads=10可知,在http并发低于10的时候,线程是不会被销毁的,会持续在线程池中重复使用。如果请求并发大于10,那么会创建更多线程直到200,然后在空闲一定时间会再被销毁,但是10个以内的线程将一直不会被销毁!

和第一点ThreadLocal代码综合可知:由于tomcat线程池不会销毁线程,导致10次以后ThreadLocal.get()始终未上一次该线程生效set()的值,引起逻辑异常重现示例代码:

@RestController
@RequestMapping("/thlocal")
@Slf4j
public class ThreadLocalTest {

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

    @RequestMapping(value = "/print", method = {RequestMethod.GET, RequestMethod.POST})
    public String thlocalPrint() {
        Thread curt = Thread.currentThread();
        String rsp = "not exist";
        if (null == thlocal.get()) {
            thlocal.set(String.valueOf(DggKeyWorker.nextId()));
        } else {
            rsp = "exist:" + thlocal.get();
        }
        rsp = String.format("ThreadId:%s %s", curt.getId(), rsp);
        log.info(rsp);
        return rsp;
    }
}

对应日志

ThreadId:62 not exist
ThreadId:63 not exist
ThreadId:64 not exist
ThreadId:65 not exist
ThreadId:66 not exist
ThreadId:67 not exist
ThreadId:68 not exist
ThreadId:69 not exist
ThreadId:70 not exist
ThreadId:71 not exist
ThreadId:62 exist:7845178637774557184
ThreadId:63 exist:7845178641041920000
ThreadId:64 exist:7845178643764023296
ThreadId:65 exist:7845178645232029696
ThreadId:66 exist:7845178652232323072
ThreadId:67 exist:7845178654644047872
ThreadId:68 exist:7845178655801675776
ThreadId:69 exist:7845178657135464448
ThreadId:70 exist:7845178658607665152
ThreadId:71 exist:7845178660130197504
ThreadId:62 exist:7845178637774557184
ThreadId:63 exist:7845178641041920000
ThreadId:64 exist:7845178643764023296
ThreadId:65 exist:7845178645232029696
ThreadId:66 exist:7845178652232323072
ThreadId:67 exist:7845178654644047872
ThreadId:68 exist:7845178655801675776
ThreadId:69 exist:7845178657135464448

ThreadLocal使用建议:

使用完毕后,无论应用的线程模型如何,均执行remove()操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值