Java架构师方案—分布式session基于redis的共享机制(附完整项目代码)
- 1. 读这篇文章你可以学到的东西
- 2. 基于redis存储共享session方案
- 3. 登入数据存放到session服务器
- 4. 拦截器注册及配置
- 5. 简单谈谈我对redis共享session方案的看法
1. 读这篇文章你可以学到的东西
认识问题
这篇文章介绍的是服务在集群部署的情况下,用户的请求会被负载均衡到不同的服务器,但是用户的登入数据、权限数据、操作数据等session数据只会存在一个服务器的内存中。若用户下一次请求被转发到其他没有session数据的服务器上,那用户就还需要再重新登入,重新在网站上进行业务操作后创建操作数据。
用户体验极差
如果用户session数据只在一个服务器上,那用户访问另外服务器上服务时候,就会要求用户重新登入和业务操作。多次要求用户进行重新登入和业务操作,这样的体验是个灾难。
解决方案
将用户的session状态从服务中剥离出来,放到一个独立的session服务器上,这里选择redis来建session服务器。集群服务有关状态数据的操作都要和redis服务器交互。这种分布式session方案我们成为共享session服务器方案。
2. 基于redis存储共享session方案
正如上面的架构图所示,所有的应用服务器关于session数据保存和获取操作都会与session服务器交互。用户每次请求的权限验证都与session服务器中的数据进行比对。如果用户已经登入了,那登入状态数据就会保存在session服务器,用户访问其他服务器时就不需要重新登入
本文的方案,笔者提供了一个完整的demo项目,项目地址看末尾获取方式。
SpringBoot项目引入redis依赖
<!-- 引入 redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
redis配置
springboot项目默认使用StringRedisTemplate,StringRedisTemplate和RedisTemplate不同,前者比较简单,只需要配置redis参数就行。
第一种
在application.yml文件中配置下面的参数。
spring:
redis:
#redis数据库地址
host: localhost
port: 6379
password:
timeout: 1000
#redis数据库索引,默认0
database: 1
第二种
在application.properties文件中配置下面的参数。
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000
使用StringRedisTemplate
直接在service实现类中注入StringRedisTemplate。
@Autowired
private StringRedisTemplate redisTemplate;
到这里,我们项目就能正常使用redis服务了。
redis操作实现类RedisOperator
该类简单封装了String类型的redis命令:set、get, ttl等,业务逻辑可以直接调用。
/**
* 使用redisTemplate的操作实现类
* @author YI
* @date 2018-6-12 10:54:28
*/
@Component
public class RedisOperator {
@Autowired
private StringRedisTemplate redisTemplate;
// Key(键),简单的key-value操作
/**
* 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
*
* @param key
* @return
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
......//因类代码比较多,这里就不全部展示了,读者可以查阅项目demo
3. 登入数据存放到session服务器
用户登入
用户登入接口逻辑:
- 获取username和password数据,与数据库保存的用户密码比对。
- 用户名和密码比对正确后,生成唯一的uuid作为用户的登入状态session数据。
- 通过redis操作类将session数据保存在redis中,key由username生成,并设置30分钟有效时间。
- 将用户username保存到cookie中。
@PostMapping(value = "loginCheck")
@ResponseBody
public RestResponseBo loginCheck(@RequestParam String username,
@RequestParam String password,
HttpServletRequest request,
HttpServletResponse response) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
return RestResponseBo.fail("用户名或者密码不能为空!");
}
if(!username.equals("admin") || !password.equals("admin"))
{
return RestResponseBo.fail("用户名或者密码不正确!");
}
String token = StrUtil.uuid();
//存放唯一的 token 并设置过期时间
operator.set(JdkApiInterceptor.USER_REDIS_SESSION + ":" + username, token, REDIS_TIMEOUT);
//设置用户 密码 token等信息
operator.set(username, username+":"+password+":"+token);
//用户浏览器会存放两种cookie: userToken,userId。
CookieUtil.addCookie("userName", username);
return RestResponseBo.ok();//
}
拦截器JdkApiInterceptor校验用户登入状态
拦截逻辑:
- 先从请求cookie 中获取username。
- 再查询redis中该用户的登入session数据。
- 不为空则是已登入,为空则是未登入或登入状态过期。
- 未登入或登入状态过期则重定向到登入页面。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
//获取用户cookies
String userName = CookieUtil.getCookie("userName");
//放开登入接口
// String uri = request.getRequestURI();
// logger.info("请求uri:" + uri);
//
// if(uri.equals("loginCheck"))
// return true;
//
logger.info(" ======= 拦截UserId:" + userName);
//用户id和token都不为空
if (!StringUtils.isEmpty(userName)) {
//根据userid生成唯一key从redis中查出唯一token
String uniqueToken = redis.get(USER_REDIS_SESSION + ":" + userName);
logger.info("拦截uniqueToken:" + uniqueToken);
//如果唯一token为空 ,则拦截url重定向到登入页面
if (StringUtils.isEmpty(uniqueToken)) {
response.sendRedirect("/login");
returnErrorResponse(response, "请登录...");
return false;
}
//用户id和token有一个为空,则重定向登入页面
} else {
response.sendRedirect("/login");
returnErrorResponse(response,"请登录...");
return false;
}
return true;
}
到此,就能实现不同服务器的session共享了。
4. 拦截器注册及配置
注册
使用@Configuration修饰创建配置类WebSecurityConfig
@Configuration
public class WebSecurityConfig implements WebMvcConfigurer {
...
创建拦截器bean对象,放入ioc容器。
@Bean
public JdkApiInterceptor JdkApiInterceptor(){
return new JdkApiInterceptor();
}
配置
配置拦截器要拦截的url和不拦截的url
/**
* 拦截器注册 设置拦截接口
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(JdkApiInterceptor()).addPathPatterns("/**")//设置拦截所有的路径
//排除的路径:静态资源路径。防止被JdkApiInterceptor拦截
.excludePathPatterns("/loginCheck","/login","/error",
"/js/**", "/css/**", "/imag/**");
}
这里排除拦截的url中,除了静态资源路径和登入接口,为什么还有/error路径呢?具体原因请查看我这篇文章:WebMvcConfigurer的excludePathPatterns配置 "失效" 问题
5. 简单谈谈我对redis共享session方案的看法
基于redis的共享session方案能解决用户重复登入的问题,带来更好的用户体验,是分布式架构经典的解决方案,但同时也带了问题,redis服务器的高可用问题以及远处通信的性能问题。当然集群下session问题解决方案有很多种,这只是其中之一,日后我还会进一步以文章形式与大家探讨
如果大家觉得这篇文章对你学习架构有帮助的话,还请点赞,在看支持一下。github项目也记得点个星哦!
完整的demo项目,请关注公众号“前沿科技bot“并发送"redis共享session"获取。
- 本文标签: Java Spring Boot Java架构师方案
- 版权声明: 本站原创文章,于2020年10月14日由空白发布,转载请注明出处