shiro(四)集成redis实现分布式session

一、前言

前面的文章中,shiro使用的是ehcache做缓存,这样在单机服务中,没有任何问题,但是如果是在集群环境下,就无法实现session共享了。分布式session有多种实现方式:

1. Session Replication 方式管理 (即session复制)
        简介:将一台机器上的Session数据广播复制到集群中其余机器上
        使用场景:机器较少,网络流量较小
        优点:实现简单、配置较少、当网络中有机器Down掉时不影响用户访问
        缺点:广播式复制到其余机器有一定廷时,带来一定网络开销
2. Session Sticky 方式管理
        简介:即粘性Session、当用户访问集群中某台机器后,强制指定后续所有请求均落到此机器上
        使用场景:机器数适中、对稳定性要求不是非常苛刻
        优点:实现简单、配置方便、没有额外网络开销
        缺点:网络中有机器Down掉时、用户Session会丢失、容易造成单点故障
3. 缓存集中式管理
       简介:将Session存入分布式缓存集群中的某台机器上,当用户访问不同节点时先从缓存中拿Session信息
       使用场景:集群中机器数多、网络环境复杂
       优点:可靠性好
       缺点:实现复杂、稳定性依赖于缓存的稳定性、Session信息放入缓存时要有合理的策略写入

本文介绍第三种方式实现分布式session

二、具体实现

使用缓存集中式管理实现分布式session,必然要有一个分布式的缓存,这里采用redis实现

1.集成redis

  • 引入redis相关jar包
<dependency>  
	<groupId>org.springframework.data</groupId>  
	<artifactId>spring-data-redis</artifactId>  
	<version>1.0.2.RELEASE</version>  
</dependency>      
<dependency>  
	<groupId>redis.clients</groupId>  
	<artifactId>jedis</artifactId>  
	<version>2.1.0</version>  
</dependency>
  • 添加redis配置

spring-redis.xml配置

<beans     xmlns="http://www.springframework.org/schema/beans" 
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p" 
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/tx 
            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.0.xsd
               ">
    
    <!-- scanner redis properties  --> 
    <context:property-placeholder location="/WEB-INF/property/redis.properties" />
    
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
        <property name="maxIdle" value="${redis.maxIdle}" />  
        <property name="maxActive" value="${redis.maxActive}" />  
        <property name="maxWait" value="${redis.maxWait}" />  
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
    </bean>  
      
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"  
        p:host-name="${redis.host}" 
        p:port="${redis.port}" 
        p:password="${redis.pass}"  
        p:pool-config-ref="poolConfig"/>  
      
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">  
        <property name="connectionFactory"   ref="connectionFactory" />  
    </bean>      
     
</beans>

redis.properties配置

redis.host=127.0.0.1
redis.port=6379
redis.pass=
  
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true

2.重写session

之前的session是通过EnterpriseCacheSessionDAO来生成,查询的,那么如果想重写session,必然也是重写session的增删改查,所以只需要继承EnterpriseCacheSessionDAO类来,重写有关session的方法即可。

package com.wangcongming.crm.config.shiro;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Repository;

/**
 * 
 * @ClassName:  RedisSessionDAO   
 * @Description:redis实现共享session
 * @author: wangcongming
 * @date:   2018年6月28日 下午3:04:41   
 *
 */
@Repository
public class RedisSessionDAO extends EnterpriseCacheSessionDAO {

    private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);

    /*
     * session 在redis过期时间是30分钟30*60
     */
    private int expireTime = 1800;
    //shiro session
    public final static String USER_SHIRO_SESSION = "user:shiro:session:";

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String, Serializable> redisTemplate;
    @Resource(name = "redisTemplate")
    private ValueOperations<String, SimpleSession> vops;

    /**
     * 
     * <p>Title: doCreate</p>   
     * <p>Description:创建session,保存到数据库 </p>   
     * @param session
     * @return   
     * @see EnterpriseCacheSessionDAO#doCreate(Session)
     */
    @Override
    protected Serializable doCreate(Session session) {
    	Serializable sessionId = super.doCreate(session);
        logger.debug("创建session:{}", sessionId);
        vops.set(USER_SHIRO_SESSION + sessionId.toString(), (SimpleSession)session);
        return sessionId;
    }

    // 获取session
    @Override
    protected Session doReadSession(Serializable sessionId) {
        logger.debug("获取session:{}", sessionId);
        // 先从缓存中获取session,如果没有再去数据库中获取
        Session session = super.doReadSession(sessionId);
        if (session == null) {
            session = (SimpleSession)vops.get(USER_SHIRO_SESSION + sessionId.toString());
        }
        return session;
    }

    /**
     *
     * <p>Title: doUpdate</p>
     * <p>Description:  更新session的最后一次访问时间</p>
     * @param session
     * @see EnterpriseCacheSessionDAO#doUpdate(Session)
     */
    @Override
    protected void doUpdate(Session session) {
        super.doUpdate(session);
        logger.debug("获取session:{}", session.getId());
        String key = USER_SHIRO_SESSION + session.getId().toString();
        if (!redisTemplate.hasKey(key)) {
        	vops.set(key, (SimpleSession)session);
        }
        redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
    }

    /**
     *
     * <p>Title: doDelete</p>
     * <p>Description:  删除session</p>
     * @param session
     * @see EnterpriseCacheSessionDAO#doDelete(Session)
     */
    @Override
    protected void doDelete(Session session) {
        super.doDelete(session);
        logger.debug("删除session:{}", session.getId());
        redisTemplate.delete(USER_SHIRO_SESSION + session.getId().toString());
    }
}

