最近接手了一个项目,跑起来后,发现打开页面的速度特别缓慢,有时仅仅打开一个页面甚至需要几分钟,让人百思不得其解。
观察日志,发现在打开页面时,日志打印及其缓慢,有时几十秒才打印一行,项目仿佛在做慢动作一样。
于是检查了项目注册的几个handler和filter的代码,也没有发现什么复杂的逻辑会大量消耗性能。正在一筹莫展,突然想到可以用jconsole看看线程的方法调用链。于是打开jconsole,打开页面,找到对应的http-nio-8080-exec-*工作线程。 因为系统像慢动作一样,所以顺利的根据打印的工作线程名找到了正在工作的exec线程。
浏览了一下线程的方法栈,好像也没什么特殊的……等等!这一串的redis调用是什么情况? 直觉告诉我,它们很可疑!
继续往上找,发现对这些redisManager的调用来自一个RedisSessionDAO,从类的命名来看,这是shiro管理session的类。
打开一看,原来是个第三方的jar包,作者好像还是个中国人。它继承了shiro的AbstractSessionDAO,实现了对shiro sessionDAO的redis支持。里面主要有doReadSession、delete、saveSession、update几个方法。对应增删改查几个操作。
于是在方法里打了断点,发现会反复进入doReadSession这个方法,次数高达八十多次。
这个项目由于是异地,受网络影响,redis的速度很慢。如果每次读一个session要1秒的话,八十多次就接近一分半了!所以这便是页面打开速度缓慢的原因了。
既然找到了原因当然就要解决了,为了减少网络导致的延迟,最好的方法自然是加缓存了。由于redis在异地,我想到了ehcache这个内存缓存神器。浏览了一下shiro的代码,发现AbstractSession正好有个CachingSessionDAO的抽象子类,它实现了CaCheManagerAware接口。它的read和update方法长的是这样的:
也就是说读的时候会先读cache,读不到再去调实现类的doReadSession方法;更新的时候则会清掉cache。所以只要继承这个类,并重写doReadSession和doUpdate等几个方法就好了。
于是编写LocalRedisSessionDAO:
RedisSessionDAO就是原先配置的SessionDAO。这样整个流程只是加了一层缓存,完全没动原先的逻辑。
当然,ehcache缓存管理器是要配置的:
在spring-cache.xml的nativeEhCacheManger配置里把shared属性设置为true:
id="nativeEhCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
name="configLocation" value="classpath:ehcache.xml" />
name="shared" value="true">
在ehcache.xml新增一个cache:
name="shiroSessionCache" maxEntriesLocalHeap="5000" eternal="false" timeToIdleSeconds="60" timeToLiveSeconds="60" overflowToDisk="true" statistics="true">
在spring-shiro.xml加上:
id="shiroEhCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> name="cacheManager" ref="nativeEhCacheManager" />
id="localSessionDAO" class="com.lianjia.estuary.web.LocalRedisSessionDAO">
name="redisSessionDAO" ref="redisSessionDAO"> (给localSessionDAO注入原先redisSessionDAO的bean)
name="cacheManager" ref="shiroEhCacheManager">
name="activeSessionsCacheName" value="shiroSessionCache"> (在这里指定ehCache的缓存名)
再把sessionManager里配置的sessionDAO改为localSessionDAO。大工告成!
重新启动服务,页面速度快了很多。 “只”花了几秒钟。
注意:WebSessionManager里的setCacheManager方法会在调用时把sessionDAO里的cacheManager覆盖。 所以如果WebSessionManager有配置cacheManager,需同步改为和sessionDAO里的ehCacheManager。