您的位置:首页 > 数据库 > Redis

springboot/springmvc+shiro+zookeeper作session共享+redis作缓存共享

2017-08-17 00:00 288 查看
摘要: shiro-session共享缓存共享,zookeeper作session共享,redis作缓存共享,springboot/springmvc的session共享和缓存共享,服务器节点二级本地缓存

**理解.**说到session共享,有了解过的开发人员应该都会想到应用场景是在集群环境中,是为了保证用户登录或者访问集群系统中,在各个应用节点的生命周期保持一致性。

集群方式

一、真正的请求奋发,就是用户的每个请求都通过'nginx'或者'apache'做一个路由转发,根据定义的规则随机转发到一台负载并不是很高的服务器上,也就是负载均衡,最小粒度是请求。
二、“伪集群”,我对这种方式的称谓,也是通过nginx(ip_hash)或者apache的功能实现定向转发,即按照用户的ip地址实现唯一一个服务器为用户提供服务,最小粒度是用户。

实现方式

一、就拿springmvc/springboot为例,可以实现无状态应用,就是在系统中不依赖于session来实现,集群方式一、二都可以采用
二、在有状态的系统中,在不集成shiro的前提下,使用'spring-session'的jar包+'redis'作为session存储的容器,当然也可以选取一些其他内存型数据库也可以,如:'mongo'。系统不需要修改任何的代码即可实现session共享或者缓存共享。
三、还是有状态的前提下,如果采用实现方式二的方式也可以实现,但是因为shiro的特殊性,他会频繁的去读取session,这样由于网络传输的延时造成性能的急剧下降,问题很严重,后来考虑到zookeeper用于存储配置信息,还能建立回调监听,集群环境下可以将zookeeper跟应用部署在一台机器上,能大大减少读取数据网络延时,决定在zookeeper作为session共享介质,这种方式可以加上服务器节点二级缓存,利用zookeeper的监听保持二级缓存一致性

以上三种实现方式缓存共享都还在redis中,因为考虑到缓存数据可能很大,zookeeper不适合存储大数据,前两种实现方式有兴趣的“童鞋”可以自行去尝试,我只介绍一下第三种实现方式的细节springboot+shiro+zookeeper+redis+ehcache

#shiro+zookeeper-session共享

**shiro-session共享:**需要重写sessionDao的实现,将session的CRUD存储到zookeeper中,代码如下:

a. zookeeper操作的客户端封装:

package com.github.tc.shiro.zookeeper;

import com.github.tc.shiro.shiroredis.ClearCacheVo;
import com.github.tc.shiro.tools.SerializeUtils;
import com.github.tc.shiro.zookeeper.listener.ZookeeperListener;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.UnhandledErrorListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* usedfor:zookeeper的客户端Curator
* Created by javahao on 2017/8/15.
* auth:JavaHao
*/
public class CuratorClient implements DisposableBean {
private static Logger logger = LoggerFactory.getLogger(CuratorClient.class);
private static CuratorFramework client;
private static String connectionString="127.0.0.1:2181";
private static List<ZookeeperListener> listeners;
/**
* zookeeper缓存清除配置存储路径
*/
public static final String CACHE_PATH="/scm/door/cache";
public static final String SESSION_PATH="/scm/door/session";

public static void setConnectionString(String connectionString) {
CuratorClient.connectionString = connectionString;
}

public static void setListeners(List<ZookeeperListener> listeners) {
CuratorClient.listeners = listeners;
}

/**
* 删除节点
* @param path 路径
* @return 结果
*/
public static boolean delete(String path){
try {
if(!checkExists(path))
return true;
getClient().delete().forPath(path);
} catch (Exception e) {
logger.error("删除节点失败:"+path,e);
}
return true;
}

/**
* 获取子节点数据集合
* @param path 父节点
* @param <T> 数据类型
* @return 结果
*/
public static <T> Set<T> getChilds(String path){
Set<T> results = null;
try {
//如果节点不存在则返回空
if(!checkExists(path))
return null;
List<String> childsPath = getClient().getChildren().forPath(path);
results = new HashSet<T>();
byte[] nodeData = null;
for(String childPath : childsPath){
if(StringUtils.isBlank(childPath))
continue;
nodeData = get(path+"/"+childPath);
T t = null;
if(nodeData!=null&&(t=(T) SerializeUtils.deserialize(nodeData))!=null) {
results.add(t);
}else {
delete(path+"/"+childPath);
}
}
} catch (Exception e) {
logger.error("获取子节点数据集合错误",e);
}
return results;
}

/**
* 获取节点数据
* @param path 节点路径
* @return 结果
*/
public static byte[] get(String path){
try {
//如果节点不存在则返回空
if(!checkExists(path))
return null;
return getClient().getData().forPath(path);
} catch (Exception e) {
logger.error("获取节点数据错误:"+path,e);
}
return null;
}

/**
* 判断节点是否存在
* @param path 节点路径
* @return 结果
*/
public static boolean checkExists(String path){
//如果节点不存在则返fasle
try {
if(getClient().checkExists().forPath(path)==null)
return false;
} catch (Exception e) {
logger.error("校验节点是否存在失败:"+path,e);
}
return true;
}

/**
* 设置zookeeper节点数据,有则更新无则插入
* @param path 节点路径
* @param obj 数据
* @return 成功标识
*/
public static boolean update(String path, Object obj){
try {
//如果节点不存在则返回空
if(checkExists(path))
getClient().setData().forPath(path,SerializeUtils.serialize(obj));
else
getClient().create().creatingParentsIfNeeded().forPath(path,SerializeUtils.serialize(obj));
} catch (Exception e) {
logger.error("设置节点数据失败:"+path,e);
}
return true;
}

/**
* 获取zookeeper操作客户端
* @return 返回客户端
*/
public static CuratorFramework createSimple(){
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
return CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
}

public static CuratorFramework getClient() {
if(client==null) {
client = createSimple();
client.start();
}
return client;
}

/**
* 清除配置内所有节点缓存
* @param vo 配置
*/
public static void clearNodesCache(ClearCacheVo vo){
try {
String path = CACHE_PATH+"/"+vo.getCacheKey();
Stat s = getClient().checkExists().forPath(path);
//如果节点存在直接将清除缓存配置设置到此节点,不存在则创建节点并设置数据
if(s!=null){
getClient().setData().forPath(path,SerializeUtils.serialize(vo));
}else
getClient().create().creatingParentsIfNeeded().forPath(path,SerializeUtils.serialize(vo));
} catch (Exception e) {
logger.error("标识清除缓存节点配置失败!",e);
}
}

/**
* 注册zookeeper节点的监听
*/
public static void registerListeners(){
getClient().getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
logger.info("CuratorFramework state changed: {}", newState);
if(newState == ConnectionState.CONNECTED || newState == ConnectionState.RECONNECTED){
for(ZookeeperListener listener : listeners){
listener.executor(client);
logger.info("Listener {} executed!", listener.getClass().getName());
}
}
}
});

