zoukankan      html  css  js  c++  java
  • 使用redis进行基于shiro的session集群共享

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

  • 相关阅读:
    深入理解Java Proxy机制
    StringBuilder与StringBuffer的区别
    反射
    list和set区别
    spring总结
    Hibernate与 MyBatis的比较
    手机拍照或选择照片上传html5代码
    JSP两种跳转
    离散化
    圆方树
  • 原文地址:https://www.cnblogs.com/xumaojun/p/8521743.html
Copyright © 2011-2022 走看看