分布式Session

一、场景

在集群条件下,比如利用NGINX转发HTTP请求到多个TOMCAT,需要保证每个用户的SESSION在每个TOMCAT下都是一样的。否则会出现需要重复登录的情况,影响用户体验和浪费服务器性能。

二、方案

1、利用Ngnix的ip_hash负载均衡算法每次都将同一用户的所有请求转发至同一台服务器上。优点是简便快捷,缺点是如果那台服务器挂了,所有被hash到那台服务器的ip地址都将无法访问该网站。
2、利用tomcat自带的集群session复制共享,即每次session发生变化时,就广播给所有集群中的服务器,使所有的服务器上的session保持相同。配置不是很难,缺点是会消耗更多内存和带宽,tomcat官方推荐在集群比较小时采用此方案。
3、利用第三方开源组件Tomcat-redis-session-manager来实现session共享。这个方法需要在Tomcat下添加依赖jar包,简单配置context.xml,无代码侵入,但是依赖redis数据库。

三、Shiro实现Session共享

项目中的身份认证、权限管理经常要用到Shiro框架,Shiro也有一套自己的Session管理机制。shiro提供的会话可以用于JavaSE/JavaEE环境,不依赖于任何底层容器,可以独立使用,是完整的会话模块。即可以直接使用shiro的会话管理替换如web容器的会话管理。

Shiro会话管理器 sessionManager

shiro提供了三个默认实现:

  • DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;
  • ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;
  • DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

单机环境下我们使用DefaultWebSecurityManager的默认会话管理器ServletContainerSessionManager,使用的仍然是Tomcat底层的会话机制。

集群环境下要实现Session共享就要用到DefaultWebSessionManager,自己编写Dao来处理Session在Redis中的增删改查。

结合Spring的配置依赖注入如下:

    
        
        
        
        
        
    

    
        
        
        
        
    
        
    
        
    

    
        
    
    
        
    
    
    
        
        
        
        
        
    

    

        
        

        
        

        
        

        
        

        
        

        
        
    


    
      
      
        
    
//继承AbstractSessionDAO重写Session的怎能增删改查方法
public class RedisSessionDao extends AbstractSessionDAO {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private RedisManager redisManager;

    /**
     * The Redis key prefix for the sessions
     */
    private static final String KEY_PREFIX = "shiro_redis_session:";

    @Override
    public void update(Session session) throws UnknownSessionException {
        this.saveSession(session);
    }

    @Override
    public void delete(Session session) {
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }
        redisManager.del(KEY_PREFIX + session.getId());
    }

    @Override
    public Collection getActiveSessions() {
        Set sessions = new HashSet();
        Set keys = redisManager.keys(KEY_PREFIX + "*");
        if(keys != null && keys.size()>0){
            for(byte[] key : keys){
                Session s = (Session) SerializerUtil.deserialize(redisManager.get(SerializerUtil.deserialize(key)));
                sessions.add(s);
            }
        }
        return sessions;
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        this.saveSession(session);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if(sessionId == null){
            logger.error("session id is null");
            return null;
        }

        Session s = (Session)redisManager.get(KEY_PREFIX + sessionId);
        return s;
    }

    private void saveSession(Session session) throws UnknownSessionException{
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }
        //设置过期时间
        long expireTime = 18000000;
        session.setTimeout(expireTime);
        redisManager.setEx(KEY_PREFIX + session.getId(), session, expireTime);
    }

    public void setRedisManager(RedisManager redisManager) {
        this.redisManager = redisManager;
    }

    public RedisManager getRedisManager() {
        return redisManager;
    }
}
//统一操作Redis的Manager工具类
public class RedisManager {


    private RedisTemplate redisTemplate;


    public  void set(String key, T obj) throws DataAccessException{
        final byte[] bkey = key.getBytes();
        final byte[] bvalue = SerializerUtil.serialize(obj);
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Void doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set(bkey, bvalue);
                return null;
            }
        });
    }


    public  boolean setNX(String key, T obj) throws DataAccessException{
        final byte[] bkey = key.getBytes();
        final byte[] bvalue = SerializerUtil.serialize(obj);
        boolean result = redisTemplate.execute(new RedisCallback() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.setNX(bkey, bvalue);
            }
        });

        return result;
    }


    public  void setEx(String key, T obj, final long expireSeconds) throws DataAccessException{
        final byte[] bkey = key.getBytes();
        final byte[] bvalue = SerializerUtil.serialize(obj);
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                connection.setEx(bkey, expireSeconds, bvalue);
                return true;
            }
        });
    }


    public  T get(final String key) throws DataAccessException{
        byte[] result = redisTemplate.execute(new RedisCallback() {
            @Override
            public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.get(key.getBytes());
            }
        });
        if (result == null) {
            return null;
        }
        return SerializerUtil.deserialize(result);
    }


    public Long del(final String key){
        if (StringUtils.isEmpty(key)) {
            return 0l;
        }
        Long delNum = redisTemplate.execute(new RedisCallback() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keys = key.getBytes();
                return connection.del(keys);
            }
        });
        return delNum;
    }

    public Set keys(final String key){
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        Set bytesSet = redisTemplate.execute(new RedisCallback>() {
            @Override
            public Set doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keys = key.getBytes();
                return connection.keys(keys);
            }
        });

        return bytesSet;
    }

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}

亲测过集群环境下没有问题,但是相对于Tomcat-redis-session-manager代码的侵入性较大,自己需要编写的代码较多。

你可能感兴趣的:(分布式Session)