基于redis实现tomcat8及以上版本的tomcat集群的session持久化实现(tomcat-redis-session-manager二次开发)

前言:

本项目是基于jcoleman的tomcat-redis-session-manager二次开发版本

1、修改了小部分实现逻辑

2、去除对juni.jar包的依赖

3、去除无效代码和老版本tomcat操作API

4、支持tomcat 8 及以后的版本

感谢jcoleman的项目: https://github.com/jcoleman/tomcat-redis-session-manager由于该项目已经停止更新,最新版本只支持tomcat7,对于tomcat7以后的版本都不支持。

源码提供:

github项目地址:https://github.com/eguid/tomcat-redis-sessioon-manager

下载目录:

tomcat-redis-session-manager-by-eguid.jar下载地址:http://download.csdn.net/detail/eguid_1/9638171

tomcat-redis-session-manager-by-eguid.jar+jedis-2.9.0.jar+commons-pool2-2.2.jar集合包下载


注意:本项目依赖5个jar包,tomcat-api.jar;catalina.jar;servlet-api.jar;jedis-2.9.0.jar;commons-pool-2.4.2.jar,其中tomcat-api.jar、catalina.jar和servlet-api.jar这三个包是tomcat原生jar包,本项目打包时不需要打入这三个包


一、主要代码实现

1、session管理器实现

该类用于实现session的基本增删改查操作,加入了redis实现持久化

package cn.eguid.redisSessionManager;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Valve;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Protocol;

import java.io.IOException;
import java.util.Arrays;
import java.util.Set;

/**
 * 
 * @author eguid
 * 
 */
public class RedisSessionManager extends ManagerBase implements Lifecycle {

	protected byte[] NULL_SESSION = "null".getBytes();

	protected String host = "localhost";
	protected int port = 6379;
	protected int database = 0;
	protected String password = null;
	protected int timeout = Protocol.DEFAULT_TIMEOUT;
	protected JedisPool connectionPool = null;

	protected RedisSessionHandlerValve handlerValve;
	protected ThreadLocal<RedisSession> currentSession = new ThreadLocal<RedisSession>();
	protected ThreadLocal<String> currentSessionId = new ThreadLocal<String>();
	protected ThreadLocal<Boolean> currentSessionIsPersisted = new ThreadLocal<Boolean>();
	protected Serializer serializer;

	protected static String name = "RedisSessionManager";
	// 用于序列化的类
	protected String serializationStrategyClass = "cn.eguid.redisSessionManager.JavaSerializer";

	protected LifecycleSupport lifecycle = new LifecycleSupport(this);

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public int getDatabase() {
		return database;
	}

	public void setDatabase(int database) {
		this.database = database;
	}