getClient().getUnhandledErrorListenable().addListener(new UnhandledErrorListener() {
@Override
public void unhandledError(String message, Throwable e) {
logger.info("CuratorFramework unhandledError: {}", message);
}
});
}

@Override
public void destroy() throws Exception {
getClient().close();
}

public static void main(String[] args) throws Exception {
CuratorFramework client = getClient();
Stat stat = client.checkExists().forPath("/scm/cache");
System.out.println(stat);
if(stat!=null)
client.setData().forPath("/scm/cache", SerializeUtils.serialize( new ClearCacheVo("authorizationCache","5db4373868d24118959ac15f8f11d69d.authorizationCache")));
else
client.create().creatingParentsIfNeeded().forPath("/scm/cache", SerializeUtils.serialize( new ClearCacheVo("authorizationCache","c77ddd161f354f9ab665a998b358179d.authorizationCache")));
//        System.out.println(SerializeUtils.deserialize(client.getData().forPath("/scm/cache")));
client.close();
}
}

**b.**sessionDao实现重写到zookeeper

package com.github.tc.shiro.zookeeper.session;

import com.github.tc.shiro.tools.SerializeUtils;
import com.github.tc.shiro.zookeeper.CuratorClient;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
* usedfor:用zookeeper管理session
* Created by javahao on 2017/8/16.
* auth:JavaHao
*/
public class ShiroZookeeperSessionDao extends CachingSessionDAO {
public static final String SHIRO_SESSION_ZK_ROOT_PATH="/scm/repository/shirosession";
private boolean useMemCache = true;
public ShiroZookeeperSessionDao() {
}

/**
* SESSION ZK DAO 实例
* 如果开户缓存
* 用户登录时自动缓存, 用户登录超时自动删除
* 由于shiro的cacheManager是全局的, 所以这里使用setActiveSessionsCache直接设置Cache来本地缓存, 而不使用全局zk缓存.
* 由于同一用户可能会被路由到不同服务器,所以在doReadSession方法里也做了缓存增加.
*
* @param useMemCache 是否使用内存缓存登录信息
*/
public ShiroZookeeperSessionDao(boolean useMemCache) {
this.useMemCache = useMemCache;
}

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

/**
* 缓存根路径, 结尾不加/
*/
private String shiroSessionZKPath = SHIRO_SESSION_ZK_ROOT_PATH;

/**
* 缓存项前缀
*/
private String sessionPrefix = "session-";

/**
* 设置Shiro Session 前缀 默认 session-
*
* @param sessionPrefix
*/
public void setSessionPrefix(String sessionPrefix) {
this.sessionPrefix = sessionPrefix;
}

/**
* 设置Shiro在ZK服务器存放根路径
*
* @param shiroSessionZKPath 默认值:"/scm/repository/shirosession"
*/
public void setShiroSessionZKPath(String shiroSessionZKPath) {
this.shiroSessionZKPath = shiroSessionZKPath;
}

/**
* session更新
* @param session
* @throws UnknownSessionException
*/
@Override
public void update(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
logger.error("session argument cannot be null.");
}
try {
//如果会话过期/停止 没必要再更新了
if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
return;
}
if (session instanceof ShiroSession) {
// 如果没有主要字段(除lastAccessTime以外其他字段)发生改变
ShiroSession ss = (ShiroSession) session;
if (!ss.isChanged()) {
return;
}
ss.setChanged(false);
ss.setLastAccessTime(new Date());
saveSession(session);
this.cache(session, session.getId());
}
} catch (Exception e) {
logger.error("更新Session失败", e);
}
}

