之前写过一篇nginx多tomcat负载均衡,主要记录了使用nginx对多个tomcat 进行负载均衡,其实进行负载均衡之前还有一个问题没有解决,那就是集群间的session共享,不然用户在登录网站之后session保存在tomcat A,但是下次访问的时候nginx分发到了tomcat B,这个时候tomcat B没有刚刚用户登录的session,所以用户就失去了(本次)登录状态,下次访问的时候nginx可能又分发到了tomcat A(其实通过配置可以给各个服务器分配权重,nginx根据权重来转发到对应的服务器),用户本次又是登录的状态了,这样飘忽不定肯定是不行的,所以在进行集群负载均衡之前需要解决session共享的问题。
目录
- 概述:简述本次记录的主要内容
- shiro的session:关于shiro的session管理
- 实现共享
- 总结
概述
因为项目中用到了shiro的权限控制,而且使用的是shiro的session,所以我就基于shiro的session管理基础上对session进行多tomcat共享,共享的思路也很简单,就是将session保存到数据库,每个服务器在收到客户端请求的时候都从数据库中取,这样就统一了多个服务器之间的session来源,实现了共享。只不过这里我使用的数据库是redis。
shiro的session
之前在另外一篇博客(shiro实现APP、web统一登录认证和权限管理)里面也提到了shiro的session问题,其实shiro的session只不过是基于认证的需要对tomcat的session进行了封装,所以只要实现对shiro的session进行持久化就可以了,关于shiro的session管理,开涛老师的这一篇博客讲得很清楚了(http://jinnianshilongnian.iteye.com/blog/2028675),可以参考这一篇博客来了解shiro对session的管理。
实现共享
在明白了shiro的session管理之后,我们就可以在此基础上进行session的共享了,其实只需要继承EnterpriseCacheSessionDAO(其实继承CachingSessionDAO就可以了,但是这里考虑到集群中每次都访问数据库导致开销过大,这里在本地使用ehcache进行缓存,每次读取session的时候都先尝试读取本地ehcache缓存,没有的话再去远程redis数据库中读取),然后覆盖原来的增删改查操作,这样多个服务器就共享了session,具体实现如下:
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.apache.shiro.session.Session; import org.apache.shiro.session.mgt.SimpleSession; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; public class SessionRedisDao extends EnterpriseCacheSessionDAO { // 创建session,保存到数据库 @Override protected Serializable doCreate(Session session) { Serializable sessionId = super.doCreate(session); RedisDb.setObject(sessionId.toString().getBytes(), sessionToByte(session)); return sessionId; } // 获取session @Override protected Session doReadSession(Serializable sessionId) { // 先从缓存中获取session,如果没有再去数据库中获取 Session session = super.doReadSession(sessionId); if(session == null){ byte[] bytes = RedisDb.getObject(sessionId.toString().getBytes()); if(bytes != null && bytes.length > 0){ session = byteToSession(bytes); } } return session; } // 更新session的最后一次访问时间 @Override protected void doUpdate(Session session) { super.doUpdate(session); RedisDb.setObject(session.getId().toString().getBytes(), sessionToByte(session)); } // 删除session @Override protected void doDelete(Session session) { super.doDelete(session); RedisDb.delString(session.getId() + ""); } // 把session对象转化为byte保存到redis中 public byte[] sessionToByte(Session session){ ByteArrayOutputStream bo = new ByteArrayOutputStream(); byte[] bytes = null; try { ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(session); bytes = bo.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return bytes; } // 把byte还原为session public Session byteToSession(byte[] bytes){ ByteArrayInputStream bi = new ByteArrayInputStream(bytes); ObjectInputStream in; SimpleSession session = null; try { in = new ObjectInputStream(bi); session = (SimpleSession) in.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return session; } }
上面的主要逻辑是实现session的管理,下面是和redis数据库交互
import java.util.Arrays; import java.util.Date; import java.util.Set; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisDb { private static JedisPool jedisPool; // session 在redis过期时间是30分钟30*60 private static int expireTime = 1800; // 计数器的过期时间默认2天 private static int countExpireTime = 2*24*3600; private static String password = "123456"; private static String redisIp = "10.10.31.149"; private static int redisPort = 6379; private static int maxActive = 200; private static int maxIdle = 200; private static long maxWait = 5000; private static Logger logger = Logger.getLogger(RedisDb.class); static { initPool(); } // 初始化连接池 public static void initPool(){ JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(maxActive); config.setMaxIdle(maxIdle); config.setMaxWaitMillis(maxWait); config.setTestOnBorrow(false); jedisPool = new JedisPool(config, redisIp, redisPort, 10000, password); } // 从连接池获取redis连接 public static Jedis getJedis(){ Jedis jedis = null; try{ jedis = jedisPool.getResource(); // jedis.auth(password); } catch(Exception e){ ExceptionCapture.logError(e); } return jedis; } // 回收redis连接 public static void recycleJedis(Jedis jedis){ if(jedis != null){ try{ jedis.close(); } catch(Exception e){ ExceptionCapture.logError(e); } } } // 保存字符串数据 public static void setString(String key, String value){ Jedis jedis = getJedis(); if(jedis != null){ try{ jedis.set(key, value); } catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } } // 获取字符串类型的数据 public static String getString(String key){ Jedis jedis = getJedis(); String result = ""; if(jedis != null){ try{ result = jedis.get(key); }catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } return result; } // 删除字符串数据 public static void delString(String key){ Jedis jedis = getJedis(); if(jedis != null){ try{ jedis.del(key); }catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } } // 保存byte类型数据 public static void setObject(byte[] key, byte[] value){ Jedis jedis = getJedis(); String result = ""; if(jedis != null){ try{ if(!jedis.exists(key)){ jedis.set(key, value); } // redis中session过期时间 jedis.expire(key, expireTime); } catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } } // 获取byte类型数据 public static byte[] getObject(byte[] key){ Jedis jedis = getJedis(); byte[] bytes = null; if(jedis != null){ try{ bytes = jedis.get(key);; }catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } return bytes; } // 更新byte类型的数据,主要更新过期时间 public static void updateObject(byte[] key){ Jedis jedis = getJedis(); if(jedis != null){ try{ // redis中session过期时间 jedis.expire(key, expireTime); }catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } } // key对应的整数value加1 public static void inc(String key){ Jedis jedis = getJedis(); if(jedis != null){ try{ if(!jedis.exists(key)){ jedis.set(key, "1"); jedis.expire(key, countExpireTime); } else { // 加1 jedis.incr(key); } }catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } } // 获取所有keys public static Set<String> getAllKeys(String pattern){ Jedis jedis = getJedis(); if(jedis != null){ try{ return jedis.keys(pattern); }catch(Exception e){ ExceptionCapture.logError(e); } finally{ recycleJedis(jedis); } } return null; } }
总结
这里只是实现了简单的session共享,但是对session的管理还不够全面,比如说session的验证。其实通过tomcat容器本身就可以实现session共享,后面再详细了解下tomcat对于session的管理。