文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java分布式系统中session一致性问题怎么解决

2023-06-14 19:40

关注

小编给大家分享一下Java分布式系统中session一致性问题怎么解决,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

Java可以用来干什么

Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4. 网页开发;5. 企业级应用开发;6. Java大数据开发;7.游戏开发等。

业务场景

在单机系统中,用户登陆之后,服务端会保存用户的会话信息,只要用户不退出重新登陆,在一段时间内用户可以一直访问该网站,无需重复登陆。用户的信息存在服务端的 session 中,session中可以存放服务端需要的一些用户信息,例如用户ID,所属公司companyId,所属部门deptId等等。

Java分布式系统中session一致性问题怎么解决

但是随着业务的发展,技术架构需要调整,原来的单机系统逐渐被更换,架构由单机扩展到分布式,甚至当下流行的微服务。虽然在用户端看来系统仍然是一个整体,但在技术端来说业务则被拆分成多个模块,各个模块之间相互独立,甚至不在同一台物理机器上,模块之间通过 RPC 进行通信。

Java分布式系统中session一致性问题怎么解决

那么原来单机只需一份的 session, 如何满足在多系统的运行下保证会话一致性呢?单独保存在任何一个系统中都不合适,而且每个单独模块系统也可能是分布式形式的,是由集群组成。那么session的分配就更复杂了。

Redis 实现

针对以上问题,我们可能会从以下几个方面想到解决的方法,每个服务端存储一份,通过同步的方式保证一致性,但是这种方式有个很明显的缺点:session的同步需要数据传输,占内网带宽,有时延,网络不稳定的时候会造成部分系统同步延迟,那么就不能保证 session 一致性。而且所有服务端都包含所有session数据,数据量受内存限制,无法水平扩展。

那么我们是否可以单独将 session 信息存储在某一个独立的介质中,介质可以是DB也可以是缓存。

考虑到如下业务:登陆的时候我们经常会给用户一个过期时间(一般移动端常设置为7天或者一个月甚至更久),到期后用户需要输入登陆信息重新登陆,即会话过期。这种到期的设置我们自然想到了Redis的 key expire功能,所以最终我们可以将Redis引入进来实现我们的这种需求。系统如下图所示:

Java分布式系统中session一致性问题怎么解决

我们只需在用户首次登陆的时候将用户信息放到 Token并缓存到 Redis 中,同时设置一个过期时间,伪代码如下:

@Overridepublic Map login(UserDto dto) {    Map<String, Object> restMap = new HashMap<>();        // 校验登陆信息    User user = checkLoginInfo(dto);     //删除旧的token    String token = (String) redisUtils.get(CacheConstants.USER_TOKEN_KEY_COPY + user.getUserName());        if (!ObjectUtils.isEmpty(token)) {        redisUtils.delete(CacheConstants.USER_TOKEN_KEY_WEB + token);    }    // 唯一签名信息    String signStr = user.getCompanyId() + user.getUserName() + dto.getPassword() + DateUtils.now().getTime();    token = MD5Utils.md5(signStr);    // 设置用户 token    redisUtils.setExpiredAt(CacheConstants.USER_TOKEN_KEY_WEB + token, user.getId(), LOGIN_EXPIRED_TIME);    //缓存新的token    redisUtils.setExpiredAt(CacheConstants.USER_TOKEN_KEY_COPY + user.getUserName(), token, LOGIN_EXPIRED_TIME);    dto.setCompanyId(user.getCompanyId());    dto.setId(user.getId());    restMap.put("token", token);    restMap.put("userName", user.getUserName());    return restMap;}

那么在系统中如何使用呢,我们可以定义一个拦截器 SessionInterceptor,当访问 web 接口的时候检验用户的 token 信息,判断用户是否登陆,未登录的情况下一些业务接口是无法访问的,以及在登陆的情况下拿到我们需要的用户信息,如 userId。

public class SessionInterceptor {    @Autowired    private RedisUtils redisUtils;        @Autowired    private UserService userService;    @Pointcut("execution(* com.jajian.demo.web.*.controller.*.*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")    public void controllerMethodPointcut() {    }    @Around("controllerMethodPointcut()")    public Object Interceptor(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {                Signature signature = proceedingJoinPoint.getSignature();        MethodSignature methodSignature = (MethodSignature) signature;        Method targetMethod = methodSignature.getMethod();        if (targetMethod.getDeclaringClass().isAnnotationPresent(NoLogin.class) || targetMethod.isAnnotationPresent(NoLogin.class)) {            return proceedingJoinPoint.proceed();        }        // 从获取RequestAttributes中获取HttpServletRequest的信息        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);        String token = request.getHeader("token");        if(StringUtils.isEmpty(token)){            Log.debug("验证token", "token验证失败,{}", "token不存在");            throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");        }        Integer userId= (Integer)redisUtils.get(CacheConstants.USER_TOKEN_KEY_WEB + token);               if (null == userId) {            Log.debug("验证token", "token验证失败,{}", "token超时");            throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");        }        User user = userService.getById(userId.longValue());        if (ObjectUtils.isEmpty(user)){            Log.debug("验证token", "token验证失败,{}", "用户信息不存在");            throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");        }        if (user.getStatus() == UserStatusEnum.NO.getCode() || user.getDeleteFlag() == DeleteFlagEnum.YES.getCode()){            Log.debug("验证token", "token验证失败,用户信息异常 userName : {}, status : {},deleteFlag : {}", user.getUserName(),user.getStatus(), user.getDeleteFlag());            throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");        }        return proceedingJoinPoint.proceed();    }    }

以上是“Java分布式系统中session一致性问题怎么解决”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