分布式,集群环境下session如何处理?

如果不共享session的请求下,对手机进行短信验证.

对短信验证的步骤如下 :

  • 一. 获取验证码,把获取得到的验证码保存到该用户的session中
  • 二. 然后把该验证码以短信的方式发送到手机上.
  • 三. 用户在进行操作的时候,所要提交的表单中需要包含,要操作的信息和输入的验证码.
    后台会把用户输入的验证码与session中的验证码进行对比,如果验证通过,则可以进行后续的操作.
    在这个流程中,验证码存到session中,之后验证码的校验是这个流程的关键.
    如果session不共享,那么请求分配到的是不同机器上,那么就会有数据不同步的问题
    为了解决以上遇到的问题,需要处理的问题是,session如何在分布式环境上共享.

网上提供了三种常用的分布式环境,管理session的方案

session复制

将一台机器上的session数据以广播的形式发布到其余机器上

session绑定

指的是如果机器A有用户A的session信息,那么用户A在访问的时候,仍被分配到机器A上,(如上的例子,则需要单独去短信模块中进行验证)

session集中式管理

这种方式,是建立一个单独的服务器,共同处理集群中的session信息.

权衡一下这几种方案

  • 第一种方案中每台机器上都存有大量的session信息,对网络的依赖较大.
  • 第二种方案中,session信息只存在指定的某台机器上,如果某台机器down掉之后,会有用户信息的丢失.
  • 结合上面的几种优缺点.决定还是使用第三种方案,如果为了考虑可用性,可以建立多个服务器管理session.

最终采用的解决方案,用户获取验证码之后,把验证码保存到Redis里面.然后用户在操作之后,把输入的验证码与存到session中的验证码对比,如果验证码正确则可以继续操作.

当然系统能不使用session就尽量不使用session,但是对于之前开发的项目很多地方使用到了session,为了系统稳定,则需要维护之前的.

之前项目使用的session处理是 tomcat-redis-session-manager

https://github.com/jcoleman/tomcat-redis-session-manager 上面提供的

用这个可以解决tomcat session共享的问题,但是这样依赖于环境,需要配置环境.

之前项目就是用的是 tomcat-redis-session-manager 因为太依赖环境,并且只支持tomcat 7,后面我换成了spring-session进行管理session.

spring-session 不仅配置简单,而且有人持续维护更新,为什么不适用spring-session呢.

spring-session 配置

pom.xml 加入依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>

web.xml 加入配置

1
2
3
4
5
6
7
8
9
<!-- spring-session 做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>
</filter-mapping>

配置redis和spring-session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

<bean id="redisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="6"></property>
<property name="minEvictableIdleTimeMillis" value="300000"></property>
<property name="numTestsPerEvictionRun" value="3"></property>
<property name="timeBetweenEvictionRunsMillis" value="60000"></property>
</bean>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="poolConfig" ref="redisPoolConfig"></property>
<property name="hostName" value="${redis.host}"></property>
<property name="port" value="${redis.port}"></property>
<property name="password" value="${redis.password}"/>
<property name="database" value="0"></property>
<property name="timeout" value="15000"></property>
<property name="usePool" value="true"></property>
</bean>

<!-- 配置数据操作 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="redisConnectionFactory"></property>
</bean>

<!--
用 spring-session 做分布式session
将session放入redis
设置 3600 秒
-->
<bean id="redisHttpsessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpsessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="3600"/>
</bean>

这样就OK了,这样把这个项目打成war部署到多台服务器,只要redis是同一个源那么他们的session就是一样的. (当然域名要一样)

好了session共享搞定了,并且不依赖环境了,但是后面又遇到一个不同域名需要进行session共享的问题

因为我们后台系统是只能用内网才能访问,所以后台系统并没有申请域名,直接使用的是IP

比如 : 10.0.0.122,10.0.0.123,这两台机器是集群部署,但是按照上面的配置,session是无法共享的,因为域名不一致

后面翻阅了spring-session的文档,找到了答案

http://docs.spring.io/spring-session/docs/current-SNAPSHOT/reference/html5/guides/custom-cookie.html
http://docs.spring.io/spring-session/docs/current-SNAPSHOT/reference/html5/guides/java-custom-cookie.html

spring-session 不同域名共享配置

配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 设置Cookie domain 和 名称 -->
<bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
<!-- 10.0.0.122,10.0.0.123 -->
<property name="DomainNamePattern" value="(^10.0.0.12)[2-3]{1}"/>
<property name="cookieName" value="JsessionID"/>
</bean>

<!--
用 spring-session 做分布式session
将session放入redis
设置 3600 秒
-->
<bean id="redisHttpsessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpsessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="3600" />
</bean>

如果正则表达式觉得很难去写,可以写个单元测试慢慢匹配

源代码 org.springframework.session.web.http.DefaultCookieSerializer 中就是这样匹配的