springboot集成spring-session及spring-redis实现session共享
说明
本文提到的集成是基于springboot,及非官方提供的redis配置方式的情况下集成(如,官方在yml文件中配置的redis相关的key类似spring.redis.host
,但是我们项目中提供的redis配置可能不是这种官方配置,如,我的项目用到的redis配置类似于redis.master.host
这样的.而这样配置,spring是解析不到的,就需要我们手动提供给spring-redis).此外,当我们需要用到多种模式的redis时,也适用.如果您只是集成一种模式的redis,且,redis的配置是官方的配置,那么,可以查找更加简洁的集成方式.
一.流程图
1.普通集成
2.多模式redis情况下的集成
二.普通spring-session集成redis
1.引入jar包
这里用的是spring对应的包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2.创建springsession,springredis配置类
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SpringRedisSessionConfig{
@Configuration
注解不用多解释.@EnableRedisHttpSession
注解是开启springsession,我们用的session都将是被spring所管理的session了,同时,这个注解也指明了,用什么容器存储session信息,也就是redis.里面的maxInactiveIntervalInSeconds
配置是设置session的最大存活时间,默认是1800秒.强调的是,这个存活时间,也是我们session在redis中存的值的过期时间.
3.配置类中的关键bean维护
虽然我们在SpringRedisSessionConfig
这个自定义的配置类上打了注解,但是我们还没有提供给spring-redis一个db的连接也好,通道也好,那么我们这里需要在该类里给定这个通道或者连接.
因为redis有三种模式-standalone
,sentinel
,cluster
,所以这里,我们可提供给spring-redis的通道应该有三种.集成的时候选择一种实现即可,不能同时提供三种,因为spring-redis只需要唯一的一个连接方式或者通道.
3.1.standalone模式的通道提供
@Bean("redisStandaloneConnectionFactory")
public RedisConnectionFactory redisStandaloneMasterConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);
RedisConnectionFactory lettuceConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
return lettuceConnectionFactory;
}
3.2.sentinel模式的通道提供
@Bean("redisSentinelctionFactory")
public RedisConnectionFactory redisSentinelctionFactory() {
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("10.70.33.238", 26379)
.sentinel("10.70.33.239", 26379)
.sentinel("10.70.33.246", 26379);
return new JedisConnectionFactory(redisSentinelConfiguration);
}
3.3.cluster模式的通道提供
@Bean("redisRedisClusterFactory")
public RedisConnectionFactory redisSentinelctionFactory() {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration()
RedisNode redisNode1 = new RedisNode("127.0.0.1", 6379);
RedisNode redisNode2 = new RedisNode("127.0.0.1", 6379);
redisClusterConfiguration.addClusterNode(redisNode1);
redisClusterConfiguration.addClusterNode(redisNode2);
return new JedisConnectionFactory(redisClusterConfiguration);
}
至此,三种模式redis的配置已经提供给spring-redis管理.
4.将自定义配置SpringRedisSessionConfig
注册给spring的AbstractHttpSessionApplicationInitializer
管理
/**
* 为了使每个serverlet容器都使用 spring 提供的 springSessionRepositoryFilter 过滤器创建此类.
* 上述过滤器,作用是用Spring会话支持的自定义实现替换HttpSession
* @author 愉快淡定
*/
public class ForSpringSessionRepositoryFilter extends AbstractHttpSessionApplicationInitializer {
public ForSpringSessionRepositoryFilter() {
super(SpringRedisSessionConfig.class);
}
}
实现方式比较简单,直接继承后,通过构注册.
5.启动测试
经过上述操作,普通的或者说单模式redis情况下的spring-session集成redis已经完成.
测试是否共享,我这里用到了nginx,用它进行负载均衡代理,请求我的两个端口下的项目,同时两个端口下的项目打印进来的请求的sessionId进行对比.
nginx配置:
upstream springboot{
server 127.0.0.1:8090 weight=1;
server 127.0.0.1:8091 weight=2;
}
server {
listen 8089;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://springboot;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
实现代理后,发起请求,对比sessionId,正常情况下,两个端口下打印出来的sessionId应该是一致的.
我们还需要到redis中确认,是不是按我们的预期,session的信心存进了redis中.存在spring中的key是spring:session:
接后续内容的模式,如图:
三.集成多种类型redis(standalone,sentinel,cluster)
说明里我已经提到了,因为我的redis在yml文件中配置是非官方那样的,且,我的这边需求,集成多种模式的redis来存储session信息,就需要利用以下方式来集成.
其实我们这次集成的核心是围绕@EnableSpringHttpSession
及重写适合自己的RedisSessionRepository
类来展开的.
翻开RedisSessionRepository
的源码,我们发现,spring-redis仅提供了单一的redis操作对象来进行数据的操作,进而限制了我们只能集成一种模式的redis.源码片段如下:
public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {
private static final String DEFAULT_KEY_NAMESPACE = "spring:session:";
private final RedisOperations<String, Object> sessionRedisOperations;
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(1800L);
public void deleteById(String sessionId) {
String key = this.getSessionKey(sessionId);
this.sessionRedisOperations.delete(key);
}
我们看到上面的源码中,RedisOperations<String, Object>
操作对象只有一个,对应的,下面的操作,也就由这个对象来进行,如我上面的展示的源码中的删除操作.
所以我们集成多种模式的redis工作,就从这里确定了,那就是提供多个操作对象,比如集合,下面对应的操作也遍历循环成单个操作对象后再操作
.
1.引入jar包
同普通集成一样.
2.以RedisSessionRepository
为模板重写自定义的RedisSessionRepostory类
这里的工作比较简单,就是将单个操作对象变集合,下面对应的操作,变成遍历集合后再操作,但是注意要跟RedisSessionRepository
一样去实现对应的接口:
public class DistributeRedisSessionRepository implements SessionRepository<DistributeRedisSessionRepository.RedisSession> {
private static final String DEFAULT_KEY_NAMESPACE = "spring:session:";
//private final RedisOperations<String, Object> sessionRedisOperations;
private final List<RedisOperations<String, Object>> sessionRedisOperationss;
@Override
public void deleteById(String sessionId) {
String key = getSessionKey(sessionId);
//this.sessionRedisOperations.delete(key);
//批量删除
for (RedisOperations<String, Object> redisOperations: this.sessionRedisOperationss){
redisOperations.delete(key);
}
}
这里,我们自己的DistributeRedisSessionRepository
类改造完成.
3.将源码中的RedisSessionMapper
复制一份
因为,自定的DistributeRedisSessionRepository
类中有用到RedisSessionMapper这个类,那我们可以从源码中将这个类拷贝出来,与自定义的那个类放同一级别即可.
4.利用自定义的DistributeRedisSessionRepository
注册给spring-redis
因为我们自定义了DistributeRedisSessionRepository
,spring并不知道这个类,那我们就要将其注册给spring.
先利用@EnableSpringHttpSession
注解开启springsession,如下:
/**
* 用于配置spring-session 及关联的 redis配置
* @author 愉快淡定
*/
@Slf4j
@Configuration
@EnableSpringHttpSession
public class DistributeSpringRedisSessionConfig{
private ArrayList<RedisOperations<String, Object>> redisOperations = new ArrayList<>();
可以看到我自定义了DistributeSpringRedisSessionConfig
类,属性redisOperations
(用于存放多种模式redis操作对象,对应自定义的DistributeRedisSessionRepository
中的修改),那么后续的配置在这里进行.
5.提供多种模式的redis操作对象
这里提供两种模式的,至于cluster各位看官应该能根据普通集成或者通过这两种示例自行集成进来.强调,这里的三种模式并非互斥,而是可以共存,甚至可以同时存在多种同一模式的配置(我项目中同时存在两个Standalone模式的操作对象).
5.1.集成Standalone
模式的操作对象
我的redis配置较多,各位看官可以根据自己需要进行配置,添加删除都行,或者简单默认也ok.
@Bean("masterRedisTemplate")
public RedisOperations<String, Object> masterSessionRedisOperations() {
RedisTemplate<String, Object> masterRedisTemplate = new RedisTemplate<>();
RedisStandaloneConfiguration masterRedisStandaloneConfiguration = new RedisStandaloneConfiguration(masterHostName, masterPort);
JedisClientConfiguration.DefaultJedisClientConfigurationBuilder jd = (JedisClientConfiguration.DefaultJedisClientConfigurationBuilder)JedisClientConfiguration.builder();
jd.readTimeout(Duration.ofMillis(this.masterTimeout));
jd.connectTimeout(Duration.ofMillis(this.masterTimeout));
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setTestOnBorrow(masterTestOnBorrow);
poolConfig.setMaxIdle(masterMaxIdle);
poolConfig.setMinIdle(masterMinIdle);
poolConfig.setMaxWaitMillis(masterMaxWait);
poolConfig.setMaxTotal(masterMaxActive);
jd.poolConfig(poolConfig);
jd.usePooling();
RedisConnectionFactory masterRedisConnectionFactory = new JedisConnectionFactory(masterRedisStandaloneConfiguration,jd.build());
masterRedisTemplate.setConnectionFactory(masterRedisConnectionFactory);
masterRedisTemplate.setKeySerializer(new StringRedisSerializer());
masterRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
masterRedisTemplate.setHashKeySerializer(new StringRedisSerializer());
masterRedisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
//设置模板其他配置
redisOperations.add(masterRedisTemplate);
return masterRedisTemplate;
}
@Bean
public DistributeRedisSessionRepository sessionRepository() {
Duration duration = Duration.ofSeconds(maxlive);
return new DistributeRedisSessionRepository(redisOperations,duration);
}
5.2.集成Sentinel
模式的操作对象
@Bean("sentinelRedisTemplate")
public RedisOperations<String, Object> sentinelSessionRedisOperations() {
RedisTemplate<String, Object> sentinelRedisTemplate = new RedisTemplate<>();
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("10.70.33.238", 26379)
.sentinel("10.70.33.239", 26379)
.sentinel("10.70.33.246", 26379);
RedisConnectionFactory sentinelRedisConnectionFactory = new JedisConnectionFactory(redisSentinelConfiguration);
sentinelRedisTemplate.setConnectionFactory(sentinelRedisConnectionFactory);
sentinelRedisTemplate.setKeySerializer(new StringRedisSerializer());
sentinelRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
sentinelRedisTemplate.setHashKeySerializer(new StringRedisSerializer());
sentinelRedisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
redisOperations.add(sentinelRedisTemplate);
return sentinelRedisTemplate;
}
@Bean
public DistributeRedisSessionRepository sessionRepository() {
Duration duration = Duration.ofSeconds(maxlive);
return new DistributeRedisSessionRepository(redisOperations,duration);
}
5.3 集成Cluster
模式的操作对象
略,各位看官如有需要,根据上面两种模式的集成方式自行集成即可.
6.启动测试
启动测试同上面的普通集成中的启动测试.
四.结束
以上是根据最近项目需求进行的springboot+spring-session+spring-redis的集成工作,我这里实际遇到的情况是项目中的redis配置非官方那种配置,同时,需要集成两个Standalone
的redis来存储session信息,哨兵模式及集群模式尚未实际用到,提供的解决方案供各位看官参考,能帮到各位最好,不能也希望各位勿怪.如果能提出意见给我,我这边也会十分感谢,谢谢各位.