zoukankan      html  css  js  c++  java
  • 基于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"/>


  • 相关阅读:
    STL中的map
    HDU 4027 Can you answer these queries?
    HDU 2199 Can you solve this equation?
    USACO section1.2 Name That Number 命名那个数字
    HDU 3790 最短路径问题 (双重权值)
    [笔记]CiscoPT配置RIP
    [笔记]Cisco PT VLANTrunk配置
    iptables感悟Ubuntu
    CentOS网络配置
    Discuz X2 数据库备份功能分析
  • 原文地址:https://www.cnblogs.com/eguid/p/6821590.html
Copyright © 2011-2022 走看看