springboot/springmvc+shiro+zookeeper作session共享+redis作缓存共享
2017-08-17 00:00
288 查看
摘要: shiro-session共享缓存共享,zookeeper作session共享,redis作缓存共享,springboot/springmvc的session共享和缓存共享,服务器节点二级本地缓存
**理解.**说到session共享,有了解过的开发人员应该都会想到应用场景是在集群环境中,是为了保证用户登录或者访问集群系统中,在各个应用节点的生命周期保持一致性。
集群方式
实现方式
以上三种实现方式缓存共享都还在redis中,因为考虑到缓存数据可能很大,zookeeper不适合存储大数据,前两种实现方式有兴趣的“童鞋”可以自行去尝试,我只介绍一下第三种实现方式的细节springboot+shiro+zookeeper+redis+ehcache
#shiro+zookeeper-session共享
**shiro-session共享:**需要重写sessionDao的实现,将session的CRUD存储到zookeeper中,代码如下:
a. zookeeper操作的客户端封装:
**b.**sessionDao实现重写到zookeeper
**c.**session重写,不必要的参数变化不更新session状态,SessionFactory创建ShiroSession
SessionFactory
**d.**zookeeper管理二级缓存session同步监听
监听
#shrio+redis+ehcache缓存共享
**注:**同上ehcache(作为二级缓存),zookeeper监听作二级缓存同步更新,减轻网络传输延时,shiro实现session共享需要重写CacheManager实现
**a.**redis操作类封装
**b.**缓存仓库,方便以后扩展
**c.**实现
**d.**CacheManager重写
**e.**重点,缓存管理实现
**f.**二级缓存同步更新监听
**g.**vo类
#到这里shiro的session共享以及缓存共享已经完成,最后再附赠上我的springboot配置类。
#联系方式
AUTH:JavaHao
QQ:353475081
QQ群:424890813
Email:353475081@qq.com
**理解.**说到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
相关文章推荐
- jeesz分布式企业框架 javaWeb分布式架构 springmvc+mybatis+shiro dubbo zookeeper redis kafka app服务
- SpringBoot+Redis+Nginx实现负载均衡以及Session缓存共享
- 精华【分布式微服务云架构dubbo+zookeeper+springmvc+mybatis+shiro+redis】分布式大型互联网企业架构!
- Spring Boot系列(十五) 安全框架Apache Shiro(二)缓存-EhCache
- J2EE通用后台管理系统 springmvc+mybatis+nginx+shiro+redis架构的session共享
- Spring boot+Shiro+ spring MVC+swagger UI +Mybatis+mysql+Vue +Element UI 之四 vue 基本知识点概述
- Spring boot+Shiro+ spring MVC+swagger UI +Mybatis+mysql+Vue +Element UI 之四 vue 整合Element UI
- 【推荐】微服务分布式企业框架Springmvc+mybatis+shiro+Dubbo+ZooKeeper+Redis
- Spring Boot Shiro 权限信息缓存处理,记住我,thymleaf使用shiro标签
- 分布式架构真正适用于大型互联网项目的架构! dubbo+zookeeper+springmvc+mybatis+shiro+redis
- 分布式微服务云架构dubbo+zookeeper+springmvc+mybatis+shiro
- 精华【分布式微服务云架构dubbo+zookeeper+springmvc+mybatis+shiro+redis】分布式大型互联网企业架构!
- Springmvc+mybatis+shiro+Dubbo+ZooKeeper+Redis+KafKa j2ee分布式架构
- Spring Boot Shiro 权限信息缓存处理,记住我,thymleaf使用shiro标签
- 【分享】微服务分布式企业框架 Springmvc+mybatis+shiro+Dubbo+ZooKeeper+Redis+KafKa
- Spring boot +spring mvc+shiro 登录验证demo
- 【推荐】微服务大型分布式企业框架 Springmvc+mybatis+shiro+Dubbo+ZooKeeper+Redis+KafKa
- 分布式架构真正适用于大型互联网项目的架构! dubbo+zookeeper+springmvc+mybatis+shiro+redis
- 分布式 dubbo zookeeper springmvc mybatis shiro restful redis fastdfs activemq
- spring mvc+Mybatis整合shiro 第五章 缓存