一般情况下WEB应用集群的会话复制都是通过容器实现的,shiro权限框架中可以定义SessionDAO直接将session持久化到缓存中,这样只需要对缓存做集群就可以代替session的复制。
实现思路
1、用SessionDAO将session保存到ehcache缓存
2、配置ehcache的jgroups集群复制,如果集群服务器比较多,可升级到缓存服务器
思路很简单,但
实现过程遇到不少问题,现在把过程及配置文件记录下来,避免忘记。
第一步将SessionDAO保存到ehcache缓存,这个步骤很简单,也没出什么问题,按照网上的
例子很容易,下面是配置文件主要片段:
class="java"><!-- 主要是为了注入SessionDAO -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="sessionDAO"/>
</bean>
<!-- SessionDAO用shiro提供的默认实现 -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<!-- <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/> -->
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
<!-- 为了实现自己的用户权限 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<!-- By default the servlet container sessions will be used. Uncomment this line
to use shiro's native sessions (see the JavaDoc for more): -->
<!-- <property name="sessionMode" value="native"/> -->
</bean>
<!-- 自定义的用户权限,这里也需要用缓存,避免从数据库加载权限 -->
<bean id="userRealm" class="cn.ys.security.UserRealm">
<property name="cacheManager" ref="shiroCacheManager"/>
<property name="userService" ref="securityService" />
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!-- 与spring ehcache集成 -->
<bean id="shiroCacheManager" class="cn.ys.security.ehcache.EhCacheManager">
<property name="cacheManager" ref="ehcacheFactory"></property>
</bean>
第二步遇到很多问题,网上资料比较少,对jgroups又不熟,下面是配置文件及我遇到的问题:
ehcache.xml
<!-- 缓存同步jgroup配置 -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"
properties="file=jgroups_tcp.xml" />
<diskStore path="java.io.tmpdir/ehcache"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<cache name="shiro-activeSessionCache"
maxElementsInMemory="1000"
overflowToDisk="true"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true,
replicateUpdates=true, replicateUpdatesViaCopy=true, replicateRemovals=true" />
</cache>
<cache name="cn.ys.security.UserRealm.authorizationCache"
maxElementsInMemory="1000"
eternal="true"
overflowToDisk="true">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true,
replicateUpdates=true, replicateUpdatesViaCopy=true, replicateRemovals=true" />
</cache>
<cache name="org.apache.shiro.realm.text.PropertiesRealm-0-accounts"
maxElementsInMemory="1000"
eternal="true"
overflowToDisk="true">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true,
replicateUpdates=true, replicateUpdatesViaCopy=true, replicateRemovals=true" />
</cache>
jgroups_tcp.xml
<config>
<TCP bind_port="7800" bind_addr="localhost"
loopback="false"
recv_buf_size="${tcp.recv_buf_size:20M}"
send_buf_size="${tcp.send_buf_size:1M}"
discard_incompatible_packets="true"
max_bundle_size="2M"
max_bundle_timeout="30"
enable_bundling="true"
use_send_queues="true"
sock_conn_timeout="300"
timer_type="new"
timer.min_threads="4"
timer.max_threads="10"
timer.keep_alive_time="3000"
timer.queue_max_size="500"
thread_pool.enabled="true"
thread_pool.min_threads="1"
thread_pool.max_threads="10"
thread_pool.keep_alive_time="5000"
thread_pool.queue_enabled="false"
thread_pool.queue_max_size="100"
thread_pool.rejection_policy="discard"
oob_thread_pool.enabled="true"
oob_thread_pool.min_threads="1"
oob_thread_pool.max_threads="8"
oob_thread_pool.keep_alive_time="5000"
oob_thread_pool.queue_enabled="false"
oob_thread_pool.queue_max_size="100"
oob_thread_pool.rejection_policy="discard"
/>
<TCPPING timeout="3000"
initial_hosts="localhost[7800]"
port_range="5"
num_initial_members="5"/>
<MERGE2 min_interval="10000"
max_interval="30000"/>
<FD_SOCK/>
<FD timeout="3000" max_tries="3" />
<VERIFY_SUSPECT timeout="1500" />
<BARRIER />
<pbcast.NAKACK use_mcast_xmit="false"
retransmit_timeout="300,600,1200,2400,4800"
discard_delivered_msgs="true"/>
<pbcast.STABLE stability_delay="1000" desired_avg_gossip="50000" max_bytes="400000"/>
<pbcast.GMS print_local_addr="true" join_timeout="5000" view_bundling="true"/>
</config>
在网上查找的例子jgroups
版本可能比较老,ehcache中的例子也一样,有些属性新的jgroups已经不认了,如start_port已经改成了bind_port,另外
新版本jgroups增加了bind_addr,这样就可以直接指定ip地址绑定了,不需要在jvm上增加ipv4参数。
配置jgroups的几个需要
理解的地方
1、jgroups的TCP的端口是自动分配自动
发现的,同一台服务器上的多个实例不需要都配置到initial_hosts中,一个IP在initial_hosts中只需要定义一次,端口与bind_port保持一致
2、port_range是用于端口自动分配和自动发现用的,例如bind_port值为7800,当同服务上的第一个实例启动是使用7800端口,第二个实现启动时使用的是7801,
最多只到7804
3、max_bundle_size不要太小,例子上该值只有64K,结果有时会现在无法复制的情况
4、当实例关闭再启动时,jgroups会认为是另一个host,原来的host如果不设置
超时会一直存在,所以要注意设置keep_alive_time