@Override
protected void doUpdate(Session session) {
}

/**
* session删除
*
* @param session
*/
@Override
public void delete(Session session) {
if (session == null || session.getId() == null) {
logger.error("session argument cannot be null.");
}
logger.debug("delete session for id: {}", session.getId());
CuratorClient.delete(getPath(session.getId()));
if (useMemCache) {
this.uncache(session);
}
}

@Override
protected void doDelete(Session session) {
}

/**
* 获取当前活跃的session, 当前在线数量
*
* @return
*/
@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = CuratorClient.getChilds(shiroSessionZKPath);
Set<Session> sessionResults = new HashSet<Session>();
for(Session s : sessions)
if(s instanceof ShiroSession) {
if (!((ShiroSession) s).outDate()) {
sessionResults.add(s);
} else {
//清除掉过期的session
CuratorClient.delete(getPath(s.getId()));
}
}else{
sessionResults.add(s);
}

if(sessionResults!=null)
logger.debug("shiro getActiveSessions. size: {}", sessionResults.size());
return sessionResults;
}

/**
* 创建session, 用户登录
*
* @param session
* @return
*/
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
saveSession(session);
return sessionId;
}

/**
* session读取
*
* @param sessionId
* @return
*/
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
logger.error("id is null!");
return null;
}
logger.debug("doReadSession for path: {}", getPath(sessionId));

Session session = getCachedSession(sessionId);
byte[] byteData = CuratorClient.get(getPath(sessionId));
if (byteData != null && byteData.length > 0) {
session = (Session) SerializeUtils.deserialize(byteData);
if (useMemCache) {
this.cache(session, sessionId);
logger.debug("doReadSession for path: {}, add cached !", getPath(sessionId));
}
return session;
} else {
return null;
}
}

/**
* 生成全路径
*
* @param sessID
* @return
*/
private String getPath(Serializable sessID) {
return shiroSessionZKPath + '/' + sessionPrefix + sessID.toString();
}

/**
* session读取或更新
*
* @param session
*/
private void saveSession(Session session) {
CuratorClient.update(getPath(session.getId()), session);
}

/**
* 因为shiro的缓存为全局的,所以覆盖采用本地二级缓存
* @param cacheManager
*/
@Override
public void setCacheManager(CacheManager cacheManager) {
if(cacheManager instanceof EhCacheManager)
super.setCacheManager(cacheManager);
}
}

**c.**session重写,不必要的参数变化不更新session状态,SessionFactory创建ShiroSession

package com.github.tc.shiro.zookeeper.session;

import org.apache.shiro.session.mgt.SimpleSession;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;
/**
* usedfor:
* Created by javahao on 2017/8/14.
* auth:JavaHao
*/

/**
* 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,
* 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回
*/
public class ShiroSession extends SimpleSession implements Serializable {
private static final long serialVersionUID = -5982055217740470507L;
// 除lastAccessTime以外其他字段发生改变时为true
private boolean isChanged;

public ShiroSession() {
super();
this.setChanged(true);
}

public ShiroSession(String host) {
super(host);
this.setChanged(true);
}

@Override
public void setId(Serializable id) {
super.setId(id);
this.setChanged(true);
}

@Override
public void setStopTimestamp(Date stopTimestamp) {
super.setStopTimestamp(stopTimestamp);
this.setChanged(true);
}

@Override
public void setExpired(boolean expired) {
super.setExpired(expired);
this.setChanged(true);
}

@Override
public void setTimeout(long timeout) {
super.setTimeout(timeout);
this.setChanged(true);
}

@Override
public void setHost(String host) {
super.setHost(host);
this.setChanged(true);
}

@Override
public void setAttributes(Map<Object, Object> attributes) {
super.setAttributes(attributes);
this.setChanged(true);
}

@Override
public void setAttribute(Object key, Object value) {
super.setAttribute(key, value);
this.setChanged(true);
}

@Override
public Object removeAttribute(Object key) {
this.setChanged(true);
return super.removeAttribute(key);
}

/**
* 停止
*/
@Override
public void stop() {
super.stop();
this.setChanged(true);
}

/**
* 设置过期
*/
@Override
protected void expire() {
this.stop();
this.setExpired(true);
}

/**
* 判断下session是否过时
* @return 结果
*/
public boolean outDate(){
try {
return this.isTimedOut();
} catch (Exception e) {
e.printStackTrace();
return true;
}
}

public boolean isChanged() {
return isChanged;
}

public void setChanged(boolean isChanged) {
this.isChanged = isChanged;
}

@Override
public boolean equals(Object obj) {
return super.equals(obj);
}

@Override
protected boolean onEquals(SimpleSession ss) {
return super.onEquals(ss);
}

@Override
public int hashCode() {
return super.hashCode();
}

@Override
public String toString() {
return super.toString();
}
}

