用redis管理session数据 ,主要用到 fst序列化序列到redis,目前只支持tomcat8
1.RedisSession
package com.lb.tomcat.redissessions; import java.security.Principal; import org.apache.catalina.Manager; import org.apache.catalina.session.StandardSession; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.io.IOException; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; 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; /**是否更新过 */ private boolean isUpdate = false; @Override public Enumeration<String> getAttributeNames() { // TODO Auto-generated method stub Set<String> names = new HashSet<>(); names.addAll(changedAttributes.keySet()); return Collections.enumeration(names); } public void setChangedAttributes(HashMap<String, Object> changedAttributes) { this.changedAttributes = changedAttributes; } public RedisSession(Manager manager) { super(manager); resetDirtyTracking(); } public HashMap<String, Object> getChangedAttributes() { return changedAttributes; } public void resetDirtyTracking() { changedAttributes = new HashMap<>(2); isUpdate = true; } @Override public Object getAttribute(String name) { // TODO Auto-generated method stub return changedAttributes.get(name); } @Override public void setAttribute(String key, Object value) { if (manualDirtyTrackingAttributeKey.equals(key)) { isUpdate = true; return; } Object oldValue = getAttribute(key); super.setAttribute(key, value); if ((value != null || oldValue != null) && (value == null && oldValue != null || oldValue == null && value != null || !value.getClass().isInstance(oldValue) || !value .equals(oldValue))) { isUpdate = true; changedAttributes.put(key, value); } } @Override public void removeAttribute(String name) { super.removeAttribute(name); isUpdate = true; changedAttributes.remove(name); } public boolean isUpdate() { return isUpdate; } public void setUpdate(boolean isUpdate) { this.isUpdate = isUpdate; } @Override public void setId(String id) { // Specifically do not call super(): it's implementation does unexpected // things // like calling manager.remove(session.id) and manager.add(session). this.id = id; } @Override public void setPrincipal(Principal principal) { isUpdate = true; super.setPrincipal(principal); } @Override public void writeObjectData(java.io.ObjectOutputStream out) throws IOException { super.writeObjectData(out); out.writeLong(this.getCreationTime()); } @Override public void readObjectData(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { super.readObjectData(in); this.setCreationTime(in.readLong()); } }
2.RedisSessionHandlerValve
package com.lb.tomcat.redissessions; 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; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; public class RedisSessionHandlerValve extends ValveBase { private final Log log = LogFactory.getLog(RedisSessionManager.class); private RedisSessionManager manager; 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 { manager.afterRequest(); } } }
3.RedisSessionManager
package com.lb.tomcat.redissessions; 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.Context; import org.apache.catalina.LifecycleState; import org.apache.catalina.Loader; import org.apache.catalina.Valve; import org.apache.catalina.Session; import org.apache.catalina.session.ManagerBase; import redis.clients.util.Pool; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisSentinelPool; 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.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Set; import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; public class RedisSessionManager extends ManagerBase implements Lifecycle { enum SessionPersistPolicy { DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST; static SessionPersistPolicy fromName(String name) { for (SessionPersistPolicy policy : SessionPersistPolicy.values()) { if (policy.name().equalsIgnoreCase(name)) { return policy; } } throw new IllegalArgumentException( "Invalid session persist policy [" + name + "]. Must be one of " + Arrays.asList(SessionPersistPolicy.values()) + "."); } } protected byte[] NULL_SESSION = "null".getBytes(); private final Log log = LogFactory.getLog(RedisSessionManager.class); protected String host = "localhost"; protected int port = 6379; protected int database = 0; protected String password = null; protected int timeout = Protocol.DEFAULT_TIMEOUT; protected String sentinelMaster = null; Set<String> sentinelSet = null; protected Pool<Jedis> connectionPool; protected JedisPoolConfig connectionPoolConfig = new JedisPoolConfig(); protected RedisSessionHandlerValve handlerValve; protected ThreadLocal<RedisSession> currentSession = new ThreadLocal<>(); protected ThreadLocal<String> currentSessionId = new ThreadLocal<>(); /**是否持久化*/ protected ThreadLocal<Boolean> currentSessionIsPersisted = new ThreadLocal<>(); protected static String name = "RedisSessionManager"; protected Serializer serializer; protected SerializeUtils serializeUtils; protected String serializationStrategyClass = "com.lb.tomcat.redissessions.JavaSerializer"; protected String serializationStrategyClass2 = "com.lb.tomcat.redissessions.SerializeUtils"; protected String webName ; // protected String serializationStrategyClass = // "com.radiadesign.catalina.session.SerializeUtils"; /** * The lifecycle event support for this component. */ 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; } public String getSentinels() { StringBuilder sentinels = new StringBuilder(); for (Iterator<String> iter = this.sentinelSet.iterator(); iter .hasNext();) { sentinels.append(iter.next()); if (iter.hasNext()) { sentinels.append(","); } } return sentinels.toString(); } public void setSentinels(String sentinels) { if (null == sentinels) { sentinels = ""; } String[] sentinelArray = sentinels.split(","); this.sentinelSet = new HashSet<String>(Arrays.asList(sentinelArray)); } public Set<String> getSentinelSet() { return this.sentinelSet; } public String getSentinelMaster() { return this.sentinelMaster; } public void setSentinelMaster(String master) { this.sentinelMaster = sentinelMaster; } @Override public int getRejectedSessions() { // Essentially do nothing. return 0; } public void setRejectedSessions(int i) { // Do nothing. } protected Jedis acquireConnection() { Jedis jedis = connectionPool.getResource(); if (getDatabase() != 0) { jedis.select(getDatabase()); } return jedis; } protected void returnConnection(Jedis jedis, Boolean error) { if (error) { connectionPool.returnBrokenResource(jedis); } else { connectionPool.returnResource(jedis); } } protected void returnConnection(Jedis jedis) { returnConnection(jedis, false); } @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 { super.startInternal(); setState(LifecycleState.STARTING); Boolean attachedToValve = false; for (Valve valve : getContainer().getPipeline().getValves()) { if (valve instanceof RedisSessionHandlerValve) { this.handlerValve = (RedisSessionHandlerValve) valve; this.handlerValve.setRedisSessionManager(this); log.info("Attached to RedisSessionHandlerValve"); attachedToValve = true; break; } } if (!attachedToValve) { String error = "Unable to attach to session handling valve; sessions cannot be saved after the request without the valve starting properly."; log.fatal(error); throw new LifecycleException(error); } try { initializeSerializer(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { log.fatal("Unable to load serializer", e); throw new LifecycleException(e); } log.info("Will expire sessions after " + getMaxInactiveInterval() + " seconds"); initializeDatabaseConnection(); setDistributable(true); } /** * 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 { if (log.isDebugEnabled()) { log.debug("Stopping"); } setState(LifecycleState.STOPPING); try { connectionPool.destroy(); } catch (Exception e) { // Do nothing. } // Require a new random number generator if we are restarted super.stopInternal(); } @Override public Session createSession(String requestedSessionId) { System.out.println("createSession"); RedisSession session = null; String sessionId = null; String jvmRoute = getJvmRoute(); Boolean error = true; Jedis jedis = null; try { jedis = acquireConnection(); // Ensure generation of a unique session identifier. if (null != requestedSessionId) { sessionId = requestedSessionId; if (jvmRoute != null) { sessionId += '.' + jvmRoute; } if (jedis.setnx(sessionId.getBytes(), NULL_SESSION) == 0L) { sessionId = null; } } else { do { sessionId = generateSessionId(); if (jvmRoute != null) { sessionId += '.' + jvmRoute; } } while (jedis.setnx(sessionId.getBytes(), NULL_SESSION) == 0L); // 1 // = // key // set; // 0 // = // key // already // existed } /* * 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'. */ error = false; if (null != sessionId) { session = (RedisSession) createEmptySession(); session.setNew(true); session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(getMaxInactiveInterval()); session.setId(sessionId); session.tellNew(); } currentSession.set(session); currentSessionId.set(sessionId); currentSessionIsPersisted.set(false); if (null != session) { try { error = saveInternal(jedis, session); } catch (IOException ex) { log.error("Error saving newly created session: " + ex.getMessage()); currentSession.set(null); currentSessionId.set(null); session = null; } } } finally { if (jedis != null) { returnConnection(jedis, error); } } return session; } @Override public Session createEmptySession() { return new RedisSession(this); } @Override public void add(Session session) { try { save(session); } catch (IOException ex) { log.warn("Unable to add to session manager store: " + ex.getMessage()); throw new RuntimeException( "Unable to add to session manager store.", ex); } } @Override public Session findSession(String id) throws IOException { RedisSession session; 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); } else { currentSessionIsPersisted.set(false); id = null; } } currentSession.set(session); currentSessionId.set(id); return session; } public void clear() { Jedis jedis = null; Boolean error = true; try { jedis = acquireConnection(); jedis.flushDB(); error = false; } finally { if (jedis != null) { returnConnection(jedis, error); } } } public int getSize() throws IOException { Jedis jedis = null; Boolean error = true; try { jedis = acquireConnection(); int size = jedis.dbSize().intValue(); error = false; return size; } finally { if (jedis != null) { returnConnection(jedis, error); } } } public String[] keys() throws IOException { Jedis jedis = null; Boolean error = true; try { jedis = acquireConnection(); Set<String> keySet = jedis.keys("*"); error = false; return keySet.toArray(new String[keySet.size()]); } finally { if (jedis != null) { returnConnection(jedis, error); } } } public RedisSession loadSessionFromRedis(String id) { RedisSession session; Jedis jedis = null; Boolean error = true; try { log.trace("Attempting to load session " + id + " from Redis"); jedis = acquireConnection(); byte[] data = jedis.get(id.getBytes()); error = false; if (data == null) { log.trace("Session " + id + " not found in Redis"); session = null; } else { log.trace("Deserializing session " + id + " from Redis"); session = (RedisSession) createEmptySession(); HashMap<String, Object> map = (HashMap<String, Object> )serializeUtils.fdeserialize(data); session.setChangedAttributes(map); //TODO //serializer.deserializeInto(data, session); session.setId(id); session.setNew(false); session.setMaxInactiveInterval(getMaxInactiveInterval() * 1000); session.access(); session.setValid(true); // session.resetDirtyTracking(); if (log.isTraceEnabled()) { log.trace("Session Contents [" + id + "]:"); Enumeration en = session.getAttributeNames(); while (en.hasMoreElements()) { log.trace(" " + en.nextElement()); } } } return session; } catch (Exception e) { e.printStackTrace(); log.fatal(e.getMessage()); } finally { if (jedis != null) { returnConnection(jedis, error); } } return null; } public void save(Session session) throws IOException { Jedis jedis = null; Boolean error = true; try { jedis = acquireConnection(); error = saveInternal(jedis, session); } catch (IOException e) { e.printStackTrace(); throw e; } finally { if (jedis != null) { returnConnection(jedis, error); } } } protected boolean saveInternal(Jedis jedis, Session session) throws IOException { Boolean error = true; try { log.trace("Saving session " + session + " into Redis"); RedisSession redisSession = (RedisSession) session; if (log.isTraceEnabled()) { log.trace("Session Contents [" + redisSession.getId() + "]:"); Enumeration en = redisSession.getAttributeNames(); while (en.hasMoreElements()) { log.trace(" " + en.nextElement()); } } byte[] binaryId = redisSession.getId().getBytes(); Boolean isCurrentSessionPersisted = this.currentSessionIsPersisted .get(); //更新状态 和未持久化 if (redisSession.isUpdate() || (isCurrentSessionPersisted == null || !isCurrentSessionPersisted)) { redisSession.setUpdate(false); //TODO //jedis.set(binaryId, serializer.serializeFrom(redisSession)); jedis.set(binaryId,serializeUtils.fserialize(redisSession.getChangedAttributes())); } currentSessionIsPersisted.set(true); log.trace("Setting expire timeout on session [" + redisSession.getId() + "] to " + getMaxInactiveInterval()); jedis.expire(binaryId, redisSession.getMaxInactiveInterval() * 60); error = false; return error; } catch (Exception e) { log.error(e.getMessage()); throw e; } finally { return error; } } @Override public void remove(Session session) { remove(session, false); } @Override public void remove(Session session, boolean update) { Jedis jedis = null; Boolean error = true; log.trace("Removing session ID : " + session.getId()); try { jedis = acquireConnection(); jedis.del(session.getId()); error = false; } finally { if (jedis != null) { returnConnection(jedis, error); } } } /** * 整个会话结束后 */ public void afterRequest() { RedisSession redisSession = currentSession.get(); if (redisSession != null) { try { // if (redisSession.isValid()) { log.trace("Request with session completed, saving session " + redisSession.getId()); save(redisSession); } catch (Exception e) { log.error("Error storing/removing session", e); } finally { currentSession.remove(); currentSessionId.remove(); currentSessionIsPersisted.remove(); log.trace("Session removed from ThreadLocal :" + redisSession.getIdInternal()); } } } @Override public void processExpires() { // We are going to use Redis's ability to expire keys for session // expiration. // Do nothing. } /** * 初始化redis * @throws LifecycleException */ private void initializeDatabaseConnection() throws LifecycleException { try { if (getSentinelMaster() != null) { Set<String> sentinelSet = getSentinelSet(); if (sentinelSet != null && sentinelSet.size() > 0) { connectionPool = new JedisSentinelPool(getSentinelMaster(), sentinelSet, this.connectionPoolConfig, getTimeout(), getPassword()); } else { throw new LifecycleException( "Error configuring Redis Sentinel connection pool: expected both `sentinelMaster` and `sentiels` to be configured"); } } else { connectionPool = new JedisPool(this.connectionPoolConfig, getHost(), getPort(), getTimeout(), getPassword()); } } catch (Exception e) { e.printStackTrace(); throw new LifecycleException("Error connecting to Redis", e); } } /**初始化序列化*/ private void initializeSerializer() throws ClassNotFoundException, IllegalAccessException, InstantiationException { log.info("Attempting to use serializer :" + serializationStrategyClass); serializer = (Serializer) Class.forName(serializationStrategyClass) .newInstance(); serializeUtils = (SerializeUtils) Class.forName(serializationStrategyClass2) .newInstance(); Loader loader = null; Context context = this.getContext(); if (context != null) { loader = context.getLoader(); } ClassLoader classLoader = null; if (loader != null) { classLoader = loader.getClassLoader(); } if (webName == null) { throw new RuntimeException("webName is null"); } String d = classLoader.toString(); if (d.indexOf(webName) != -1) { System.out.println(classLoader.toString()); serializer.setClassLoader(classLoader); serializeUtils.conf.setClassLoader(classLoader); } } // Connection Pool Config Accessors // - from org.apache.commons.pool2.impl.GenericObjectPoolConfig public int getConnectionPoolMaxTotal() { return this.connectionPoolConfig.getMaxTotal(); } public void setConnectionPoolMaxTotal(int connectionPoolMaxTotal) { this.connectionPoolConfig.setMaxTotal(connectionPoolMaxTotal); } public int getConnectionPoolMaxIdle() { return this.connectionPoolConfig.getMaxIdle(); } public void setConnectionPoolMaxIdle(int connectionPoolMaxIdle) { this.connectionPoolConfig.setMaxIdle(connectionPoolMaxIdle); } public int getConnectionPoolMinIdle() { return this.connectionPoolConfig.getMinIdle(); } public void setConnectionPoolMinIdle(int connectionPoolMinIdle) { this.connectionPoolConfig.setMinIdle(connectionPoolMinIdle); } // - from org.apache.commons.pool2.impl.BaseObjectPoolConfig public String getWebName() { return webName; } public void setWebName(String webName) { this.webName = webName; } public boolean getLifo() { return this.connectionPoolConfig.getLifo(); } public void setLifo(boolean lifo) { this.connectionPoolConfig.setLifo(lifo); } public long getMaxWaitMillis() { return this.connectionPoolConfig.getMaxWaitMillis(); } public void setMaxWaitMillis(long maxWaitMillis) { this.connectionPoolConfig.setMaxWaitMillis(maxWaitMillis); } public long getMinEvictableIdleTimeMillis() { return this.connectionPoolConfig.getMinEvictableIdleTimeMillis(); } public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { this.connectionPoolConfig .setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); } public long getSoftMinEvictableIdleTimeMillis() { return this.connectionPoolConfig.getSoftMinEvictableIdleTimeMillis(); } public void setSoftMinEvictableIdleTimeMillis( long softMinEvictableIdleTimeMillis) { this.connectionPoolConfig .setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis); } public int getNumTestsPerEvictionRun() { return this.connectionPoolConfig.getNumTestsPerEvictionRun(); } public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { this.connectionPoolConfig .setNumTestsPerEvictionRun(numTestsPerEvictionRun); } public boolean getTestOnBorrow() { return this.connectionPoolConfig.getTestOnBorrow(); } public void setTestOnBorrow(boolean testOnBorrow) { this.connectionPoolConfig.setTestOnBorrow(testOnBorrow); } public boolean getTestOnReturn() { return this.connectionPoolConfig.getTestOnReturn(); } public void setTestOnReturn(boolean testOnReturn) { this.connectionPoolConfig.setTestOnReturn(testOnReturn); } public boolean getTestWhileIdle() { return this.connectionPoolConfig.getTestWhileIdle(); } public void setTestWhileIdle(boolean testWhileIdle) { this.connectionPoolConfig.setTestWhileIdle(testWhileIdle); } public long getTimeBetweenEvictionRunsMillis() { return this.connectionPoolConfig.getTimeBetweenEvictionRunsMillis(); } public void setTimeBetweenEvictionRunsMillis( long timeBetweenEvictionRunsMillis) { this.connectionPoolConfig .setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); } public String getEvictionPolicyClassName() { return this.connectionPoolConfig.getEvictionPolicyClassName(); } public void setEvictionPolicyClassName(String evictionPolicyClassName) { this.connectionPoolConfig .setEvictionPolicyClassName(evictionPolicyClassName); } public boolean getBlockWhenExhausted() { return this.connectionPoolConfig.getBlockWhenExhausted(); } public void setBlockWhenExhausted(boolean blockWhenExhausted) { this.connectionPoolConfig.setBlockWhenExhausted(blockWhenExhausted); } public boolean getJmxEnabled() { return this.connectionPoolConfig.getJmxEnabled(); } public void setJmxEnabled(boolean jmxEnabled) { this.connectionPoolConfig.setJmxEnabled(jmxEnabled); } public String getJmxNamePrefix() { return this.connectionPoolConfig.getJmxNamePrefix(); } public void setJmxNamePrefix(String jmxNamePrefix) { this.connectionPoolConfig.setJmxNamePrefix(jmxNamePrefix); } public static void main(String[] args) throws LifecycleException { RedisSessionManager manager = new RedisSessionManager(); manager.initializeDatabaseConnection(); } }
4.SerializeUtils
package com.lb.tomcat.redissessions; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import org.nustaq.serialization.FSTConfiguration; public class SerializeUtils { public FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration(); /** * 序列化 * @param objectData * @return */ public byte[] fserialize(Serializable obj) { byte[] bytes = conf.asByteArray( obj); return bytes; } /** * 反序列化 * @param objectData * @return */ public Object fdeserialize(byte[] objectData) { return conf.asObject(objectData); } }
以上代码打包redis-session.jar
其他jar包
commons-pool2-2.0.jar
jedis-2.4.1.jar
fst-2.08.jar
以上4个jar包 放入 tomcat\lib 下面
tomcat/conf/context.xml
增加
<Valve className="com.lb.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.lb.tomcat.redissessions.RedisSessionManager"
host="localhost" <!--- redis服务器IP -->
port="6379"
maxInactiveInterval="60"
webName="Test3" <!-- 你的web应用名-->
/>