Tomcat集群session共享方式

第一种方式: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共享,还得继续分析源码,坑较多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值