使用redis進行基於shiro的session集羣共享

以前寫過一篇nginx多tomcat負載均衡,主要記錄了使用nginx對多個tomcat 進行負載均衡,其實進行負載均衡以前還有一個問題沒有解決,那就是集羣間的session共享,否則用戶在登陸網站以後session保存在tomcat A,可是下次訪問的時候nginx分發到了tomcat B,這個時候tomcat B沒有剛剛用戶登陸的session,因此用戶就失去了(本次)登陸狀態,下次訪問的時候nginx可能又分發到了tomcat A(其實經過配置能夠給各個服務器分配權重,nginx根據權重來轉發到對應的服務器),用戶本次又是登陸的狀態了,這樣飄忽不定確定是不行的,因此在進行集羣負載均衡以前須要解決session共享的問題。html

目錄

  • 概述:簡述本次記錄的主要內容 
  • shiro的session:關於shiro的session管理
  • 實現共享
  • 總結

概述

由於項目中用到了shiro的權限控制,並且使用的是shiro的session,因此我就基於shiro的session管理基礎上對session進行多tomcat共享,共享的思路也很簡單,就是將session保存到數據庫,每一個服務器在收到客戶端請求的時候都從數據庫中取,這樣就統一了多個服務器之間的session來源,實現了共享。只不過這裏我使用的數據庫是redis。java

shiro的session

以前在另一篇博客(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的管理。數據庫

相關文章
相關標籤/搜索