第一种方式:nginx配置 ip_hash轮询机制实现,这个实现方式简单,但是有很大的局限性,同一个ip,会被分配始终访问同一个tomcat,因为ip不变,nginx计算出来的hash也就不变,除非这个tomcat挂了,才会分配到另外一个tomcat访问,万一出现这种情况,session就会丢失,分配到的新tomcat并没有它在前一个tomcat里的session,所有,这并不是session共享,只是尽量保证某一连接始终分发到固定的服务器而已。
假如在一个公司内的所有员工,访问的外网服务器,通过这种方式配置集群,将无法实现负载均衡,因为对外网nginx而言,这些人全部来自一个外网ip,所以,全部会被分配到一个tomcat上,而集群中其它tomcat将一直闲置。
另外,也有可能出现某些高频访问,始终压在一台服务器上的情况。
这样都是大大的弱化了nginx的负载均衡能力。
第二种方式,nginx不配置,tomcat服务配置session复制功能。
在Server.xml中,1)取消Cluster节点的注释. (2)保持每个Engine 节点jvmRoute的值是相同的.
tomcat的session复制是基于IP组播(multicast)来完成的。将集群的tomcat通过配置统一的组播IP和端口来确定一个集群组, 当一个node的session发生变更的时候, 它会向IP组播发送变更的数据, IP组播会将数据分发给所有组里的其他成员(node).
(网络通讯方式,1,tcp/ip,一对一通讯。2,udp广播方式通讯,发送全部网内机器某一端口,需要的机器监听端口接受数据处理数据,不需要的机器直接不理睬就行。3,组播方式,配置统一的组播IP和端口来确定一个集群组,集群内开黑通讯)
<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>
同时, 在web.xml里增加<distributable/>
描述,就是告诉tomcat,这个项目是可分布式的。。。
这种配置方式,相对简单,无需更改项目代码,也实现了真正的session共享,整个集群,只要有一个tomcat存活,那么其它tomcat都死掉了都重启了,session都不会丢失。
缺点也很明显,session在网络内复制的过程中,会有一定的延迟,当然,延迟长短,取决于网络环境、session个数(用户多不多)、session信息大小、集群的服务器个数。
所以,它适用于小集群、少用户的web服务。。。
以上配置会报错
ava.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: xxxxxxxx
原因是:tomcat停止时,保存session资源,然后在重启服务后,会尝试恢复session。
解决办法一:配置tomcat在关闭的时候就不去保存session资源。
配置于Context标签内
PersistentManager对Session进行持久化到文件系统或数据库中
<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="false"> <Store className="org.apache.catalina.session.FileStore"/> </Manager>
解决办法二:
将那些需要放在session的类进行序列化。
也就是让类实现接口java.io.Serializable即可。
第三种方式,nginx不配置,使用nosql或者sql数据库,实现session共享,特别是spring + redis方式。
1、添加依赖
特别注意,引入不同的版本时,可能会引起原有jar包版本不兼容
<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>
2、配置
spring-mvc.xml:
RedisHttpSessionConfiguration
<bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600"/>
</bean>
由于使用了这里的配置,由redis负责接管Session,原来web.xml里配置的Session超时时间就会失效了
还有一个redis连接池的配置。。。。
redis.clients.jedis.JedisPoolConfig
org.springframework.data.redis.connection.jedis.JedisConnectionFactory
3、web.xml添加拦截器
特别注意,这个filter要写在比较靠前的第一个的位置
<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>
这种配置方式,也较简单,需要更改一定的项目配置,实现了真正的session共享,可靠性很高。
缺点是项目配置有一定修改,每次访问多一个网络开销(去redis取session,开销也很小了)
所以,它适用于大集群、多用户的web服务。。。
Spring4.3,这里需要注意,所有Spring的xml的配置文件里标签要变为 -4.3.xsd
静态文件资源访问配置:要注意这里的静态文件不要放到WEB-INF里,要放到项目内,例如项目下的resources
<mvc:resources mapping="/**" location="/resources/" />
这样就可以放行静态文件的访问
还有一个比较大的坑,使用Spring Session实际是Spring加入了一个Filter,其本质是:对每一个请求的request都会被DelegatingFilterProxy进行了一次封装。那么,在Controller里面拿出的request实际上是封装后的request,因为Session要存在Session,所以调用request.getSession()的时候,实际上拿到是Spring封装后的session ,因此对于request实际上拿到是Spring封装后的request。那么可能会导致Request.getInputStream无法获取到流数据,对于使用raw格式,即非Key,Value参数的提交 会被Spring-Session获取,当contentType为application/x-www-form-urlencoded时,就会被解析成Paramater,从而导致request.getInputStream的数据再获取时为空了。
还有就是Controller里如果用到了request.setCharacterEncoding(“GBK”); 设置字符集,这样的请求也无法生效,因为request.setCharacterEncoding只有在request.getSession或者request.getParamater之前使用有效,而由于使用Spring Session加入了filter,所以Controller里的request.setCharacterEncoding这种方式转编码就失效了,解决办法,可以是new String(request.getParameter(key).getBytes(), “GBK”),获取到参数后转码。
另外,对于多项目间session共享,跨域名session共享,还得继续分析源码,坑较多。