在以上实现中可以看到,都是先调用EnterpriseCacheSessionDAO的方法进行一个本地session的操作,然后再去redis中操作session,这样做就可以减少连接redis的次数,效率更高。

3.重写cache

shiro中有缓存策略,之前使用的ehcache,ehcache是一种本地缓存,不适用于分布式服务,对缓存有研究的人,应该都知道,缓存分为存储缓存的cache和管理缓存的CacheManager,如果想实现分布式缓存,只需要重写cache和CacheManager即可。

  • cache重写
package com.wangcongming.crm.config.shiro;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;

public class ShiroRedisCache<K, V> implements Cache<K, V> {

	private String cacheKey;
	private RedisTemplate<K, V> redisTemplate;
	//过期时间
	private long globExpire = 30;
	
	private final static String USER_SHIRO_PREFLX = "user:shiro:preflx:";
	
    @SuppressWarnings("rawtypes")
    public ShiroRedisCache(String name, RedisTemplate client) {
        this.cacheKey = String.format("%s%s:", USER_SHIRO_PREFLX,name);
        this.redisTemplate = client;
    }

    @Override
    public V get(K key) throws CacheException {
        redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MINUTES);
        return redisTemplate.boundValueOps(getCacheKey(key)).get();
    }

    @Override
    public V put(K key, V value) throws CacheException {
        V old = get(key);
        redisTemplate.boundValueOps(getCacheKey(key)).set(value);
        return old;
    }

    @Override
    public V remove(K key) throws CacheException {
        V old = get(key);
        redisTemplate.delete(getCacheKey(key));
        return old;
    }

    @Override
    public void clear() throws CacheException {
        redisTemplate.delete(keys());
    }

    @Override
    public int size() {
        return keys().size();
    }

    @Override
    public Set<K> keys() {
        return redisTemplate.keys(getCacheKey("*"));
    }

    @Override
    public Collection<V> values() {
        Set<K> set = keys();
        List<V> list = new ArrayList<>();
        for (K s : set) {
            list.add(get(s));
        }
        return list;
    }

    private K getCacheKey(Object k) {
        return (K) (this.cacheKey + k);
    }

}

看代码就可以看出,重写cache就是实现shiro定义的org.apache.shiro.cache.Cache接口即可。

  • 重写CacheManager
package com.wangcongming.crm.config.shiro;

import java.io.Serializable;

import javax.annotation.Resource;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * 
 * @ClassName:  RedisCacheManager   
 * @Description:使用redis实现shiro缓存管理 
 * @author: wangcongming
 * @date:   2018年2月1日 下午3:05:07   
 *
 */
public class RedisCacheManager implements CacheManager {

    @Resource
    private RedisTemplate<String, Serializable> redisTemplate;

    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        return new ShiroRedisCache<K, V>(name, redisTemplate);
    }

    public RedisTemplate<String, Serializable> getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<String, Serializable> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

}

同样的重写cacheManager也是实现shiro定义的org.apache.shiro.cache.CacheManager接口即可

4.配置

实现了session和cache的重写,但是此时还没有被spring shiro使用,所以需要通过配置来实现让容器使用自己实现的session和cache,具体实现如下:

<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"></bean>
	
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
	<property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
</bean>

<!-- 管理Session -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
	<property name="sessionDAO" ref="sessionDAO"/>
	<property name="cacheManager" ref="cacheManager" />
</bean>

猜你喜欢

转载自blog.csdn.net/linhui258/article/details/81141639