集群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;
@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); 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 { String token = request.getHeader("authorization"); if(StrUtil.isBlank(token)){ return true; } Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token); if(entries.isEmpty()){ return true; } UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false); UserHolder.saveUser(userDTO); 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(); } }
|