大家都知道,Tomcat服务器并不共享 Session ,当一个项目部署在一个集群时,假设第一次路由分配给到 tomcat01,Session会保存在这台服务器上,但是短时间内当第二次访问时(可以看作是无操作,再次刷新时),被分配到tomcat02上,这台服务器的tomcat就没有Session的,这时显示前端显示未登录,这样显然是不合理的。这就叫Tomcat服务器的Session混淆问题。
Session共享问题主要说的是多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务器时导致数据丢失的问题。那么久需要思考一个替代的方案了,而且这个替代方案应该满足以下三点:数据共享、内存存储、key:value结构。满足以上三点的,主流的服务器可以选择 Redis 服务器,首先时基于内存存储的,访问速度快;集群数据共享,可以解决session混淆问题;而且是key:value结构存储数据的,存取方便;并且具有各种缓存淘汰机制,不用过于担心性能问题。
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式,可以省去我们自定义RedisTemplate的过程,可以直接调用:
1 | @Autowired |
下面是基于StringRedisTemplate来实现的Session共享:
一般的登录登录页面可能会用到验证码来校验,这时也可以传入session来辅助登录。
下面是一个根据手机号来获取验证码登录的例子:
1 | @Override |
注意:RegexUtils是自定义的工具,isPhoneInvalid
方法是用来判断手机号是否符合规范,符合才能发送验证码;LOGIN_CODE_KEY
和LOGIN_CODE_TTL
静态变量都是自定义的值,防止多处编写出错,第一个是Redis的key的前缀,第二个是验证码过期时间。
登录的共享session的处理,并且用到了hutool工具包,代码如下所示:
1 | @Override |
注意:LoginFormDTO是只封装了手机号、验证码和密码三个字段,防止数据过多而导致有泄露的风险。这里使用UUID来作为token来替代session存储到redis服务器中,当用户登录的时候,就会尝试获取token,如果能够获取到,就直接登录并刷新token有效期,为空就创建存储并放行。
对于token有效期的刷新,可以自定义一个拦截器,用来刷新session的有效期,防止用户在使用过程中session过期的问题:
1 | /** |
1 | /** |
注意:UserHolder是基于ThreadLocal来实现的,在同一个线程内,方法区内存是共享的,所以可以获得这个线程的信息。需要自定义拦截的路径,如果配置多个拦截器,要给拦截器指定执行顺序,order方法可以给自定义的拦截器加权重,权重越小执行优先级越高。
1 | public class UserHolder { |
以上是基于Redis实现Session共享的问题的一种解决方案,还有更好的替代方案,比如在处理拦截的时候,可以使用更专业的工具,如spring getaway 可以很好地在微服务架构中应用;比如生成token的算法,可以选择雪花算法等。
本文链接: https://longzas.github.io/2023/08/22/%E5%9F%BA%E4%BA%8ERedis%E5%AE%9E%E7%8E%B0%E5%85%B1%E4%BA%ABSession%E7%99%BB%E5%BD%95/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!