想要做Tomcat集群,其中需要解决的一个问题就是多个Tomcat中session的共享。共享的方法有很多种,比如使用Tomcat自带的session复制,使用数据库等。这里一些介绍我使用过的方法。
1.替换Tomcat的sessionManager
这种方法实现起来比较容易,但是需要改动每个Tomcat服务器的配置。对于Tomcat6和7,可以使用tomcat-redis-session-manager库来实现,对于Tomcat8以及8以上的版本,可以搜索对应的库。
tomcat-redis-session-manager(Github):https://github.com/jcoleman/tomcat-redis-session-manager
库的介绍里有详细的使用方法,这里再简单列一下:
- 修改 TOMCAT_BASE/conf 目录下的 context.xml 文件,示例配置如下:
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="localhost" <!-- 非必填: 默认值 "localhost" --> port="6379" <!-- 非必填: 默认值 "6379" --> database="0" <!-- 非必填: 默认值 "0" --> maxInactiveInterval="60" <!-- 非必填: 默认值 "60" (秒) --> sessionPersistPolicies="PERSIST_POLICY_1,PERSIST_POLICY_2,.." <!-- 非必填 --> sentinelMaster="SentinelMasterName" <!-- 非必填 --> sentinels="sentinel-host-1:port,sentinel-host-2:port,.." <!-- 非必填 --> />
注意:Value标签必须在Manager标签之前
- 将以下依赖包拷贝到 TOMCAT_BASE/lib 目录
- tomcat-redis-session-manager-VERSION.jar
- jedis-2.5.2.jar
- commons-pool2-2.2.jar
重启Tomcat后生效。
如果项目中使用了Spring和Apache Shiro,可以使用下面的方法。
2.继承Shiro的AbstractSessionDAO
序列化工具示例:
import java.io.Serializable;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.Session;
public class ShiroSerializationUtils {
public static String serialize(Session session) {
return Base64.encodeToString(SerializationUtils.serialize((Serializable) session));
}
public static Session deserialize(String sessionStr) {
return SerializationUtils.deserialize(Base64.decode(sessionStr));
}
}
Redis操作类示例:
import java.util.Set;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisManager {
private String url = null;
private int port = 6379;
private int timeout = 0;
private String password = null;
private JedisPool jedisPool = null;
public RedisManager(){
}
@PostConstruct
public void init() {
url = StringUtils.defaultIfBlank(url, "127.0.0.1");
if(StringUtils.isNotBlank(password)) {
jedisPool = new JedisPool(new JedisPoolConfig(), url, port, timeout, password);
} else if (timeout != 0) {
jedisPool = new JedisPool(new JedisPoolConfig(), url, port, timeout);
} else {
jedisPool = new JedisPool(new JedisPoolConfig(), url, port);
}
}
public Jedis getJedis() {
return jedisPool.getResource();
}
public String get(String key){
try(Jedis jedis = jedisPool.getResource();){
return jedis.get(key);
}
}
public void set(String key, String value){
try(Jedis jedis = jedisPool.getResource();){
jedis.set(key, value);
}
}
public void set(String key, String value, int timeToLiveSeconds){
try(Jedis jedis = jedisPool.getResource();){
jedis.setex(key, timeToLiveSeconds, value);
}
}
public void del(String key){
try(Jedis jedis = jedisPool.getResource();){
jedis.del(key);
}
}
public Set<String> keys(String pattern){
try(Jedis jedis = jedisPool.getResource();){
return jedis.keys(pattern);
}
}
public void setUrl(String url) {
this.url = url;
}
public void setPort(int port) {
this.port = port;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void setPassword(String password) {
this.password = password;
}
}
实现AbstractSessionDAO示例:
import java.io.Serializable;
import java.util.Collection;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RedisSessionDao extends AbstractSessionDAO {
private static final Logger log = LoggerFactory.getLogger(RedisSessionDao.class);
private int expirationTime = 1800; // 超时时间,秒
private RedisManager redisManager;
@Override
protected Serializable doCreate(Session session) {
log.debug("Create session: '{}'",session.getId());
Serializable sessionId = this.generateSessionId(session);
assignSessionId(session, sessionId);
String value = ShiroSerializationUtils.serialize(session);
redisManager.set(String.valueOf(sessionId), value, expirationTime);
return sessionId;
}
@Override
public void update(Session session) throws UnknownSessionException {
log.debug("update session: '{}'",session.getId());
if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
return;
}
redisManager.set(String.valueOf(session.getId()),ShiroSerializationUtils.serialize(session), expirationTime);
}
@Override
public void delete(Session session) {
log.debug("delete session: '{}'",session.getId());
redisManager.del(String.valueOf(session.getId()));
}
@Override
protected Session doReadSession(Serializable sessionId) {
log.debug("Read session: '{}'",sessionId);
String sessionStr = redisManager.get(String.valueOf(sessionId));
return sessionStr == null ? null : ShiroSerializationUtils.deserialize(sessionStr);
}
//使用 会话验证调度器 需实现此方法
@Override
public Collection<Session> getActiveSessions() {
return null;
}
public void setExpirationTime(int expirationTime) {
this.expirationTime = expirationTime;
}
public void setRedisManager(RedisManager redisManager) {
this.redisManager = redisManager;
}
}
Spring对应部分配置示例:
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="redisSessionDao"/>
</bean>
<bean id="redisManager" class="RedisManager" >
<property name="url" value="${redis.url}" />
<property name="password" value="${redis.password}"></property>
</bean>
<bean id="redisSessionDao" class="RedisSessionDao">
<property name="redisManager" ref="redisManager" />
<property name="expirationTime" value="442000" /><!-- 秒为单位 -->
</bean>