最近接手了一个项目,跑起来后,发现打开页面的速度特别缓慢,有时仅仅打开一个页面甚至需要几分钟,让人百思不得其解。

观察日志,发现在打开页面时,日志打印及其缓慢,有时几十秒才打印一行,项目仿佛在做慢动作一样。

于是检查了项目注册的几个handler和filter的代码,也没有发现什么复杂的逻辑会大量消耗性能。正在一筹莫展,突然想到可以用jconsole看看线程的方法调用链。于是打开jconsole,打开页面,找到对应的http-nio-8080-exec-*工作线程。 因为系统像慢动作一样,所以顺利的根据打印的工作线程名找到了正在工作的exec线程。

session放到redis java redissessiondao_redis

浏览了一下线程的方法栈,好像也没什么特殊的……等等!这一串的redis调用是什么情况? 直觉告诉我,它们很可疑!

继续往上找,发现对这些redisManager的调用来自一个RedisSessionDAO,从类的命名来看,这是shiro管理session的类。

session放到redis java redissessiondao_redis_02

打开一看,原来是个第三方的jar包,作者好像还是个中国人。它继承了shiro的AbstractSessionDAO,实现了对shiro sessionDAO的redis支持。里面主要有doReadSession、delete、saveSession、update几个方法。对应增删改查几个操作。

于是在方法里打了断点,发现会反复进入doReadSession这个方法,次数高达八十多次。

这个项目由于是异地,受网络影响,redis的速度很慢。如果每次读一个session要1秒的话,八十多次就接近一分半了!所以这便是页面打开速度缓慢的原因了。

既然找到了原因当然就要解决了,为了减少网络导致的延迟,最好的方法自然是加缓存了。由于redis在异地,我想到了ehcache这个内存缓存神器。浏览了一下shiro的代码,发现AbstractSession正好有个CachingSessionDAO的抽象子类,它实现了CaCheManagerAware接口。它的read和update方法长的是这样的:

session放到redis java redissessiondao_session放到redis java_03

也就是说读的时候会先读cache,读不到再去调实现类的doReadSession方法;更新的时候则会清掉cache。所以只要继承这个类,并重写doReadSession和doUpdate等几个方法就好了。


于是编写LocalRedisSessionDAO:

session放到redis java redissessiondao_xml_04

RedisSessionDAO就是原先配置的SessionDAO。这样整个流程只是加了一层缓存,完全没动原先的逻辑。


当然,ehcache缓存管理器是要配置的:

在spring-cache.xml的nativeEhCacheManger配置里把shared属性设置为true:

<bean id="nativeEhCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:ehcache.xml" />
    <property name="shared" value="true"></property>
</bean>

在ehcache.xml新增一个cache:

<!-- shiro session的缓存 -->
<cache name="shiroSessionCache"
       maxEntriesLocalHeap="5000"
       eternal="false"
       timeToIdleSeconds="60"
       timeToLiveSeconds="60"
       overflowToDisk="true"
       statistics="true">
</cache>



在spring-shiro.xml加上:

<!-- 缓存管理器 使用Ehcache实现 -->
<bean id="shiroEhCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <property name="cacheManager" ref="nativeEhCacheManager" />
</bean>
<bean id="localSessionDAO" class="com.lianjia.estuary.web.LocalRedisSessionDAO">
    <property name="redisSessionDAO" ref="redisSessionDAO"></property>  (给localSessionDAO注入原先redisSessionDAO的bean)
    <property name="cacheManager" ref="shiroEhCacheManager"></property> 
    <property name="activeSessionsCacheName" value="shiroSessionCache"></property> (在这里指定ehCache的缓存名)
</bean>

再把sessionManager里配置的sessionDAO改为localSessionDAO。大工告成!

session放到redis java redissessiondao_缓存_05

重新启动服务,页面速度快了很多。 “只”花了几秒钟。

注意:WebSessionManager里的setCacheManager方法会在调用时把sessionDAO里的cacheManager覆盖。 所以如果WebSessionManager有配置cacheManager,需同步改为和sessionDAO里的ehCacheManager。