一、实现session共享
1. 聊聊session共享2. shiro实现session共享(使用redis方式实现)3. 共享缓存实现4. 总结
一、实现session共享 1. 聊聊session共享如果是单机应用,session共享意义不大。使用默认的session缓存,还是存放到第三方应用缓存中,都可以。当然极端情况下,如果服务器内存非常小等极特殊情况下可能需要第三方缓存的。
session共享是针对集群(或分布式、或分布式集群)的情况下采用;如果不做session共享,仍然采用默认的方式(session存放到默认的servlet容器),当我们的应用是以集群的方式发布的时候,同个用户的请求会被分发到不同的集群节点(分发依赖具体的负载均衡规则),那么每个处理同个用户请求的节点都会重新生成该用户的session,这些session之间是毫无关联的。那么同个用户的请求会被当成多个不同用户的请求,这肯定是不行的。
2. shiro实现session共享(使用redis方式实现)实现共享session是比较简单的,换一种说明你就能明白。大家都会增、删、改、查,session的操作就是增删改查的过程,只不过默认是缓存到servlet容器中,咱们要将数据转移到redis,来实现它的增删改查。这样在集群环境中,大家都访问这个redis,也就实现了共享session.
下面首先要了解下 shiro创建和缓存session的过程。
- DefaultWebSessionManager.java
这个类是shiro的session管理类,我们在管理session生命周期时候,也该从这入手。CachingSessionDAO.java
这个类是session创建、保存、删除、更新操作的代码,默认会保存的servlet容器中。我们只需要继承这个类,并覆写对应代码即可实现session从redis中增删改查。下面来看看CachingSessionDAO类官方的源码,其实也是增删改查:
protected abstract Serializable doCreate(Session session);
public void delete(Session session) {
uncache(session);
doDelete(session);
}
public void update(Session session) throws UnknownSessionException {
doUpdate(session);
if (session instanceof ValidatingSession) {
if (((ValidatingSession) session).isValid()) {
cache(session, session.getId());
} else {
uncache(session);
}
} else {
cache(session, session.getId());
}
}
protected abstract Session doReadSession(Serializable sessionId);
从源码中可以看出,共享sessionId说白了就是改变增删改查保存的位置。
默认session是保存的servlet缓存中,进行增删改查,现在咱们覆写方法,把增删改查的数据源改为redis。
- 新建实现类RedisCachingSessionDao,并集成CachingSessionDAO
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
@Component
public class RedisCachingSessionDao extends CachingSessionDAO {
private long expireTime = 1800000;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Collection getActiveSessions() {
return redisTemplate.keys("*");
}
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
return null;
}
Session session = (Session) redisTemplate.opsForValue().get(sessionId);
return session;
}
@Override
protected void doUpdate(Session session) {
if (session == null || session.getId() == null) {
return;
}
//设置超时时间,这个是毫秒
session.setTimeout(expireTime);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
}
@Override
protected void doDelete(Session session) {
if (null == session) {
return;
}
redisTemplate.opsForValue().getOperations().delete(session.getId());
}
}
这个类的代码中实现了对session的增删改查操作,大家应该到这比较容易理解了。
- 最后将实现类应用到DefaultWebSessionManager中。
在自己的ShiroConfig配置类型,如下代码:
@Bean
public DefaultWebSecurityManager securityManager(RedisCachingSessionDao redisCachingSessionDao) {
//新建security并设置realm、SessionManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(new AdminAuthorizingRealm());
//新建SessionManager并设置SessionDao(session的获取途径)
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisCachingSessionDao);
//应用sessionManager
securityManager.setSessionManager(sessionManager);
return securityManager;
}
将自己的实现类redisCachingSessionDao,通过代码set到sessionManager中。
这样自己创建session后,可以从redis中查到自己的session数据了,共享session完成了,简单吧!
共享session已经实现了,集群环境中能够访问相同的session数据源,但是shiro仍会有一些数据会缓存在servlet容器中,这样集群环境会出现一些其他的shiro配置数据各自用的还是各自的,出现各种各样问题。所以后面还需要解决 共享缓存的问题。
用到下面一些类:
- CacheManager.java
这个类是shiro的缓存接口,默认会使用servlet容器的来充当缓存。
我们看下源码,这个类定义了获取缓存的方法。
package org.apache.shiro.cache;
public interface CacheManager {
Cache getCache(String var1) throws CacheException;
}
- Cache.java
这个类是获取缓存方式的,通过getCache得到Cache的实现类,默认获取到的是shiro自己容器中,看下源码分析:
public interface Cache{ V get(K var1) throws CacheException; V put(K var1, V var2) throws CacheException; V remove(K var1) throws CacheException; void clear() throws CacheException; int size(); Set keys(); Collection values(); }
同样道理,咱们继承这个类,并覆写这个类的增删改查,从缓存到servlet,转移到redis中。
下面咱们定义自己的实现类
3. ShiroRedisCache
import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Set; import java.util.concurrent.TimeUnit; @Component public class ShiroRedisCacheimplements Cache { @Autowired private RedisTemplate redisTemplate; private long expireTime = 1800; @Override public V get(K k) throws CacheException { return redisTemplate.opsForValue().get(k); } @Override public V put(K k, V v) throws CacheException { redisTemplate.opsForValue().set(k,v,expireTime, TimeUnit.SECONDS); return null; } @Override public V remove(K k) throws CacheException { V v = redisTemplate.opsForValue().get(k); redisTemplate.opsForValue().getOperations().delete(k); return v; } @Override public void clear() throws CacheException { } @Override public int size() { return 0; } @Override public Set keys() { return null; } @Override public Collection values() { return null; } }
然后再次继承CacheManger,实现getCache方法
4. ShiroRedisCacheManager
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class ShiroRedisCacheManager implements CacheManager {
@Resource
private Cache shiroRedisCache;
@Override
public Cache getCache(String s) throws CacheException {
return shiroRedisCache;
}
}
最后将两个类set到Shrio配置中。
4. set到shiro配置类中
@Bean
public DefaultWebSecurityManager securityManager(RedisCachingSessionDao redisCahingSessionDao, ShiroRedisCacheManager shiroRedisCacheManager) {
//新建security并设置realm、CacheManager、SessionManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(new AdminAuthorizingRealm());
//如果使用redis共享session,这个必须设置,因为集群之中 session要共享,同样一些缓存的数据也要共享,比如shiro缓存的数据
securityManager.setCacheManager(shiroRedisCacheManager);
//新建SessionManager并设置SessionDao(session的获取途径)
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisCahingSessionDao);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
上面代码通过securityManager设置cacheManager属性来使用redis缓存方式。
4. 总结- 集群环境中shiro实现完共享session缓存,同样也要实现共享缓存,才能保证系统完美运行。共享缓存不一定要用redis,大家通过覆写方法可以用mongodb、mysql、其他缓存工具等等都是可以实现的,当然要考虑效率和性能。集群环境中负载均衡还可以通过ip_hash的机制将同个ip的请求定向到同一台后端,这样保证用户的请求始终是同一台服务处理,与单机应用基本一致了;但这有很多方面的缺陷(比如在同一个局域网环境下,都会分配到同一个ip,就失去负载均衡作用了)
nginx负载均衡可以参考本人博文:https://blog.csdn.net/wangyue23com/article/details/108197650