SessionFactory

package com.github.tc.shiro.zookeeper.session;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionFactory;

public class ShiroSessionFactory implements SessionFactory {

@Override
public Session createSession(SessionContext initData) {

if (initData != null) {
String host = initData.getHost();
if (host != null) {
return new ShiroSession(host);
}
}

return new ShiroSession();
}
}

**d.**zookeeper管理二级缓存session同步监听

/**
* 开启zookeeper支持
* @param secondLevelCacheManager 二级缓存
* @return 返回zookeeper的客户端curator
*/
@Bean(name = "curatorClient")
@ConditionalOnExpression("${jedis.cloud}")
public CuratorClient startZookeeper(@Qualifier("secondCacheManager")CacheManager secondLevelCacheManager){
EmbedZookeeperServer.start(2181);
List<ZookeeperListener> linsteners = new ArrayList<ZookeeperListener>();
linsteners.add(new RedisCacheListener(CuratorClient.CACHE_PATH,secondLevelCacheManager));
linsteners.add(new ZookeeperSessionListener(secondLevelCacheManager));
CuratorClient.setListeners(linsteners);
CuratorClient.registerListeners();
return new CuratorClient();
}

监听

package com.github.tc.shiro.zookeeper.listener;

import com.github.tc.shiro.tools.SerializeUtils;
import com.github.tc.shiro.zookeeper.session.ShiroZookeeperSessionDao;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_UPDATED;

/**
* usedfor:二级session同步监听
* Created by javahao on 2017/8/15.
* auth:JavaHao
*/
public class ZookeeperSessionListener implements ZookeeperListener {
Logger log = (Logger) LoggerFactory.getLogger(this.getClass());
private String path= ShiroZookeeperSessionDao.SHIRO_SESSION_ZK_ROOT_PATH;

private CacheManager cacheManager;

public ZookeeperSessionListener() {
}
public ZookeeperSessionListener(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public ZookeeperSessionListener(String path, CacheManager cacheManager) {
this.path = path;
this.cacheManager = cacheManager;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public CacheManager getCacheManager() {
return cacheManager;
}

public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}

@Override
public void executor(CuratorFramework client) {
ExecutorService pool = Executors.newCachedThreadPool();
PathChildrenCache childrenCache = new PathChildrenCache(client, getPath(), true);
PathChildrenCacheListener childrenCacheListener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
ChildData child = event.getData();
if(event.getType()==CHILD_UPDATED||event.getType()==CHILD_REMOVED){
byte[] data = child.getData();
Session session = (Session) SerializeUtils.deserialize(data);
log.debug(String.format("【%s】Session发生变化Zookeeper清空节点缓存数据!",session.getId()));
Cache c = null;
if((c=cacheManager.getCache(CachingSessionDAO.ACTIVE_SESSION_CACHE_NAME))!=null
&&c.get(session.getId())!=null)
c.remove(session.getId());
}
}
};
childrenCache.getListenable().addListener(childrenCacheListener,pool);
try {
childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
} catch (Exception e) {
log.error("",e);
}
}
}

#shrio+redis+ehcache缓存共享

**注:**同上ehcache(作为二级缓存),zookeeper监听作二级缓存同步更新,减轻网络传输延时,shiro实现session共享需要重写CacheManager实现

**a.**redis操作类封装

package com.github.tc.shiro.shiroredis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.Iterator;
import java.util.Set;

