shiro使用ehcache实现集群同步和session复制
一般情况下WEB应用集群的会话复制都是通过容器实现的,shiro权限框架中可以定义SessionDAO直接将session持久化到缓存中,这样只需要对缓存做集群就可以代替session的复制。
实现思路
1、用SessionDAO将session保存到ehcache缓存
2、配置ehcache的jgroups集群复制,如果集群服务器比较多,可升级到缓存服务器
思路很简单,但实现过程遇到不少问题,现在把过程及配置文件记录下来,避免忘记。
第一步将SessionDAO保存到ehcache缓存,这个步骤很简单,也没出什么问题,按照网上的例子很容易,下面是配置文件主要片段:
<!-- 主要是为了注入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="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="ehcache"/> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </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