当前位置: 首页> 财经> 产业 > 公司名称注册查询官网入口_绿色资源网_seo引擎优化是做什么的_网络整合营销推广

公司名称注册查询官网入口_绿色资源网_seo引擎优化是做什么的_网络整合营销推广

时间:2025/8/8 22:05:32来源:https://blog.csdn.net/weixin_42541479/article/details/147105977 浏览次数:0次
公司名称注册查询官网入口_绿色资源网_seo引擎优化是做什么的_网络整合营销推广

使用Redis解决:集群的Session共享问题


session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同的tomcat服务时导致数据丢失的问题。

在这里插入图片描述


  1. 问题背景
    • 无状态HTTP协议:HTTP协议本身是无状态的,服务器无法直接识别不同请求是否来自同一用户。
    • Session的作用:通过Session(通常存储在服务器内存中)跟踪用户状态(如登录信息、购物车数据等)。
    • 集群环境的问题:当用户请求被负载均衡分配到不同服务器时,每个服务器的Session数据是独立的。若用户第二次请求被路由到另一台服务器,该服务器可能没有对应的Session数据,导致用户状态丢失。
  2. 问题示例
    假设用户访问一个由3台服务器组成的集群:
    1. 用户首次访问服务器A,登录成功后,Session(含用户ID)存储在A的内存中。
    2. 下一次请求被负载均衡分配到服务器B,但B的内存中没有该用户的Session。
    3. 结果:用户被迫重新登录,体验中断。
  3. 核心挑战
    • ​数据一致性:多个服务器需要共享或同步Session数据。
    • ​可用性:Session存储服务需高可用,避免单点故障。
    • 性能:频繁的Session读写需低延迟,不影响用户体验。
  4. 常见解决方案
    • ​方案1:Session复制(Replication)​
      • 原理:将Session复制到集群中所有服务器。
      • ​优点:无需外部依赖,简单易实现。
      • 缺点:
        • 内存和带宽开销大(尤其节点多时)。
        • 数据一致性问题(如网络分区时)。
    • 方案2:集中式存储(推荐)​
      • ​原理:使用独立存储(如Redis、Memcached、数据库)保存Session,所有服务器共享访问。
      • ​优点:
        • 解耦Session与服务器,扩展性强。
        • 支持高可用(如Redis集群)。
      • ​缺点:引入额外组件,增加系统复杂度。
    • ​方案3:粘性会话(Sticky Session)​
      • ​​原理:负载均衡器将同一用户的请求始终路由到同一台服务器。
      • ​​优点:天然保证Session一致性。
      • ​​缺点:
        • 负载不均衡(某些服务器可能过载)。
        • 服务器故障时,其上的Session永久丢失。
    • 方案4:无状态设计(如JWT)​
      • ​原理:将用户状态直接存储在客户端(如Token中),服务器无需保存Session。
      • ​优点:彻底解决共享问题,天然支持分布式。
      • ​缺点:Token体积较大,且需处理加密和安全性。
  5. 实际应用场景
    • ​电商/社交平台:常用集中式存储(如Redis)确保高并发下的Session一致性。
    • ​微服务架构:通过JWT等无状态方案简化服务间通信。
    • ​传统Java EE集群:Tomcat可通过Redis Session Manager插件实现共享。
  6. 最佳实践建议
    • ​​优先选择集中式存储​(如Redis),兼顾性能和扩展性。
    • ​设置合理的Session过期时间,减少存储压力。
    • ​启用存储的高可用模式​(如Redis Sentinel或Cluster)。
    • ​结合HTTPS和加密,防止Token或Session被窃取。

在这里插入图片描述

文章目录

  • 使用Redis解决:集群的Session共享问题
    • 一、基于Session实现的用户登录
    • 二、使用Redis替代Session,解决集群的Session共享问题
      • 1.UserServiceImpl业务层实现
        • 前置条件,注入 StringRedisTemplate
        • 发送短信验证码
        • 短信验证码登录、注册
      • 2.LoginInterceptor登录拦截器
        • 校验登录状态
      • 3.添加登录拦截器到WebMvcConfigurer
    • 三、优化登录拦截器
      • 1.引入刷新token拦截器:RefreshTokenInterceptor
      • 2.重构LoginInterceptor登录拦截器逻辑
      • 3. 添加刷新token拦截器到WebMvcConfigurer配置中


