集群session共享问题及解决方案

问题:之前的设计方案是什么?有什么问题?解决方案是什么?为什么使用这种解决方案(Redis)?

​ 当前我们的系统结构作为单体项目是可以的,但是一旦用户量比较大,想要转化为集群项目,这种架构就不行了。主要原因是,当前系统中,我们是基于session来进行数据共享的,但session中的数据只能在同一个服务器中进行共享,如果我们想要扩展为集群项目,使用session就无法做到多个服务器之间数据的共享。因此,必须使用另一种工具作为数据共享。而Redis能够很好的胜任这一要求。

​ 1. Redis本身是一个数据库,而数据库本身就是用于存储数据以便进行数据共享的;2. Redis的数据是存放在内存中的,读取速率快,性能好;3. Redis支持各种数据类型,能够很好的满足各种数据的存储。

验证码存储进Redis以什么作为Key,Value选择什么数据类型?

​ 以手机号作为key,value选择String类型即可,为了减少内存消耗,一般会给验证码存储进Redis时设置一个有效期。

用户信息存储进Redis以什么作为Key,Key的选择需要满足什么条件(唯一,易携带)?为什么不用手机号作为Key?Value选择什么数据类型,为什么选择这种数据类型?

​ key需要具备唯一性以及请求时方便携带的特点。一般使用一个随机化字符串token作为key,如果使用手机号作为key会暴露用户信息,value选择hash类型,hash结构可以将对象中每个字段但独立存储,可以针对单个字段做CRUD

什么是token,它和cookie和session有什么区别?

​ session顾名思义,就是指一种会话技术,由于HTTP协议是无状态协议,当客户端和服务器端建立连接后,它们之间的状态就会断开。这种特点为鉴别用户是否是同一个造成了困难,为了解决该问题,就提供了session机制。当客户端向服务器发送请求时,如果该客户是第一次发送请求,服务器会创建一个session对象用于表示两者建立连接的状态,并生成一个sessionid,sessionid会随着响应报文发送给客户端。客户端收到后会将其存放在cookie中,下一次请求时,请求路径会携带cookie,服务器收到后根据cookie找到对应的session,找到两者的连接。token也是用于鉴权的,它的作用就相当于sessionid,只不过它一般是按照某种加密方式生成的一个字符串,使用token相对于cookie来说更加安全方便,相对于session来说消耗的内存更小,因此现在项目中一般使用token作为鉴权令牌使用。

如何定期更新Redis中存储的用户信息(用户活跃时不断更新Redis)?以及对其进行优化

​ 为了减少内存消耗,一般会给存储进Redis中的数据设置一个有效期,有效期一到,Redis就会自动清除数据。这就会引发一个问题,有时候你用着用着就自动退出登录了。这是我们需要解决的问题,常见的解决方案是,当用户在页面上活跃时,就定期去更新这个有效期。做法就是,再设置一个拦截器,一个拦截器用于更新token有效期,另一个用于鉴权登录。

设置拦截器的执行顺序?

​ 需要保证更新token有效期的拦截器先执行,可以通过设置拦截器的order属性来规定拦截器的执行顺序,order值越小,优先级越高。

​ 拦截器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.hmdp.config;

import com.hmdp.utils.RefreshInterceptor;
import com.hmdp.utils.UserLoginInteceptorHandler;
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;

import javax.annotation.Resource;

@Configuration
public class InceptorConfig implements WebMvcConfigurer {
@Resource
StringRedisTemplate stringRedisTemplate;
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//登录拦截器
registry.addInterceptor(new UserLoginInteceptorHandler())
.excludePathPatterns(
//放行请求,这些请求不做拦截
"/user/login",
"/user/code",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/voucher-order/**"
).order(1);
//token刷新拦截器
registry.addInterceptor(new RefreshInterceptor(stringRedisTemplate))
.addPathPatterns("/**").order(0);
}
}

拦截器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
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.Map;
import java.util.concurrent.TimeUnit;

public class RefreshInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshInterceptor(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取token
String token = request.getHeader("authorization");
if(StrUtil.isBlank(token)){
//token为空
return true;
}
//查看redis中是否有用户信息
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
if(entries.isEmpty()){
return true;
}
//将map转化为UserDTO类型
UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false);
UserHolder.saveUser(userDTO);
//更新Redis数据
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}