基于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"/>
基于redis实现tomcat8的tomcat集群的session持久化实现(tomcat-redis-session-manager二次开发)的更多相关文章
- tomcat集群和负载均衡的实现(session同步)
(一)环境说明 (1)服务器有4台,一台安装apache,三台安装tomcat (2)apache2.0.55.tomcat5.5.15.jk2.0.4.jdk1.5.6或jdk1.4.2 (3) ...
- 【原创】Tomcat集群环境下对session进行外部缓存的方法(2)
Session对象的持久化比较麻烦,虽然有序列化,但是并不确定Session对象中保存的其他信息是否可以序列化,这可能是网上很多解决方案摒弃此种做法的原因,网上的很多做法都是将Session中的att ...
- 19.Tomcat集群架构
1.Nginx+Tomcat集群架构介绍 2.Nginx+Tomcat集群架构实战 [root@lb01 conf.d]# cat proxy_zrlog.cheng.com.conf upstrea ...
- Nginx+Tomcat集群部署
为了获取更好的性能,我们常常需要将tomcat进行集群部署.下文通过nginx转发实现tomcat集群,并通过nginx-upstream-jvm-route插件保证session的粘滞. 应用场景环 ...
- tomcat集群与负载均衡
参考文章http://kalogen.iteye.com/blog/784908,加上了自己调试过程中遇到的问题. 注1:实现此集群的方法参考了网上的很多文章,但由于很多文章都表明是原创的,故无法知道 ...
- nginx反向代理和tomcat集群(适用于ubutnu16.04及其centos7)
下面示例,本人亲测有效 为什么要反向代理和集群? 因为并发问题,很多请求如果全部分发给一个tomcat,一个tomcat优化最好的话,据说可达到800负载,但是面对成千上万的请求,单单一个tomcat ...
- linux搭建tomcat集群+nginx
安装JDK 一.官方下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 下 ...
- Tomcat集群搭建超详细(apache+mod_jk+tomcat)
TOMCAT集群 目录 TOMCAT集群 1 1 集群 1 1.1 什么是集群 1 1.2 集群的特性 1 1.3 集群的分类 1 1.4 TOMCAT集群配置的优缺点 2 1.5 APACHE+TO ...
- linux下实现redis共享session的tomcat集群
为了实现主域名与子域名的下不同的产品间一次登录,到处访问的效果,因此采用rediss实现tomcat的集群效果.基于redis能够异步讲缓存内容固化到磁盘上,从而当服务器意外重启后,仍然能够让sess ...
- 基于Redis的CAS服务端集群
为了保证生产环境CAS(Central Authentication Service)认证服务的高可用,防止出现单点故障,我们需要对CAS Server进行集群部署. CAS的Ticket默认是以Ma ...
随机推荐
- T2602 最短路径问题 codevs
http://codevs.cn/problem/2602/ 时间限制: 1 s 空间限制: 32000 KB 题目等级 : 黄金 Gold 题目描述 Description 平面上有n个点(n& ...
- Volley 源码解析 StringRequest解析
Android Vollety是一个很有用的框架,所以想借鉴前人思想,分析这个源代码. 参考: http://blog.csdn.net/crazy__chen/article/details/464 ...
- ffmpeg实时编码解码部分代码
程序分为编码端和解码端,两端通过tcp socket通信,编码端一边编码一边将编码后的数据发送给解码端.解码端一边接收数据一边将解码得到的帧显示出来. 代码中的编码端编码的是实时屏幕截图. 代码调用 ...
- GoogLeNet系列解读
GoogLeNet Incepetion V1 这是GoogLeNet的最早版本,出现在2014年的<Going deeper with convolutions>.之所以名为“GoogL ...
- add swapspace file on ubuntu.
https://askubuntu.com/questions/566745/allocate-swap-after-ubuntu-14-04-lts-installation ----------- ...
- [Testing] Config jest to test Javascript Application -- Part 1
Transpile Modules with Babel in Jest Tests Jest automatically loads and applies our babel configurat ...
- [WASM Rust] Use the js-sys Crate to Invoke Global APIs Available in Any JavaScript Environment
js-sys offers bindings to all the global APIs available in every JavaScript environment as defined b ...
- C++学习总结3
链接上一篇日志,C++学习总结2,下面介绍下C++里面的其他内容 虚函数:一个接口,多种方法. 多态:编译时的多态与运行时的多态. 编译时的多态表现为运算符的重载与函数的重载. 运行时的多态表现为使用 ...
- mongodb+php通过_id查询
在php中通过_id 在mongodb中查找特定记录: <?php $conn=new Mongo("127.0.0.1:27017"); #连接指定端口远程主机 $db=$ ...
- php使用魔法函数和不使用魔法函数比较
/** * use magic 0.31868386268616s * not use magic 0.11876797676086s */ class Test { private $varstr ...