一、基于Session实现的用户登录

传送门:基于Session实现用户登录

二、使用Redis替代Session,解决集群的Session共享问题

将原存储到session的地方,替换为存储到redis中。

1.UserServiceImpl业务层实现

前置条件,注入 StringRedisTemplate
    @Resourceprivate StringRedisTemplate stringRedisTemplate;

发送短信验证码
符合
不符合
开始
提交手机号
校验手机号
生成验证码
保存验证码到Redis
发送验证码
结束
    @Overridepublic Result sendCode(String phone) {// 1. 校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4.保存验证码到redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5.发送验证码log.debug("发送短信验证码成功,验证码:{}", code);return Result.ok();}

短信验证码登录、注册
一致
存在
不一致
不存在
开始
提交手机号和验证码
校验验证码
根据手机号查询用户
判断用户是否存在
保存用户到session
结束
创建新用户
保存用户到数据库
    @Overridepublic Result login(LoginFormDTO loginForm) {// 1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误!");}// 2.从redis获取验证码String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 3.不一致,报错return Result.fail("验证码错误");}// 4.一致,根据手机号查询用户 select * from tb_user where phone = ?User user = lambdaQuery().eq(User::getPhone, phone).one();// 5.判断用户是否存在if (user == null) {// 6.不存在,创建新用户并保存user = createUserWithPhone(phone);}// 7.保存用户信息到redis中// 7.1 随机生成token(不带划线),作为登录令牌String token = UUID.randomUUID().toString(true);// 7.2 将User对象转化为HashMap存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create() // 数据拷贝选项.setIgnoreNullValue(true) // 忽略null值.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()) // 字段值编辑器);// 7.3 存储String tokenKey = RedisConstants.LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4 设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);return Result.ok(token);}private User createUserWithPhone(String phone) {// 1.创建用户User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));// 2.保存用户save(user);return user;}

2.LoginInterceptor登录拦截器

校验登录状态
非空
存在
token为空
开始
获取请求头中的token
判断token是否为空
从Redis获取用户
判断用户是否存在
保存用户到ThreadLocal
刷新token有效期
放行
结束
拦截
拦截
/*** LoginInterceptor 登录拦截器*/
public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;/*** 未加入Spring IOC容器管理,使用构造方法初始化 stringRedisTemplate*/public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {response.setStatus(401);return false;}// 2.基于token获取redis中的用户String tokenKey = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);// 3.判断用户是否存在if (userMap.isEmpty()) {// 4.不存在,拦截response.setStatus(401);return false;}// 5.将查询到的Hash数据转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}

3.添加登录拦截器到WebMvcConfigurer

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/user/code", "/user/login", "/othoer/**");}
}

三、优化登录拦截器

为解决访问未拦截接口时,不刷新token导致用户登录过期的问题,引入RefreshTokenInterceptor

在这里插入图片描述

1.引入刷新token拦截器:RefreshTokenInterceptor

// 关键包
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;/*** RefreshTokenInterceptor 刷新Token拦截器*/
public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;/*** 未加入Spring IOC容器管理,使用构造方法初始化 stringRedisTemplate*/public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {// 放行return true;}// 2.基于token获取redis中的用户String tokenKey = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);// 3.判断用户是否存在if (userMap.isEmpty()) {// 放行return true;}// 5.将查询到的Hash数据转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}

2.重构LoginInterceptor登录拦截器逻辑

// 关键包
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.concurrent.TimeUnit;/*** LoginInterceptor 登录拦截器*/
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {// 没有,需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户则放行return true;}}

3. 添加刷新token拦截器到WebMvcConfigurer配置中

// 关键包
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code", "/user/login", "/other/**").order(1);// 刷新token拦截器,order=0优先加载registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}

关键字:公司名称注册查询官网入口_绿色资源网_seo引擎优化是做什么的_网络整合营销推广

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: