以前寫過一篇nginx多tomcat負載均衡,主要記錄了使用nginx對多個tomcat 進行負載均衡,其實進行負載均衡以前還有一個問題沒有解決,那就是集羣間的session共享,否則用戶在登陸網站以後session保存在tomcat A,可是下次訪問的時候nginx分發到了tomcat B,這個時候tomcat B沒有剛剛用戶登陸的session,因此用戶就失去了(本次)登陸狀態,下次訪問的時候nginx可能又分發到了tomcat A(其實經過配置能夠給各個服務器分配權重,nginx根據權重來轉發到對應的服務器),用戶本次又是登陸的狀態了,這樣飄忽不定確定是不行的,因此在進行集羣負載均衡以前須要解決session共享的問題。html
由於項目中用到了shiro的權限控制,並且使用的是shiro的session,因此我就基於shiro的session管理基礎上對session進行多tomcat共享,共享的思路也很簡單,就是將session保存到數據庫,每一個服務器在收到客戶端請求的時候都從數據庫中取,這樣就統一了多個服務器之間的session來源,實現了共享。只不過這裏我使用的數據庫是redis。java
以前在另一篇博客(shiro實現APP、web統一登陸認證和權限管理)裏面也提到了shiro的session問題,其實shiro的session只不過是基於認證的須要對tomcat的session進行了封裝,因此只要實現對shiro的session進行持久化就能夠了,關於shiro的session管理,開濤老師的這一篇博客講得很清楚了(http://jinnianshilongnian.iteye.com/blog/2028675),能夠參考這一篇博客來了解shiro對session的管理。nginx
在明白了shiro的session管理以後,咱們就能夠在此基礎上進行session的共享了,其實只須要繼承EnterpriseCacheSessionDAO(其實繼承CachingSessionDAO就能夠了,可是這裏考慮到集羣中每次都訪問數據庫致使開銷過大,這裏在本地使用ehcache進行緩存,每次讀取session的時候都先嚐試讀取本地ehcache緩存,沒有的話再去遠程redis數據庫中讀取),而後覆蓋原來的增刪改查操做,這樣多個服務器就共享了session,具體實現以下:web
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數據庫交互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的管理。數據庫