∑(っ °Д °;)っ

Tomcat整合Nginx做服务器集群可以极大地增强系统的可用性,成倍提升系统的负载能力,并实现动静分离,提高响应速度。但与此同时,这也带来了另一个问题——服务器与客户端的之间会话(session)该如何保存?服务器集群之后怎样让客户端与服务器的每一次会话使用的都是同一个session?
庆幸的是我们有好几种办法解决这一问题。

解决分布式session的方案

方案一

采用粘性会话
即配置ngnix的负载均衡策略为ip_hash,使某个ip的请求固定访问某一台服务器;在 nginx.conf 配置文件中的upstream节点中增加ip_hash。

1
2
3
4
5
6
upstream myTomcat{
#这里指定多个源服务器,ip:端口,若为80端口则可写可不写
server 127.0.0.1:801 weight=1;
server 127.0.0.1:802 weight=1;
ip_hash;
}

评价:

优点:配置简单;
缺点:存在单点问题,如果一台服务器宕机那么该服务器对应的ip都不能正常使用;

方案二

session复制
采用tomcat官方介绍的集群方案,使用tomcat中内置的session复制功能,实现集群中各服务器之间的完全备份。
1、在tomcat服务器的Server.xml中,找到被注释节点,修改为如下:

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
33
34
35
36
37
38
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">

<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>

<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
bind="127.0.0.1"
address="228.0.0.4"<!--保留ip,用于广播-->
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4001"<!--如果是在同一台机器上的两个tomcat做负载,则此端口则不能重复-->
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
</Channel>

<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>

tomcat集群用的是组播技术,Membership是组播组,Receiver是接收方。228.0.0.4是组播地址,这是一个保留地址,关于组播地址,可以查看相关文档。port是端口(可以把Membership当作服务端,Receiver当作客户端,这样就比较好理解)。同一个集群下的机器要加入相同的组播组,所以同一个集群下的tomcat的Membership是一样的。Receiver是接收组播信息的配置,如果两个tomcat在同一个机器,Receiver的port要不相同。如果在不同的机器Receiver的address最好改成ip,如果使用auto,它的值很可能是127.0.0.1,这样会导致接收不到消息。另外,要将Membership和Receiver的端口加入到防火墙中,不然也会无法访问。

2、在项目的web.xml中需加入以支持集群。

评价:

优点:真正做到了集群,任何一台tomcat崩溃都不会产生影响;
缺点:消耗更多内存和带宽

利用Spring Session + Redis解决共享Session问题

1、在pom文件中引入spring session的相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.1.RELEASE</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
</dependencies>

2、在spring-session.xml的配置文件中配置相关的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

<!--springSession 配置-->
<bean id="sessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<!--修改session的有效时间-->
<property name="maxInactiveIntervalInSeconds" value="1800"></property>
</bean>
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="xxx.xx.xxx.xx"></property>
<property name="port" value="6379"></property>
</bean>
</beans>

3、在web.xml的配置文件中加载spring-session的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--加载Spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml,classpath:spring- session.xml</param-value>
</context-param>
<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>

注:这个filter的配置应该在web工程的其他filter配置之前。

4、 测试
登录后查看redis会发现多了一些数据,session信息在里面存放。
img
如果将该key-value删除则再次执行action则session丢失,需要重新登录。