* 目录 *
介绍
当我们使用了nginx做负载均衡,使用了多个web服务器时,我们的请求会根据配置的权重信息自动分配到配置的负载服务器上,这时,客户端发起的request并不能指定到同一台web服务器上,这时,shiro默认的ehcache来实现共享缓存比较麻烦,这里直接使用redis做共享缓存,把缓存统一保存在一个地方,这样即可解决web服务器缓存共享问题。
直接使用web服务器缓存时,如果session会话没有持久化到数据库或者文件中,当web服务器需要升级,这时服务端保存的用户会话session会随着服务器重启而不存在,这时,把session会话保存到redis中也是不错的选择。
文章将由spring cache配置和shiro使用spring cache代理两个方向进行介绍。
配置
笔者使用的redisson作为redis客户端,开发者根据实际情况选择,也可以使用jedis,本文只介绍使用redisson实现。
maven依赖
在pom.xml中加入以下依赖信息
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.2.2</version>
</dependency>
spring配置
这里使用注解进行配置,下面为配置代码
@Configuration
@ComponentScan
@EnableCaching
public class WebAppConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson(@Value("classpath:config/spring-redisson.yaml") Resource configFile) throws IOException {
Config config = Config.fromYAML(configFile.getInputStream());
return Redisson.create(config);
}
@Bean
public CacheManager cacheManager(RedissonClient redissonClient) throws IOException {
return new RedissonSpringCacheManager(redissonClient, "classpath:config/spring-cache-config.yaml");
}
}
# redisson configuration for redis servers
# see : https://github.com/mrniko/redisson/wiki/2.-Configuration
singleServerConfig:
idleConnectionTimeout: 10000
pingTimeout: 1000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
reconnectionTimeout: 3000
failedAttempts: 3
password: null
subscriptionsPerConnection: 5
clientName: null
address: "redis://127.0.0.1:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 10
connectionPoolSize: 64
database: 0
dnsMonitoring: false
dnsMonitoringInterval: 5000
threads: 0
nettyThreads: 0
#这里注意,笔者选择的这个序列化工具,官方提供了多种序列化方式,最好不要使用官方提供带Jackson标识的,会出现shiro session存入redis中,但从redis中取出数据时反序列化后session对象中数据丢失,原因未查证。
codec: !<org.redisson.codec.FstCodec> {}
useLinuxNativeEpoll: false
下图中红框标识的默认编码方式不建议使用,shiro session会话反序列化出现问题。
- spring-cache-config.yaml配置文件
一定要配置 ttl
和 maxIdleTime
,都是毫秒。
#权限缓存
authorizationCache:
ttl: 3600000
maxIdleTime: 1800000
#认证缓存
authenticationCache:
ttl: 3600000
maxIdleTime: 1800000
#session会话缓存
shiro-activeSessionCache:
ttl: 7200000
maxIdleTime: 3600000
以上配置成功完成后也就完成了spring cache的配置,可以启动项目测试是否配置成功,测试方法不再列出。
重新实现shiro CacheManager
下面为重新实现shiro CacheManager代码,让shiro缓存使用spring cache代理
/**
省略 package
*/
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import java.util.Collection;
import java.util.Set;
public class SpringCacheManagerWrapper implements CacheManager {
private org.springframework.cache.CacheManager cacheManager;
/**
* 设置spring cache manager
*
* @param cacheManager spring cache
*/
public void setCacheManager(org.springframework.cache.CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@SuppressWarnings("unchecked")
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
org.springframework.cache.Cache springCache = cacheManager.getCache(name);
return new SpringCacheWrapper(springCache);
}
static class SpringCacheWrapper implements Cache {
private org.springframework.cache.Cache springCache;
SpringCacheWrapper(org.springframework.cache.Cache springCache) {
this.springCache = springCache;
}
@Override
public Object get(Object key) throws CacheException {
Object value = springCache.get(key);
if (value instanceof SimpleValueWrapper) {
return ((SimpleValueWrapper) value).get();
}
return value;
}
@Override
public Object put(Object key, Object value) throws CacheException {
springCache.put(key, value);
return value;
}
@Override
public Object remove(Object key) throws CacheException {
springCache.evict(key);
return null;
}
@Override
public void clear() throws CacheException {
springCache.clear();
}
@Override
public int size() {
throw new UnsupportedOperationException("invoke spring cache abstract size method not supported");
}
@Override
public Set keys() {
throw new UnsupportedOperationException("invoke spring cache abstract keys method not supported");
}
@Override
public Collection values() {
throw new UnsupportedOperationException("invoke spring cache abstract values method not supported");
}
}
}
shiro缓存配置
这里暂时采用xml配置文件,下面为xml部分配置文件
<bean id="shiroCacheManager" class="com.fangshuo.basic.shiro.cache.spring.SpringCacheManagerWrapper">
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- Realm实现 -->
<bean id="userRealm" class="com.fangshuo.basic.shiro.realm.VUserRealm">
<!-- 凭证匹配器省略 credentialsMatcher -->
<property name="cacheManager" ref="shiroCacheManager"/>
<property name="cachingEnabled" value="false"/>
<property name="authenticationCachingEnabled" value="true"/>
<property name="authenticationCacheName" value="authenticationCache"/>
<property name="authorizationCachingEnabled" value="true"/>
<property name="authorizationCacheName" value="authorizationCache"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--省略其他配置信息,这里只介绍缓存 -->
<property name="cacheManager" ref="shiroCacheManager"/>
</bean>
上面为配置信息,其实只需要把以前使用shiro的缓存实现改为我们实现的即可。
如果使用了shiro提供的SimpleCookie
类,maxAge
参数设置为-1,redis会自动过期session,不在需要shiro管理session会话时间。
其他说明
如果出现错误,错误信息显示SimpleByteSource
类没有实现Serializable
,这个时候我们需要重写一些SimpleByteSource
类,例如:
import org.apache.shiro.util.SimpleByteSource;
import java.io.Serializable;
/**
* @author Created by yangyang on 2017/1/7.
* e-mail :yangyang_666@icloud.com ; tel :18580128658 ;QQ :296604153
*/
public class MySimpleByteSource extends SimpleByteSource implements Serializable {
public MySimpleByteSource(byte[] bytes) {
super(bytes);
}
public MySimpleByteSource(String string) {
super(string);
}
}
只需要继承SimpleByteSource
并实现Serializable
,构造方法根据实际情况添加。
备注:如果使用了shiro的认证匹配器,可能会用到此类。
测试
完成以上配置步骤,直接启动项目,进行登录,然后进入redis,成功标识请见下图
总结
使用独立缓存服务器,可以保证服务在重启的时候,用户不会受到服务器重启后session消失的影响,在存在大量session会话的情况下也不会导致web服务内存溢出等问题,也可以保证使用多个web服务器负载时,session共享。如果文章描述出现问题,请联系笔者修改。
原创文章,转载请注明出处