分布式系统中session一致性问题

13894260-95bf0701d47505a4
分布式系统中session一致性问题

业务场景

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

13894260-dfaadb12545f773c
分布式系统中session一致性问题

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

13894260-4cadc80942e166cc
分布式系统中session一致性问题

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

Redis 实现

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

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

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

13894260-9d6eb4d69bcc8d68
分布式系统中session一致性问题

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

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
 public 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;
 }
</pre>

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

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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();
 }

}
</pre>

以上实现方式简单易用,而且Redis 在分布式系统中的使用率也很高,所以无需额外的技术引入。可以支持水平扩展,数据库或缓存水平切分即可,服务端重启或者扩容都不会有session丢失的情况发生。

内容概要:本文档详细介绍了在三台CentOS 7服务器(IP地址分别为192.168.0.157、192.168.0.158和192.168.0.159)上安装和配置Hadoop、Flink及其他大数据组件(如Hive、MySQL、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala)的具体步骤。首先,文档说明了环境准备,包括配置主机名映射、SSH免密登录、JDK安装等。接着,详细描述了Hadoop集群的安装配置,包括SSH免密登录、JDK配置、Hadoop环境变量设置、HDFS和YARN配置文件修改、集群启动与测试。随后,依次介绍了MySQL、Hive、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala和Flink的安装配置过程,包括解压、环境变量配置、配置文件修改、服务启动等关键步骤。最后,文档提供了每个组件的基本测试方法,确保安装成功。 适合人群:具备一定Linux基础和大数据组件基础知识的运维人员、大数据开发工程师以及系统管理员。 使用场景及目标:①为大数据平台建提供详细的安装指南,确保各组件能够顺利安装和配置;②帮助技术人员快速掌握Hadoop、Flink等大数据组件的安装与配置,提升工作效率;③适用于企业级大数据平台的建与维护,确保集群稳定运行。 其他说明:本文档不仅提供了详细的安装步骤,还涵盖了常见的配置项解释和故障排查建议。建议读者在安装过程中仔细阅读每一步骤,并根据实际情况调整配置参数。此外,文档中的命令和配置文件路径均为示例,实际操作时需根据具体环境进行适当修改。
在无线通信领域,天线阵列设计对于信号传播方向和覆盖范围的优化至关重要。本题要求设计一个广播电台的天线布局,形成特定的水平面波瓣图,即在东北方向实现最大辐射强度,在正东到正北的90°范围内辐射衰减最小且无零点;而在其余270°范围内允许出现零点,且正西和西南方向必须为零。为此,设计了一个由4个铅垂铁塔组成的阵列,各铁塔上的电流幅度相等,相位关系可自由调整,几何布置和间距不受限制。设计过程如下: 第一步:构建初级波瓣图 选取南北方向上的两个点源,间距为0.2λ(λ为电磁波波长),形成一个端射阵。通过调整相位差,使正南方向的辐射为零,计算得到初始相位差δ=252°。为了满足西南方向零辐射的要求,整体相位再偏移45°,得到初级波瓣图的表达式为E1=cos(36°cos(φ+45°)+126°)。 第二步:构建次级波瓣图 再选取一个点源位于正北方向,另一个点源位于西南方向,间距为0.4λ。调整相位差使西南方向的辐射为零,计算得到相位差δ=280°。同样整体偏移45°,得到次级波瓣图的表达式为E2=cos(72°cos(φ+45°)+140°)。 最终组合: 将初级波瓣图E1和次级波瓣图E2相乘,得到总阵的波瓣图E=E1×E2=cos(36°cos(φ+45°)+126°)×cos(72°cos(φ+45°)+140°)。通过编程实现计算并绘制波瓣图,可以看到三个阶段的波瓣图分别对应初级波瓣、次级波瓣和总波瓣,最终得到满足广播电台需求的总波瓣图。实验代码使用MATLAB编写,利用polar函数在极坐标下绘制波瓣图,并通过subplot分块显示不同阶段的波瓣图。这种设计方法体现了天线阵列设计的基本原理,即通过调整天线间的相对位置和相位关系,控制电磁波的辐射方向和强度,以满足特定的覆盖需求。这种设计在雷达、卫星通信和移动通信基站等无线通信系统中得到了广泛应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值