	public int getTimeout() {
		return timeout;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setSerializationStrategyClass(String strategy) {
		this.serializationStrategyClass = strategy;
	}

	@Override
	public int getRejectedSessions() {
		// Essentially do nothing.
		return 0;
	}

	public void setRejectedSessions(int i) {
		// Do nothing.
	}

	protected Jedis getConnection() {
		System.out.println("获取jedis连接");
		Jedis jedis = connectionPool.getResource();
		if (getDatabase() != 0) {
			jedis.select(getDatabase());
		}

		return jedis;
	}

	protected void returnConnection(Jedis jedis) {
		System.out.println("注销jedis连接");
		jedis.close();
	}

	@Override
	public void load() throws ClassNotFoundException, IOException {

	}

	@Override
	public void unload() throws IOException {

	}

	/**
	 * Add a lifecycle event listener to this component.
	 *
	 * @param listener
	 *            The listener to add
	 */
	@Override
	public void addLifecycleListener(LifecycleListener listener) {
		lifecycle.addLifecycleListener(listener);
	}

	/**
	 * Get the lifecycle listeners associated with this lifecycle. If this
	 * Lifecycle has no listeners registered, a zero-length array is returned.
	 */
	@Override
	public LifecycleListener[] findLifecycleListeners() {
		return lifecycle.findLifecycleListeners();
	}

	/**
	 * Remove a lifecycle event listener from this component.
	 *
	 * @param listener
	 *            The listener to remove
	 */
	@Override
	public void removeLifecycleListener(LifecycleListener listener) {
		lifecycle.removeLifecycleListener(listener);
	}

	/**
	 * Start this component and implement the requirements of
	 * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
	 *
	 * @exception LifecycleException
	 *                if this component detects a fatal error that prevents this
	 *                component from being used
	 */
	@Override
	protected synchronized void startInternal() throws LifecycleException {
		boolean isSucc=false;
		try {
			System.out.println("准备开启redis-session-Manager处理器 ... ");
			super.startInternal();
			setState(LifecycleState.STARTING);
			Boolean attachedToValve = false;
			Valve[] values = getContainer().getPipeline().getValves();
			for (Valve valve : values) {
				if (valve instanceof RedisSessionHandlerValve) {
					System.out.println("初始化redis-session-Manager处理器 ... ");
					this.handlerValve = (RedisSessionHandlerValve) valve;
					this.handlerValve.setRedisSessionManager(this);
					attachedToValve = true;
					break;
				}
			}

			if (!attachedToValve) {
				String error = "重大错误:redis-session-Manager无法添加到会话处理器,session在请求后不能正常启动处理器!";
				throw new LifecycleException(error);
			}
			System.out.println("初始化序列化器和反序列化器 ... ");
			initializeSerializer();
			initializeDatabaseConnection();
			setDistributable(true);
			isSucc=true;
		} catch (ClassNotFoundException e) {
			throw new LifecycleException(e);
		} catch (InstantiationException e) {

			throw new LifecycleException(e);
		} catch (IllegalAccessException e) {

			throw new LifecycleException(e);
		} catch(Exception e){
			throw e;
		}finally{
			if(isSucc){
			System.out.println("redis-session-manager初始化成功");
			}else{
				System.out.println("redis-session-manager初始化失败");
			}
		}
	}

	/**
	 * Stop this component and implement the requirements of
	 * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
	 *
	 * @exception LifecycleException
	 *                if this component detects a fatal error that prevents this
	 *                component from being used
	 */
	@Override
	protected synchronized void stopInternal() throws LifecycleException {
		System.err.println("停止redis-session-manager处理器!");
		setState(LifecycleState.STOPPING);

		try {
			if (connectionPool != null) {
				connectionPool.destroy();
			}
		} catch (Exception e) {
			System.err.println("注销redis连接池失败!");
			connectionPool = null;
		}

		super.stopInternal();
	}

	@Override
	public Session createSession(String sessionId) {
		System.out.println("根据sessionId创建session:" + sessionId);
		// 初始化设置并创建一个新的session返回
		RedisSession session = (RedisSession) createEmptySession();
		session.setNew(true);
		session.setValid(true);
		session.setCreationTime(System.currentTimeMillis());
		session.setMaxInactiveInterval(getMaxInactiveInterval());
		String jvmRoute = getJvmRoute();
		Jedis jedis = null;
		try {
			jedis = getConnection();
			do {
				if (null == sessionId) {
					// 重新生成一个sessionId
					sessionId = generateSessionId();
				}

				if (jvmRoute != null) {
					sessionId += '.' + jvmRoute;
				}
			} while (jedis.setnx(sessionId.getBytes(), NULL_SESSION) == 1L);
			/*
			 * Even though the key is set in Redis, we are not going to flag the
			 * current thread as having had the session persisted since the
			 * session isn't actually serialized to Redis yet. This ensures that
			 * the save(session) at the end of the request will serialize the
			 * session into Redis with 'set' instead of 'setnx'.
			 */

			session.setId(sessionId);
			session.tellNew();

			currentSession.set(session);
			currentSessionId.set(sessionId);
			currentSessionIsPersisted.set(false);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return session;
	}

	@Override
	public Session createEmptySession() {
		System.out.println("添加空的session");
		return new RedisSession(this);
	}

	@Override
	public void add(Session session) {
		System.out.println("添加session到redis数据库");
		try {
			save(session);
		} catch (IOException e) {

			throw new RuntimeException("保存session失败", e);
		}
	}

	@Override
	public Session findSession(String id) throws IOException {
		System.out.println("查找sessionId:" + id);
		RedisSession session = null;
		if (id == null) {
			session = null;
			currentSessionIsPersisted.set(false);
		} else if (id.equals(currentSessionId.get())) {
			session = currentSession.get();
		} else {
			session = loadSessionFromRedis(id);
			if (session != null) {
				currentSessionIsPersisted.set(true);
			}
		}
		currentSession.set(session);
		currentSessionId.set(id);
		return session;
	}

	public void clear() {
	
		Jedis jedis = null;
		try {
			jedis = getConnection();
			jedis.flushDB();
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	public int getSize() throws IOException {

		Jedis jedis = null;
		try {
			jedis = getConnection();
			int size = jedis.dbSize().intValue();
			return size;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	public String[] keys() throws IOException {
		Jedis jedis = null;
		try {
			jedis = getConnection();
			Set<String> keySet = jedis.keys("*");
			return keySet.toArray(new String[keySet.size()]);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	public RedisSession loadSessionFromRedis(String id) throws IOException {
		RedisSession session;

		Jedis jedis = null;

		try {

			jedis = getConnection();
			byte[] data = jedis.get(id.getBytes());

			if (data == null) {

				session = null;
			} else if (Arrays.equals(NULL_SESSION, data)) {
				throw new IllegalStateException("Race condition encountered: attempted to load session[" + id
						+ "] which has been created but not yet serialized.");
			} else {

				session = (RedisSession) createEmptySession();
				serializer.deserializeInto(data, session);
				session.setId(id);
				session.setNew(false);
				session.setMaxInactiveInterval(getMaxInactiveInterval() * 1000);
				session.access();
				session.setValid(true);
				session.resetDirtyTracking();

			}

			return session;
		} catch (IOException e) {

			throw e;
		} catch (ClassNotFoundException ex) {

			throw new IOException("Unable to deserialize into session", ex);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	/**
	 * save session to redis
	 * 
	 * @param session
	 * @throws IOException
	 */
	public void save(Session session) throws IOException {
		System.out.println("保存session到redis");
		Jedis jedis = null;
		try {

			RedisSession redisSession = (RedisSession) session;

			Boolean sessionIsDirty = redisSession.isDirty();

			redisSession.resetDirtyTracking();
			byte[] binaryId = redisSession.getId().getBytes();

			jedis = getConnection();

			if (sessionIsDirty || currentSessionIsPersisted.get() != true) {
				jedis.set(binaryId, serializer.serializeFrom(redisSession));
			}

			currentSessionIsPersisted.set(true);

			jedis.expire(binaryId, getMaxInactiveInterval());
		} catch (IOException e) {
			throw e;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public void remove(Session session) {
		remove(session, false);
	}

	@Override
	public void remove(Session session, boolean update) {
		System.out.println("删除redis中的session,更新:"+update);
		Jedis jedis = null;
		try {
			jedis = getConnection();
			jedis.del(session.getId());
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	public void afterRequest() {
		System.out.println("删除缓存在内存中的session");
		RedisSession redisSession = currentSession.get();
		if (redisSession != null) {
			currentSession.remove();
			currentSessionId.remove();
			currentSessionIsPersisted.remove();
		}
	}

	@Override
	public void processExpires() {
		// We are going to use Redis's ability to expire keys for session
		// expiration.

		// Do nothing.
	}

	private void initializeDatabaseConnection() throws LifecycleException {
		try {
			System.out.println("初始化redis连接池 ... ");
			// 初始化redis连接池
			connectionPool = new JedisPool(new JedisPoolConfig(), getHost(), getPort(), getTimeout(), getPassword());
		} catch (Exception e) {
			e.printStackTrace();
			throw new LifecycleException("redis连接池初始化错误,redis不存在或配置错误!", e);
		}
	}

	private void initializeSerializer() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		System.out.println("准备初始化序列器 ... ");
		serializer = (Serializer) Class.forName(serializationStrategyClass).newInstance();
		ClassLoader classLoader = null;
		if (getContainer() != null) {
			classLoader = getContainer().getClass().getClassLoader();
		}
		System.out.println("初始化序列器完成!");
		serializer.setClassLoader(classLoader);
	}
}

2、redis的session实现

package cn.eguid.redisSessionManager;

import java.security.Principal;
import org.apache.catalina.Manager;
import org.apache.catalina.session.StandardSession;
import java.util.HashMap;


public class RedisSession extends StandardSession {
  protected static Boolean manualDirtyTrackingSupportEnabled = false;

  public static void setManualDirtyTrackingSupportEnabled(Boolean enabled) {
    manualDirtyTrackingSupportEnabled = enabled;
  }

  protected static String manualDirtyTrackingAttributeKey = "__changed__";

  public static void setManualDirtyTrackingAttributeKey(String key) {
    manualDirtyTrackingAttributeKey = key;
  }


  protected HashMap<String, Object> changedAttributes;
  protected Boolean dirty;

  public RedisSession(Manager manager) {
    super(manager);
    resetDirtyTracking();
  }

  public Boolean isDirty() {
    return dirty || !changedAttributes.isEmpty();
  }

  public HashMap<String, Object> getChangedAttributes() {
    return changedAttributes;
  }

  public void resetDirtyTracking() {
    changedAttributes = new HashMap<String, Object>();
    dirty = false;
  }

  @Override
  public void setAttribute(String key, Object value) {
    if (manualDirtyTrackingSupportEnabled && manualDirtyTrackingAttributeKey.equals(key)) {
      dirty = true;
      return;
    }

    Object oldValue = getAttribute(key);
    if ( value == null && oldValue != null
         || oldValue == null && value != null
         || !value.getClass().isInstance(oldValue)
         || !value.equals(oldValue) ) {
      changedAttributes.put(key, value);
    }

    super.setAttribute(key, value);
  }

  @Override
  public void removeAttribute(String name) {
    dirty = true;
    super.removeAttribute(name);
  }

  @Override
  public void setId(String id) {
    this.id = id;
  }

  @Override
  public void setPrincipal(Principal principal) {
    dirty = true;
    super.setPrincipal(principal);
  }

}

3、session处理器实现

该类可以用于在请求前后请求后做一些操作,不仅局限于session操作,可以做servlet中的所有操作

package cn.eguid.redisSessionManager;

import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;

import javax.servlet.ServletException;

import java.io.IOException;

public class RedisSessionHandlerValve extends ValveBase {
	// redis-session-manager管理器操作
	private RedisSessionManager manager;

	// 通过tomcat的context.xml可以注入该实例
	public void setRedisSessionManager(RedisSessionManager manager) {
		this.manager = manager;
	}

	// 产生一个请求后
	@Override
	public void invoke(Request request, Response response) throws IOException, ServletException {
		try {
			getNext().invoke(request, response);
		} finally {
			System.out.println("请求完毕后,redis-session-manager正在获取当前产生的session");
			Session session = request.getSessionInternal(false);
			
			storeOrRemoveSession(session);
			System.out.println("redis-session-manager操作结束,正在清理内存中的session!");
			// 删除内存中的session
			manager.afterRequest();
		}
	}
	
	private void storeOrRemoveSession(Session session) {
		try {
			if (session!=null && session.isValid() && session.getSession() != null) {
				manager.save(session);
			} else {
				manager.remove(session);
			}
		} catch (Exception e) {
			System.err.println("提示一下:session操作失败");
		}
	}
}


二、如何配置该项目到tomcat

1、拷贝tomcat-redis-session-manager-by-eguid.jar,jedis-2.9.0.jar,commons-pool2-2.2.jar到tomcat/lib目录下


2、修改Tomcat context.xml (or the context block of the server.xml if applicable.)

<Valve className="cn.eguid.redisSessionManager.RedisSessionHandlerValve"/>
<Manager className="cn.eguid.redisSessionManager.RedisSessionManager"
         host="192.168.30.21"
         port="6379"
         database="14"
         maxInactiveInterval="1800"/>


posted @ 2016-09-23 15:09  eguid  阅读(2063)  评论(0编辑  收藏  举报