/**
* usedfor:redis的管理器
* Created by javahao on 2017/7/31.
* auth:JavaHao
*/
public class RedisManager {
// ip和port属性都定义在了properties文件中,这里通过spring的注解方式来直接使用
private String host;
private int port;
private String auth;
// 设置为0的话就是永远都不会过期
private int expire = 0;

// 定义一个管理池,所有的redisManager共同使用。
private static JedisPool jedisPool = null;

public RedisManager() {
}

/**
*
* 初始化方法,在这个方法中通过host和port来初始化jedispool。
*
* */

public void init() {
if (null == host || 0 == port) {
System.out.println("请初始化redis配置文件");
throw new NullPointerException("找不到redis配置");
}
if (jedisPool == null) {
jedisPool = new JedisPool(new JedisPoolConfig(), host, port);

}
}

public Jedis getJedis(){
init();
Jedis jedis = jedisPool.getResource();
if(auth!=null&&auth.length()>0)
jedis.auth(auth);
return jedis;
}

/**
* get value from redis
*
* @param key
* @return
*/
public byte[] get(byte[] key) {
byte[] value = null;
Jedis jedis = getJedis();
try {
value = jedis.get(key);
} finally {
jedisPool.returnResource(jedis);
}
return value;
}

/**
* get value from redis
*
* @param key
* @return
*/
public String get(String key) {
String value = null;
Jedis jedis = getJedis();
try {
value = jedis.get(key);
} finally {
jedisPool.returnResource(jedis);
}
return value;
}

/**
* set
*
* @param key
* @param value
* @return
*/
public byte[] set(byte[] key, byte[] value) {
Jedis jedis = getJedis();
try {
jedis.set(key, value);
if (this.expire != 0) {
jedis.expire(key, this.expire);
}
} finally {
jedisPool.returnResource(jedis);
}
return value;
}

/**
* set
*
* @param key
* @param value
* @return
*/
public String set(String key, String value) {
Jedis jedis = getJedis();
try {
jedis.set(key, value);
if (this.expire != 0) {
jedis.expire(key, this.expire);
}
} finally {
jedisPool.returnResource(jedis);
}
return value;
}

/**
* set
*
* @param key
* @param value
* @param expire
* @return
*/
public byte[] set(byte[] key, byte[] value, int expire) {
Jedis jedis = getJedis();
try {
jedis.set(key, value);
if (expire != 0) {
jedis.expire(key, expire);
}
} finally {
jedisPool.returnResource(jedis);
}
return value;
}

/**
* set
*
* @param key
* @param value
* @param expire
* @return
*/
public String set(String key, String value, int expire) {
Jedis jedis = getJedis();
try {
jedis.set(key, value);
if (expire != 0) {
jedis.expire(key, expire);
}
} finally {
jedisPool.returnResource(jedis);
}
return value;
}

/**
* del
*
* @param key
*/
public void del(byte[] key) {
Jedis jedis = getJedis();
try {
jedis.del(key);
} finally {
jedisPool.returnResource(jedis);
}
}

/**
* del
*
* @param key
*/
public void del(String key) {
Jedis jedis = getJedis();
try {
jedis.del(key);
} finally {
jedisPool.returnResource(jedis);
}
}

/**
* flush
*/
public void flushDB() {
Jedis jedis = getJedis();
try {
jedis.flushDB();
} finally {
jedisPool.returnResource(jedis);
}
}

/**
* size
*/
public Long dbSize() {
Long dbSize = 0L;
Jedis jedis = getJedis();
try {
dbSize = jedis.dbSize();
} finally {
jedisPool.returnResource(jedis);
}
return dbSize;
}

/**
* keys
*
* @param pattern
* @return
*/
public Set<byte[]> keys(String pattern) {
Set<byte[]> keys = null;
Jedis jedis = getJedis();
try {
keys = jedis.keys(pattern.getBytes());
} finally {
jedisPool.returnResource(jedis);
}
return keys;
}

public void dels(String pattern) {
Set<byte[]> keys = null;
Jedis jedis = getJedis();
try {
keys = jedis.keys(pattern.getBytes());
Iterator<byte[]> ito = keys.iterator();
while (ito.hasNext()) {
jedis.del(ito.next());
}
} finally {
jedisPool.returnResource(jedis);
}
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public int getExpire() {
return expire;
}

public void setExpire(int expire) {
this.expire = expire;
}

public String getAuth() {
return auth;
}

public void setAuth(String auth) {
this.auth = auth;
}
}

**b.**缓存仓库,方便以后扩展

package com.github.tc.shiro.shiroredis;

import org.apache.shiro.cache.Cache;

/**
* usedfor:缓存仓库接口
* Created by javahao on 2017/7/31.
* auth:JavaHao
*/
public interface ShiroCacheRepository {
<K, V> Cache<K, V> getCache(String name);

void destroy();
}

**c.**实现

package com.github.tc.shiro.shiroredis;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;

/**
* usedfor:缓存仓库接口实现
* Created by javahao on 2017/7/31.
* auth:JavaHao
*/
public class ShiroRedisCacheRepository implements ShiroCacheRepository {

private RedisManager redisManager;
private CacheManager secondLevelCacheManager;

public void setSecondLevelCacheManager(CacheManager secondLevelCacheManager) {
this.secondLevelCacheManager = secondLevelCacheManager;
}

public RedisManager getRedisManager() {
return redisManager;
}

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

@Override
public <K, V> Cache<K, V> getCache(String name) {
return new ShiroRedisCache<K, V>(redisManager, name,secondLevelCacheManager.getCache(name));
}

@Override
public void destroy() {
redisManager.init();
redisManager.flushDB();
}

}

**d.**CacheManager重写

package com.github.tc.shiro.shiroredis;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.util.Destroyable;

/**
* usedfor:cache管理容器
* Created by javahao on 2017/7/31.
* auth:JavaHao
*/
public class ShiroRedisCacheManager implements CacheManager, Destroyable {

private ShiroCacheRepository shiroCacheRepository;

public ShiroCacheRepository getShiroCacheRepository() {
return shiroCacheRepository;
}

public void setShiroCacheRepository(ShiroCacheRepository shiroCacheRepository) {
this.shiroCacheRepository = shiroCacheRepository;
}

@Override
public void destroy() throws Exception {
getShiroCacheRepository().destroy();
}

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

}

**e.**重点,缓存管理实现

package com.github.tc.shiro.shiroredis;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.github.tc.shiro.tools.SerializeUtils;
import com.github.tc.shiro.zookeeper.CuratorClient;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* usedfor:cache管理的实现
* Created by javahao on 2017/7/31.
* auth:JavaHao
*/
public class ShiroRedisCache<K,V> implements Cache<K,V> {
private Logger logger = LoggerFactory.getLogger(this.getClass());

/**
* The wrapped Jedis instance.
*/
private RedisManager cache;

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

/**
* 二级缓存
*/
private Cache secondLevelCache;

/**
* Returns the Redis session keys
* prefix.
* @return The prefix
*/
public String getKeyPrefix() {
return keyPrefix;
}

/**
* Sets the Redis sessions key
* prefix.
* @param keyPrefix The prefix
*/
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

/**
* 通过一个JedisManager实例构造RedisCache
*/
public ShiroRedisCache(RedisManager cache){
if (cache == null) {
throw new IllegalArgumentException("Cache argument cannot be null.");
}
this.cache = cache;
}

/**
* Constructs a cache instance with the specified
* Redis manager and using a custom key prefix.
* @param cache The cache manager instance
* @param prefix The Redis key prefix
*/
public ShiroRedisCache(RedisManager cache,
String prefix,Cache secondLevelCache){

this( cache );

// set the prefix
this.keyPrefix = prefix;
this.secondLevelCache=secondLevelCache;
}

/**
* 获得byte[]型的key
* @param key
* @return
*/
private byte[] getByteKey(K key){
if(key instanceof String){
String preKey = this.keyPrefix + key;
return preKey.getBytes();
}else{
return SerializeUtils.serialize(key);
}
}

@Override
public V get(K key) throws CacheException {
if(secondLevelCache.get(key)!=null)
return (V) secondLevelCache.get(key);
logger.debug("根据key从Redis中获取对象 key [" + key + "]");
try {
if (key == null) {
return null;
}else{
byte[] rawValue = cache.get(getByteKey(key));
@SuppressWarnings("unchecked")
V value = (V) SerializeUtils.deserialize(rawValue);
secondLevelCache.put(key,value);
return value;
}
} catch (Throwable t) {
throw new CacheException(t);
}

}

@Override
public V put(K key, V value) throws CacheException {
CuratorClient.clearNodesCache(new ClearCacheVo(keyPrefix, (String) key));
logger.debug(String.format("将缓存{key:%s,value:%s}存储到Redis中。",key,value));
try {
cache.set(getByteKey(key), SerializeUtils.serialize(value));
return value;
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public V remove(K key) throws CacheException {
CuratorClient.clearNodesCache(new ClearCacheVo(keyPrefix, (String) key));
logger.debug(String.format("按照缓存{key:%s}从Redis删除缓存!",key));
try {
V previous = get(key);
cache.del(getByteKey(key));
return previous;
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public void clear() throws CacheException {
logger.debug("清除Redis中所有缓存!");
try {
cache.flushDB();
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public int size() {
try {
Long longSize = new Long(cache.dbSize());
return longSize.intValue();
} catch (Throwable t) {
throw new CacheException(t);
}
}

@SuppressWarnings("unchecked")
@Override
public Set<K> keys() {
try {
Set<byte[]> keys = cache.keys(this.keyPrefix + "*");
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
}else{
Set<K> newKeys = new HashSet<K>();
for(byte[] key:keys){
newKeys.add((K)key);
}
return newKeys;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public Collection<V> values() {
try {
Set<byte[]> keys = cache.keys(this.keyPrefix + "*");
if (!CollectionUtils.isEmpty(keys)) {
List<V> values = new ArrayList<V>(keys.size());
for (byte[] key : keys) {
@SuppressWarnings("unchecked")
V value = get((K)key);
if (value != null) {
values.add(value);
}
}
return Collections.unmodifiableList(values);
} else {
return Collections.emptyList();
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
}

**f.**二级缓存同步更新监听

package com.github.tc.shiro.zookeeper.listener;

import com.github.tc.shiro.shiroredis.ClearCacheVo;
import com.github.tc.shiro.tools.SerializeUtils;
import com.github.tc.shiro.zookeeper.CuratorClient;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_UPDATED;

/**
* usedfor:二级缓存同步监听
* Created by javahao on 2017/8/15.
* auth:JavaHao
*/
public class RedisCacheListener implements ZookeeperListener {
//获取logback实例
Logger log = (Logger) LoggerFactory.getLogger(this.getClass());
private String path= CuratorClient.CACHE_PATH;

private CacheManager cacheManager;

public RedisCacheListener() {
}

public RedisCacheListener(String path, CacheManager cacheManager) {
this.path = path;
this.cacheManager = cacheManager;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public CacheManager getCacheManager() {
return cacheManager;
}

public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}

@Override
public void executor(CuratorFramework client) {
ExecutorService pool = Executors.newCachedThreadPool();
PathChildrenCache childrenCache = new PathChildrenCache(client, CuratorClient.CACHE_PATH, true);
PathChildrenCacheListener childrenCacheListener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
ChildData child = event.getData();
if(event.getType()==CHILD_ADDED||event.getType()==CHILD_UPDATED){
byte[] data = child.getData();
ClearCacheVo vo = (ClearCacheVo) SerializeUtils.deserialize(data);
log.debug(String.format("【%s】缓存发生变化Zookeeper清空节点缓存数据!",vo.getCacheKey()));
Cache c = null;
if((c=cacheManager.getCache(vo.getCacheName()))!=null
&&c.get(vo.getCacheKey())!=null)
c.remove(vo.getCacheKey());
}
}
};
childrenCache.getListenable().addListener(childrenCacheListener,pool);
try {
childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
} catch (Exception e) {
log.error("",e);
}
}
}

**g.**vo类

package com.github.tc.shiro.shiroredis;

import java.io.Serializable;

/**
* usedfor:清除缓存vo对象
* Created by javahao on 2017/8/15.
* auth:JavaHao
*/
public class ClearCacheVo implements Serializable {
private String cacheName;
private String cacheKey;

public ClearCacheVo() {
}

public ClearCacheVo(String cacheName, String cacheKey) {
this.cacheName = cacheName;
this.cacheKey = cacheKey;
}

public String getCacheName() {
return cacheName;
}

public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}

public String getCacheKey() {
return cacheKey;
}

public void setCacheKey(String cacheKey) {
this.cacheKey = cacheKey;
}

@Override
public String toString() {
return "ClearCacheVo{" +
"cacheName='" + cacheName + '\'' +
", cacheKey='" + cacheKey + '\'' +
'}';
}
}


#到这里shiro的session共享以及缓存共享已经完成,最后再附赠上我的springboot配置类。

package com.github.tc.shiro.shiroredis.manager;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* usedfor:jedis的配置属性
* Created by javahao on 2017/8/3.
* auth:JavaHao
*/
@ConfigurationProperties(prefix = JedisProperties.JEDIS_PREFIX,ignoreUnknownFields = true)
public class JedisProperties {

public static final String JEDIS_PREFIX = "jedis";

private boolean cloud;

private String host;

private String auth;

private int port;

private int maxTotal;

private int maxIdle;

private int maxWaitMillis;

public boolean isCloud() {
return cloud;
}

public void setCloud(boolean cloud) {
this.cloud = cloud;
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public String getAuth() {
return auth;
}

public void setAuth(String auth) {
this.auth = auth;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public int getMaxTotal() {
return maxTotal;
}

public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}

public int getMaxIdle() {
return maxIdle;
}

public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}

public int getMaxWaitMillis() {
return maxWaitMillis;
}

public void setMaxWaitMillis(int maxWaitMillis) {
this.maxWaitMillis = maxWaitMillis;
}

}

package com.github.tc.shiro.shiroredis.manager;

import com.github.tc.shiro.config.ShiroProperties;
import com.github.tc.shiro.shiroredis.*;
import com.github.tc.shiro.tools.IDUtil;
import com.github.tc.shiro.zookeeper.CuratorClient;
import com.github.tc.shiro.zookeeper.EmbedZookeeperServer;
import com.github.tc.shiro.zookeeper.listener.RedisCacheListener;
import com.github.tc.shiro.zookeeper.listener.ZookeeperListener;
import com.github.tc.shiro.zookeeper.listener.ZookeeperSessionListener;
import com.github.tc.shiro.zookeeper.session.ShiroZookeeperSessionDao;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
* usedfor:shiro的redis以及cache在spring集群或者单机模式中依赖的实例管理器
* Created by javahao on 2017/8/1.
* auth:JavaHao
*/
@Configuration
@EnableConfigurationProperties({JedisProperties.class})
public class ShiroRedisAutoiredConfig {
@Autowired
private JedisProperties jedisProperties;
@Autowired
private ShiroProperties shiroProperties;

@Bean
@ConditionalOnExpression("${jedis.cloud}")
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost(jedisProperties.getHost());
redisManager.setPort(jedisProperties.getPort());
redisManager.setAuth(jedisProperties.getAuth());
return redisManager;
}

@Bean
@ConditionalOnExpression("${jedis.cloud}")
public ShiroRedisCacheRepository shiroRedisCacheRepository(RedisManager redisManager,
@Qualifier("secondCacheManager")EhCacheManager cacheManager){
ShiroRedisCacheRepository shiroRedisCacheRepository = new ShiroRedisCacheRepository();
shiroRedisCacheRepository.setRedisManager(redisManager);
shiroRedisCacheRepository.setSecondLevelCacheManager(cacheManager);
return shiroRedisCacheRepository;
}

/**
* 开启集群的cachemanager
* @param shiroRedisCacheRepository 缓存仓库
* @return 返回实例
*/
@Bean(name = "cacheManager")
@ConditionalOnExpression("${jedis.cloud}")
public ShiroRedisCacheManager cloudCacheManager(ShiroRedisCacheRepository shiroRedisCacheRepository){
ShiroRedisCacheManager shiroRedisCacheManager = new ShiroRedisCacheManager();
shiroRedisCacheManager.setShiroCacheRepository(shiroRedisCacheRepository);
return shiroRedisCacheManager;
}

/**
* 标准单机模式
* @return 返回实例
*/
@Bean(name = "cacheManager")
@ConditionalOnExpression("!${jedis.cloud}")
public EhCacheManager standCacheManager(){
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em;
}
@Bean
@ConditionalOnExpression("${jedis.cloud}")
public void putCacheMangeInSessionManager(@Qualifier("sessionManager")DefaultSessionManager sessionManager,
@Qualifier("secondCacheManager")EhCacheManager cacheManager){
sessionManager.setCacheManager(cacheManager);
}
/**
* 如果是集群缓存,采用本地二级缓存
*/
@Bean(name = "secondCacheManager")
@ConditionalOnExpression("${jedis.cloud}")
public EhCacheManager secondCacheManager(){
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em;
}
/**
* 集群环境sessiondao
* @param idUtil session生成器
* @return 返回实例
*/
@Bean(name = "sessionDAO")
@ConditionalOnExpression("${jedis.cloud}")
public ShiroZookeeperSessionDao CloudSessionDAO(IDUtil idUtil){
ShiroZookeeperSessionDao sessionDao = new ShiroZookeeperSessionDao();
sessionDao.setSessionIdGenerator(idUtil);
return sessionDao;
}
@Bean
public IDUtil sessionIdGenerator(){
return new IDUtil();
}

/**
* 标准单机模式
* @param idUtil sessionid生成器
* @return sessionDao实例
*/
@Bean(name = "sessionDAO")
@ConditionalOnExpression("!${jedis.cloud}")
public EnterpriseCacheSessionDAO StandSessionDAO(IDUtil idUtil){
EnterpriseCacheSessionDAO enterpriseCacheSessionDAO = new EnterpriseCacheSessionDAO();
enterpriseCacheSessionDAO.setSessionIdGenerator(idUtil);
enterpriseCacheSessionDAO.setActiveSessionsCacheName(shiroProperties.getActiveSessionsCacheName());
return enterpriseCacheSessionDAO;
}

/** * 开启zookeeper支持 * @param secondLevelCacheManager 二级缓存 * @return 返回zookeeper的客户端curator */ @Bean(name = "curatorClient") @ConditionalOnExpression("${jedis.cloud}") public CuratorClient startZookeeper(@Qualifier("secondCacheManager")CacheManager secondLevelCacheManager){ EmbedZookeeperServer.start(2181); List<ZookeeperListener> linsteners = new ArrayList<ZookeeperListener>(); linsteners.add(new RedisCacheListener(CuratorClient.CACHE_PATH,secondLevelCacheManager)); linsteners.add(new ZookeeperSessionListener(secondLevelCacheManager)); CuratorClient.setListeners(linsteners); CuratorClient.registerListeners(); return new CuratorClient(); }}



#联系方式
AUTH:JavaHao
QQ:353475081
QQ群:424890813
Email:353475081@qq.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