Redis实战之session共享
当线上集群时候,会出现session共享问题。
虽然Tomcat提供了session copy的功能,但是缺点比较明显:
1:当Tomcat多的时候,session需要大量同步到多台集群上,占用内网宽带
2:同一个用户session,需要在多个Tomcat中都存在,浪费内存空间.
凯哥自己开发的,领取外卖、打车、咖啡、买菜、各大电商的优惠券的公¥众¥号。如下图:

正文开始:
如果要替换掉Tomcat的session共享,替代方案应该满足:
1:数据共享
2:内存存储
3:key\value结构

基于Redis实现共享session登录
本文由凯哥Java(gz#h:kaigejava),个人blog:www#kaigejava#.com。发布于凯哥Java个人blog
再来回顾下将验证码保存在session中业务流程

我们在session中存放的是:session.setAttribute("code", code); 因为session的特点,每次访问都是一个新的sessionId.我们可以直接使用code作为key.思考:那么如果换成了Redis,还能使用code作为可以吗?
将用户信息存放在session中流程:

用户信息在session中存放:session.setAttribute("user", user); 同样思考:那么如果换成了Redis,还能使用user作为可以吗?
将code和user信息存放在Redis中,流程如下:

验证码数据结构是:string类型的
用户对象数据类型是:hash类型的
根据上面分析,我们修改原来代码:
需要考虑的是:Redis的key规则、过期时间
1:发送验证码的时候,将验证码存放到Redis中时候,需要考虑过期时间。其核心代码如下:

stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
2:用户登录的时候,将校验验证码以及用户信息存放到Redis中后,返回token
需要考虑的:
1:token不能重复
2:用户过期时间
3:登录成功后,要将token返回给前端
4:用户只要访问,Redis中的过期时间就要延长-在拦截器中处理的
用户登录核心代码修改:

//2.1:校验验证码是否正确
//String code = (String) session.getAttribute("code");
String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (StringUtils.isEmpty(code) || !code.equals(loginForm.getCode())) {
return Result.fail("验证码错误!");
}
//2.2:根据手机号查询,如果不存在,创建新用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "phone", "nick_name");
queryWrapper.eq("phone", phone);
User user = this.getOne(queryWrapper);
if (Objects.isNull(user)) {
log.info("新建用户");
//新建用户
user = createUserWithPhone(phone);
}
//2.3:保存用户到session中
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setIcon(user.getIcon());
userDTO.setNickName(user.getNickName());
//session.setAttribute("user", userDTO);
//2.3.1:获取随机的token,作为用户登录的令牌
String token = UUID.randomUUID().toString(true);
//2.3.2:将用户以hash类型存放到Redis中==》将user对象转换成map
//user对象里有非string类型的字段,用这个方法会报错的
// Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);
Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
, CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+token,userMap);
//LOGIN_USER_TOKEN_TTL
stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,LOGIN_USER_TOKEN_TTL,TimeUnit.MINUTES);
//2.3.3: 将token返回
return Result.ok(token);
需要注意:
在使用stringRedisTemplate存放hash对象的时候,对象中所有的key只能是string类型,如果存在非string类型会报错的。所以这里使用了hootool的BeanUtil工具类:
Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
, CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
拦截器修改代码:
因为拦截器是我们自定义的,所以不能被spring容器管理的,RedisTemplate就不能自动注入了。我们就使用有参构造器,传递:

