之前寫過一篇 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的管理。