springboot集成spring-session及spring-redis实现session共享

2022-11-21 11:39:50

说明

本文提到的集成是基于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)publicclassSpringRedisSessionConfig{

@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 RedisConnectionFactoryredisStandaloneMasterConnectionFactory(){
        RedisStandaloneConfiguration redisStandaloneConfiguration=newRedisStandaloneConfiguration("127.0.0.1",6379);
        RedisConnectionFactory lettuceConnectionFactory=newJedisConnectionFactory(redisStandaloneConfiguration);return lettuceConnectionFactory;}

3.2.sentinel模式的通道提供

@Bean("redisSentinelctionFactory")public RedisConnectionFactoryredisSentinelctionFactory(){
        RedisSentinelConfiguration redisSentinelConfiguration=newRedisSentinelConfiguration().master("mymaster").sentinel("10.70.33.238",26379).sentinel("10.70.33.239",26379).sentinel("10.70.33.246",26379);returnnewJedisConnectionFactory(redisSentinelConfiguration);}

3.3.cluster模式的通道提供

@Bean("redisRedisClusterFactory")public RedisConnectionFactoryredisSentinelctionFactory(){
        RedisClusterConfiguration redisClusterConfiguration=newRedisClusterConfiguration()
        RedisNode redisNode1=newRedisNode("127.0.0.1",6379);
        RedisNode redisNode2=newRedisNode("127.0.0.1",6379);
        redisClusterConfiguration.addClusterNode(redisNode1);
        redisClusterConfiguration.addClusterNode(redisNode2);returnnewJedisConnectionFactory(redisClusterConfiguration);}

至此,三种模式redis的配置已经提供给spring-redis管理.

4.将自定义配置SpringRedisSessionConfig注册给spring的AbstractHttpSessionApplicationInitializer管理

/**
 * 为了使每个serverlet容器都使用 spring 提供的 springSessionRepositoryFilter 过滤器创建此类.
 * 上述过滤器,作用是用Spring会话支持的自定义实现替换HttpSession
 * @author 愉快淡定
 */publicclassForSpringSessionRepositoryFilterextendsAbstractHttpSessionApplicationInitializer{publicForSpringSessionRepositoryFilter(){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:接后续内容的模式,如图:

img

三.集成多种类型redis(standalone,sentinel,cluster)

说明里我已经提到了,因为我的redis在yml文件中配置是非官方那样的,且,我的这边需求,集成多种模式的redis来存储session信息,就需要利用以下方式来集成.

其实我们这次集成的核心是围绕@EnableSpringHttpSession及重写适合自己的RedisSessionRepository类来展开的.

翻开RedisSessionRepository的源码,我们发现,spring-redis仅提供了单一的redis操作对象来进行数据的操作,进而限制了我们只能集成一种模式的redis.源码片段如下:

publicclassRedisSessionRepositoryimplementsSessionRepository<RedisSessionRepository.RedisSession>{privatestaticfinal String DEFAULT_KEY_NAMESPACE="spring:session:";privatefinal RedisOperations<String, Object> sessionRedisOperations;private Duration defaultMaxInactiveInterval= Duration.ofSeconds(1800L);
publicvoiddeleteById(String sessionId){
        String key=this.getSessionKey(sessionId);this.sessionRedisOperations.delete(key);}

我们看到上面的源码中,RedisOperations<String, Object>操作对象只有一个,对应的,下面的操作,也就由这个对象来进行,如我上面的展示的源码中的删除操作.

所以我们集成多种模式的redis工作,就从这里确定了,那就是提供多个操作对象,比如集合,下面对应的操作也遍历循环成单个操作对象后再操作.

1.引入jar包

同普通集成一样.

2.以RedisSessionRepository为模板重写自定义的RedisSessionRepostory类

这里的工作比较简单,就是将单个操作对象变集合,下面对应的操作,变成遍历集合后再操作,但是注意要跟RedisSessionRepository一样去实现对应的接口:

publicclassDistributeRedisSessionRepositoryimplementsSessionRepository<DistributeRedisSessionRepository.RedisSession>{privatestaticfinal String DEFAULT_KEY_NAMESPACE="spring:session:";//private final RedisOperations<String, Object> sessionRedisOperations;privatefinal List<RedisOperations<String, Object>> sessionRedisOperationss;
@OverridepublicvoiddeleteById(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@EnableSpringHttpSessionpublicclassDistributeSpringRedisSessionConfig{private ArrayList<RedisOperations<String, Object>> redisOperations=newArrayList<>();

可以看到我自定义了DistributeSpringRedisSessionConfig类,属性redisOperations(用于存放多种模式redis操作对象,对应自定义的DistributeRedisSessionRepository中的修改),那么后续的配置在这里进行.

5.提供多种模式的redis操作对象

这里提供两种模式的,至于cluster各位看官应该能根据普通集成或者通过这两种示例自行集成进来.强调,这里的三种模式并非互斥,而是可以共存,甚至可以同时存在多种同一模式的配置(我项目中同时存在两个Standalone模式的操作对象).

5.1.集成Standalone模式的操作对象

我的redis配置较多,各位看官可以根据自己需要进行配置,添加删除都行,或者简单默认也ok.

@Bean("masterRedisTemplate")public RedisOperations<String, Object>masterSessionRedisOperations(){
    RedisTemplate<String, Object> masterRedisTemplate=newRedisTemplate<>();
    RedisStandaloneConfiguration masterRedisStandaloneConfiguration=newRedisStandaloneConfiguration(masterHostName, masterPort);
    JedisClientConfiguration.DefaultJedisClientConfigurationBuilder jd=(JedisClientConfiguration.DefaultJedisClientConfigurationBuilder)JedisClientConfiguration.builder();
    jd.readTimeout(Duration.ofMillis(this.masterTimeout));
    jd.connectTimeout(Duration.ofMillis(this.masterTimeout));
    JedisPoolConfig poolConfig=newJedisPoolConfig();
    poolConfig.setTestOnBorrow(masterTestOnBorrow);
    poolConfig.setMaxIdle(masterMaxIdle);
    poolConfig.setMinIdle(masterMinIdle);
    poolConfig.setMaxWaitMillis(masterMaxWait);
    poolConfig.setMaxTotal(masterMaxActive);
    jd.poolConfig(poolConfig);
    jd.usePooling();
    RedisConnectionFactory masterRedisConnectionFactory=newJedisConnectionFactory(masterRedisStandaloneConfiguration,jd.build());
    masterRedisTemplate.setConnectionFactory(masterRedisConnectionFactory);
    masterRedisTemplate.setKeySerializer(newStringRedisSerializer());
    masterRedisTemplate.setValueSerializer(newJdkSerializationRedisSerializer());
    masterRedisTemplate.setHashKeySerializer(newStringRedisSerializer());
    masterRedisTemplate.setHashValueSerializer(newJdkSerializationRedisSerializer());//设置模板其他配置
    redisOperations.add(masterRedisTemplate);return masterRedisTemplate;}
@Beanpublic DistributeRedisSessionRepositorysessionRepository(){
        Duration duration= Duration.ofSeconds(maxlive);returnnewDistributeRedisSessionRepository(redisOperations,duration);}

5.2.集成Sentinel模式的操作对象

@Bean("sentinelRedisTemplate")public RedisOperations<String, Object>sentinelSessionRedisOperations(){
    RedisTemplate<String, Object> sentinelRedisTemplate=newRedisTemplate<>();
    RedisSentinelConfiguration redisSentinelConfiguration=newRedisSentinelConfiguration().master("mymaster").sentinel("10.70.33.238",26379).sentinel("10.70.33.239",26379).sentinel("10.70.33.246",26379);
    RedisConnectionFactory sentinelRedisConnectionFactory=newJedisConnectionFactory(redisSentinelConfiguration);
    sentinelRedisTemplate.setConnectionFactory(sentinelRedisConnectionFactory);
    sentinelRedisTemplate.setKeySerializer(newStringRedisSerializer());
    sentinelRedisTemplate.setValueSerializer(newJdkSerializationRedisSerializer());
    sentinelRedisTemplate.setHashKeySerializer(newStringRedisSerializer());
    sentinelRedisTemplate.setHashValueSerializer(newJdkSerializationRedisSerializer());
    redisOperations.add(sentinelRedisTemplate);return sentinelRedisTemplate;}
@Beanpublic DistributeRedisSessionRepositorysessionRepository(){
        Duration duration= Duration.ofSeconds(maxlive);returnnewDistributeRedisSessionRepository(redisOperations,duration);}

5.3 集成Cluster模式的操作对象

略,各位看官如有需要,根据上面两种模式的集成方式自行集成即可.

6.启动测试

启动测试同上面的普通集成中的启动测试.

四.结束

以上是根据最近项目需求进行的springboot+spring-session+spring-redis的集成工作,我这里实际遇到的情况是项目中的redis配置非官方那种配置,同时,需要集成两个Standalone的redis来存储session信息,哨兵模式及集群模式尚未实际用到,提供的解决方案供各位看官参考,能帮到各位最好,不能也希望各位勿怪.如果能提出意见给我,我这边也会十分感谢,谢谢各位.

  • 作者:愉快淡定
  • 原文链接:https://blog.csdn.net/qq_39727959/article/details/106284653
    更新时间:2022-11-21 11:39:50