public class LoginRedisInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
/**
* 因为这个类不能被spring管理,所以不能直接注入RedisTemplate对象。通过构造函数传递
* @param stringRedisTemplate
*/
public LoginRedisInterceptor(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1:从请求中获取到token
String token = request.getHeader("authorization");
if(StringUtils.isEmpty(token)){
response.setStatus(401);
return false;
}
//2:基于token获取redis中用户对象
String key = LOGIN_USER_TOKEN_KEY+token;
Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key);
//3:判断
if(userMap.isEmpty()){
response.setStatus(401);
return false;
}
//将map转对象
UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
UserHolder.saveUser(user);
//刷新token的过期时间
stringRedisTemplate.expire(key,LOGIN_USER_TOKEN_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
总结:
在使用Redis替换session的时候,需要考虑的问题:
1:选择合适的数据结构
2:选择合适的key
3:选择合适的存储粒度
Redis实战之session共享的更多相关文章
- 基于Redis缓存的Session共享(附源码)
基于Redis缓存的Session共享(附源码) 在上一篇文章中我们研究了Redis的安装及一些基本的缓存操作,今天我们就利用Redis缓存实现一个Session共享,基于.NET平台的Seesion ...
- 【Spring Session】和 Redis 结合实现 Session 共享
[Spring Session]和 Redis 结合实现 Session 共享 参考官方文档 HttpSession with Redis Guide https://docs.spring.io/s ...
- .Net Core Web Api实践(三).net core+Redis+docker实现Session共享
前言:上篇文章介绍了.net core+Redis+IIS+nginx实现Session共享,本来打算直接说明后续填坑过程,但毕竟好多坑是用docker部署后出现的,原计划简单提一下.net core ...
- 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案
分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...
- springboot+redis实现分布式session共享
官方文档,它是spring session项目的redis相关的一个子文档:https://docs.spring.io/spring-session/docs/2.0.0.BUILD-SNAPSHO ...
- springboot集成springsession利用redis来实现session共享
转:https://www.cnblogs.com/mengmeng89012/p/5519698.html 这次带来的是spring boot + redis 实现session共享的教程. 在sp ...
- tomcat redis 集群 session共享
jcoleman/tomcat-redis-session-manager: Redis-backed non-sticky session store for Apache Tomcathttps: ...
- Spring Session + Redis实现分布式Session共享
发表于 2016-09-29 文章目录 1. Maven依赖 2. 配置Filter 3. Spring配置文件 4. 解决Redis云服务Unable to configure Redis to k ...
- springboot+spring session+redis+nginx实现session共享和负载均衡
环境 centos7. jdk1.8.nginx.redis.springboot 1.5.8.RELEASE session共享 添加spring session和redis依赖 <depen ...
- SpringBoot2.x+Redis+nginx实现session共享和负载均衡
1.创建SpringBoot项目添加依赖 <dependency> <groupId>org.springframework.session</groupId> & ...
随机推荐
- java --面试题大全
J2EE面试题 文档版本号:V2.0 2016年11月 目 录 1. Java基础部分 8 1.1. 一个".java"源文 ...
- jsp---------------------复选框,全选按钮
js部分:注意:<script type="text/javascript" ></script>内不能有其他内容,否则会无效,若有则另起一对<scr ...
- VulnHub_DC-6渗透流程
DC-6 kali:192.168.157.131 靶机:192.168.157.150 修改host文件,添加192.168.157.150 wordy不然解析不了域名 信息收集 发现是WordPr ...
- Java-Session服务器端会话技术
Session 概念:服务器端会话技术,在一次对话的多次请求间共享数据,将数据保存在服务器端的对象中.HttpSession 快速入门: 获取Session对象 * HttpSession sessi ...
- IP协议学习笔记
目录 IP地址格式 IP分类 CIDR 和 子网掩码介绍 NAT+公网.私网地址 CIDR 与 VLSM VLSM 子网划分案例 练习 Reference IP的作用类似物理世界中的地址,用于定位机器 ...
- vue中sass与SCSS的区别
在Vue中,通常使用SCSS(Sassy CSS)而不是Sass来编写样式.SCSS是Sass的一种语法扩展,提供了更多的功能和灵活性,因此在Vue项目中更常见.下面是Sass和SCSS之间的主要区别 ...
- 商业级java开发单体项目环境搭建
写在开发前,端口经常被战占,windows10用以下命令先查先杀,netstat -aon | findstr "8080" taskkill /F /PID <PID> ...
- c语言模拟Python的命名参数
最近在书里看到的,让c语言去模拟其他语言里有的命名函数参数.觉得比较有意思所以记录一下. 目标 众所周知c语言里是没有命名函数参数这种东西的,形式参数虽然有自己的名字,但传递的时候并不能通过这个名字来 ...
- 对比python学julia(第二章)--(第二节)勾股树—分形之美
2.1.问题描述 二话不说,先上图: 图一.勾股定理图形 图二.勾股树 ...
- 【Java】树状节点结构的数据
数据库的菜单,权限表是具有多层级结构,有ID和PARENT_ID两个关键性的字段 通过PARENT_ID和ID相等构建层级结构: 然后需要在Java中构建出层级的数据结构,然后输出成JSON返回给前端 ...