1.介绍
分布式会话问题即分布式session的一致性问题,出现此问题的根本原因是http协议是无状态的协议。客户端和服务端在某次会话中产生的数据不会被保留下来,所以第二次请求服务端无法知道是否访问过。而记录用户信息,保持http状态的技术,就需要使用cookie和session。而基于服务端一般采用session进行实现。
2.分布式Session实现
(1)完全不用Session
(2)nginx的IP_HASH策略
(3)Tomcat + Redis
(4)Spring Session + Redis
(1)完全不用Session
使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。相当于直接将用户信息在数据库或缓存中进行共享了,也就不存在分布式session一致性问题了。
(2)nginx的IP_HASH策略
同一客户端的IP请求都会被路由到同一个目标服务器,也叫会话粘滞。这种方案,配置简单,不入侵应用,不需要额外修改代码;但存在问题是服务器重启session会丢失。
(3)Tomcat + Redis
这里有两种常见的方案,一种是session复制,即多个tomcat之间通过修改配置文件,达到session之间的复制;另一种是session共享,即将session集中存储到redis中,第二种方案的实现如下:
首先在 Tomcat 的配置文件中配置RedisSessionManager:
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
host="{redis.host}"
port="{redis.port}"
database="{redis.dbnum}"
maxInactiveInterval="60"/>
然后指定 Redis 的 host 和 port :
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
sentinelMaster="mymaster"
sentinels="<sentinel1-ip>:26379,<sentinel2-ip>:26379,<sentinel3-ip>:26379"
maxInactiveInterval="60"/>
(4)Spring Session + Redis
这里分spring中使用spring session整合redis和springboot中使用spring session整合redis使用。
在spring中使用spring session过程:
首先在 pom.xml 中配置:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
然后在 Spring 配置文件中配置:
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600"/>
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="100" />
<property name="maxIdle" value="10" />
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
<property name="hostName" value="${redis_hostname}"/>
<property name="port" value="${redis_port}"/>
<property name="password" value="${redis_pwd}" />
<property name="timeout" value="3000"/>
<property name="usePool" value="true"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>
接着在 web.xml 中配置:
<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>
</filter-mapping>
最后进行测试:
@RestController
@RequestMapping("/test")
public class TestSessionController {
@RequestMapping("/addSession")
public String addSession(HttpServletRequest request, String username) {
request.getSession().setAttribute("name", "xiaoxiyuan");
return "ok";
}
@RequestMapping("/getSession")
public String getSession(HttpServletRequest request, Model model){
String name = request.getSession().getAttribute("name");
return name;
}
}
总结:先给 Spring Session 配置基于 Redis 来存储 Session 数据,然后配置了一个 Spring Session 的过滤器,这样的话,Session 相关操作都会交给 Spring Session 来管理了。接着在代码中,就用原生的 Session 操作,就是直接基于 Spring Session 从 Redis 中获取数据了。
在springboot中使用spring session过程:
首先在项目中引入相关jar依赖:
<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>
然后配置redis:
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
最后在启动类上添加@EnableRedisHttpSession注解:
@SpringBootApplication
@EnableCaching
@EnableRedisHttpSession
@ServletComponentScan
public class LoginApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(LoginApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(LoginApplication.class, args);
}
}
总结:请求通过tomcat到达servlet容器的时候,通过过滤器对请求做了一次封装,如果没有过滤器,servlet就会从tomcat中获取session,有了过滤器之后,取出来的session就是redis中session,有的话就从redis中获取,没有的话就创建并提交到redis中去。
3.如何选用分布式session方案
根据业务场景,完全不用Session或配置nginx的IP_HASH策略这两种方案是可以使用的;
session复制不推荐使用,因为session复制性能低,不易存储太多数据,内存消耗大;
推荐使用session共享即session的集中存储,因为这种方案能适应各种负载均衡,扩展能力强,也可以存储大量数据等;
推荐使用基于spring或springboot的spring session+redis存储方案,这种方案使用简单,易于与项目整合。