基于redis解决Clustered Sessions问题(Spring Session + Redisson)

前言

集群方式部署服务器时,当高并发量的请求到达服务端时,服务端通过负载均衡算法将请求分配到集群中某个服务器,那么同一用户的多个请求可能被分发到不同的服务器,如果将session保存到某个服务器内存中,可能会出现session丢失的情况。

因此在集群时存在session共享一致性的问题。session复制或者使用hash算法反向代理存在不足,本篇利用spring-session框架把session储存到第三方容器(database,redis等)

本地容器选择redis,客户端使用redisson。

 

整合Redisson

  jar包

        <!--redis客户端 redisson-->
        <!-- JDK 1.8+ compatible -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.7.5</version>
        </dependency>
        <!--spring session-->
       <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>2.0.5.RELEASE</version>
        </dependency>

1.我们需要做的事很简单,只需要配置redis客户端并使用@EnableRedissonHttpSession注解。RedissonHttpSessionConfiguration类中实现了RedissonSessionRepository的注入,它用来操作session存储值。

RedissonSessionConfig配置类。

@EnableRedissonHttpSession
public class RedissonSessionConfig {
    @Bean(destroyMethod="shutdown")
    RedissonClient redisson() throws IOException {
        Config config = new Config();
        config.useClusterServers()
                .addNodeAddress("redis://192.168.56.129:7000", "redis://192.168.56.129:7001","redis://192.168.56.129:7002");
        return Redisson.create(config);
    }
    
}

------------------------------------------------------------------------------------------------------------------------------------------------------

观察EnableRedissonHttpSession注解,通过@Import向容器导入了有@Configuration注解的RedissonHttpSessionConfiguration配置类,其中包含与session存储有关的配置。(4.2版本后@Import支持向spring容器导入没有@Configuration的类)

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({RedissonHttpSessionConfiguration.class})
@Configuration
public @interface EnableRedissonHttpSession {
    int maxInactiveIntervalInSeconds() default 1800;

    String keyPrefix() default "";
}

 

接着观察RedissonHttpSessionConfiguration,实现了ImportWare接口,通过setImportMetadata() 方EnableRedissonHttpSession注解下的两个属性值(maxInactiveIntervalInSeconds,keyPrefix)注入RedissonHttpSessionConfiguration属性,向容器注入了RedissonSessionRepository 一个与session存储有关的容器类,它需要指定RedissonClient (redis客户端)。

@Configuration
public class RedissonHttpSessionConfiguration extends SpringHttpSessionConfiguration implements ImportAware {
    private Integer maxInactiveIntervalInSeconds;
    private String keyPrefix;

    public RedissonHttpSessionConfiguration() {
    }

    @Bean
    public RedissonSessionRepository sessionRepository(RedissonClient redissonClient, ApplicationEventPublisher eventPublisher) {
        RedissonSessionRepository repository = new RedissonSessionRepository(redissonClient, eventPublisher);
        if (StringUtils.hasText(this.keyPrefix)) {
            repository.setKeyPrefix(this.keyPrefix);
        }

        repository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds.intValue());
        return repository;
    }

    public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
        this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }

    public void setImportMetadata(AnnotationMetadata importMetadata) {
        Map<String, Object> map = importMetadata.getAnnotationAttributes(EnableRedissonHttpSession.class.getName());
        AnnotationAttributes attrs = AnnotationAttributes.fromMap(map);
        this.keyPrefix = attrs.getString("keyPrefix");
        this.maxInactiveIntervalInSeconds = (Integer)attrs.getNumber("maxInactiveIntervalInSeconds");
    }
}

观察父类SpringHttpSessionConfiguration,其中向spring容器中注入了SessionRepositoryFilter的Bean。spring正是通过这个filter用来过滤包装session

@Bean
    public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
        SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository);
        sessionRepositoryFilter.setServletContext(this.servletContext);
        sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
        return sessionRepositoryFilter;
    }

 

2.我们需要过滤request请求,对session进行包装

web.xml 中添加过滤器 (spring boot中省略)

<!--spring session-->
  <filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
  </filter-mapping>

------------------------------------------------------------------------------------------------------------------------------------------------------

DelegatingFilterProxy 类是filter的代理类,交给spring去管理filter。如果未指定init-param参数的话,DelegatingFilterProxy就会把filter-name作为要查找的Bean对象的name。这里去spring容器中寻找名字为springSessionRepositoryFilter的bean,得知为SessionRespositoryFilter过滤器。

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            HttpServletRequest httpRequest = (HttpServletRequest)request;
            HttpServletResponse httpResponse = (HttpServletResponse)response;
            boolean hasAlreadyFilteredAttribute = request.getAttribute(this.alreadyFilteredAttributeName) != null;
            if (hasAlreadyFilteredAttribute) {
                filterChain.doFilter(request, response);
            } else {
                request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);

                try {
                    this.doFilterInternal(httpRequest, httpResponse, filterChain);
                } finally {
                    request.removeAttribute(this.alreadyFilteredAttributeName);
                }
            }

        } else {
            throw new ServletException("OncePerRequestFilter just supports HTTP requests");
        }
    }

 观察SessionRespositoryFilter的doFilter()方法,里面调用了doFilterInternal()方法,在doFilterInternal中实现了对session的包装

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response, this.servletContext);
        SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response);

        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            wrappedRequest.commitSession();
        }

    }

 

测试

写一个controller类,获得session并设值

@Controller
public class HelloController {
		
	@RequestMapping("/helloWorld")
	public String helloWorld(HttpServletRequest request,Model model) throws Exception {	
		request.getSession().setAttribute("value","this is a test");
		return "hello";
	}
	
	
}

观察redis,以hash结构存储。hash的key为redisson_spring_session:sessionId 构成。

 

参考 spring-session 2.0.5官方

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当将会话管理从 Tomcat 切换到 Spring Session + Redis 时,有几个注意事项: 1. 配置 Redis:确保已正确配置和启动 Redis 服务器。你需要提供 Redis 的主机名、端口号和认证信息(如果有的话)。 2. 添加 Spring SessionRedis 的依赖:在项目的构建文件中,添加 Spring SessionRedis 的依赖。例如,在 Maven 中,你可以添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> ``` 3. 配置 Spring Session:在 Spring Boot 的配置文件中,添加以下配置来启用 Spring Session 和指定 Redis 的连接信息: ```properties # 启用 Spring Session spring.session.store-type=redis # Redis 连接信息 spring.redis.host=<redis_host> spring.redis.port=<redis_port> spring.redis.password=<redis_password> ``` 4. 在应用中使用会话:在应用中使用 Spring Session 提供的 API 来管理会话。你可以使用 `@EnableRedisHttpSession` 注解启用 Spring Session,并使用 `@Autowired` 注入 `RedisOperationsSessionRepository` 或 `SessionRepositoryFilter`。 5. 确保会话数据正确存储和检索:通过 Spring Session + Redis,会话数据将存储在 Redis 中。确保会话数据能够正确地存储和检索,以及与应用其他部分的交互正常。 6. 集群环境下的同步问题:如果你的应用在多个实例之间共享会话数据,并且使用 Redis 进行存储,需要注意在集群环境下的会话同步问题。你可以使用 Spring Session 提供的其他解决方案,如使用 Redis Pub/Sub 或 Redis Sentinel。 7. 监控和调优:在切换到 Spring Session + Redis 后,你可能需要重新评估和监控应用的性能和资源消耗情况,以确保它能够满足预期的性能需求。 以上是将会话管理从 Tomcat 切换到 Spring Session + Redis 时需要注意的一些事项。根据你的具体情况和需求,可能还需要调整其他相关配置和代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值