缓存降级设计思想
1.创建缓存操作接口定义缓存数据的增删改查方法,底层由redis工具类实现作为一级缓存,另一个实现可以选择本地缓存工具类或第三方数据存储工具类实现,作为二级缓存在redis降级时使用。
public interface IWecareCache {
String get(String key);
。。。
}
@Component(WecareSsoConstant.CACHE_TYPE_REDIS)
public class WecareRedisCache implements IWecareCache {
private final JedisCluster jedis = StaticSingletonFactory.getJedisSingleton();
@Override
public String get(String key) {
log.debug("[JedisCluster]: get -> key={}", key);
return jedis.get(key);
}
。。。
}
@Component(WecareSsoConstant.CACHE_TYPE_LOCAL)
public class WecareLocalCache implements IWecareCache {
private static final String STRING_KEY = "StringKey@";
private final Cache<String, Map<String, String>> wecareLocalCache = StaticSingletonFactory.getLocalCacheSingleton();
@Override
public String get(String key) {
Map<String, String> map = wecareLocalCache.getIfPresent(key);
if (CollectionUtils.isEmpty(map)) {
return null;
}
log.debug("[localCache]: get -> key={},value={}", key, map.get(STRING_KEY));
return map.get(STRING_KEY);
}
。。。
}
2.创建缓存策略接口用于实现根据策略选择获取什么缓存核心进行操作,并提供一个默认策略实现,再在工厂中提供热加载方法,如果存在自定义实现则优先获取自定义策略实现
public interface ICacheStrategy {
IWecareCache getCacheByStrategy();
IWecareCache getCacheByName(String cacheName);
boolean isRedisOK();
void setRedisAvailable();
void setRedisNotAvailable();
}
@Component(WecareSsoConstant.DEFAULT_CACHE_STRATEGY)
public class DefaultCacheStrategy implements ICacheStrategy {
private final AtomicBoolean redisIsLive = new AtomicBoolean(false);
@Autowired
private Map<String, IWecareCache> cacheMap;
@Override
public IWecareCache getCacheByStrategy() {
IWecareCache cacheByDefaultStrategy = getCacheByDefaultStrategy();
if (cacheByDefaultStrategy == null) {
log.error("no config cache");
throw new BizException("no config cache");
}
return cacheByDefaultStrategy;
}
@Override
public IWecareCache getCacheByName(String cacheName) {
return cacheMap.get(cacheName);
}
@Override
public boolean isRedisOK() {
return redisIsLive.get();
}
@Override
public void setRedisAvailable() {
redisIsLive.set(true);
}
@Override
public void setRedisNotAvailable() {
redisIsLive.set(false);
}
private IWecareCache getCacheByDefaultStrategy() {
if (redisIsLive.get()) {
return cacheMap.get(WecareSsoConstant.CACHE_TYPE_REDIS);
} else {
return cacheMap.get(WecareSsoConstant.CACHE_TYPE_LOCAL);
}
}
}
3.创建缓存工具类,提供缓存操作方法,每次操作缓存都通过策略获取对应的缓存核心进行操作,实现热降级
public class WecareCacheUtil {
private static final ICacheStrategy cacheStrategy = StaticSingletonFactory.getCacheStrategy();
private static IWecareCache getCacheByStrategy() {
return cacheStrategy.getCacheByStrategy();
}
private static IWecareCache getCacheByName(String cacheName) {
return cacheStrategy.getCacheByName(cacheName);
}
public static boolean isRedisOK(){
return cacheStrategy.isRedisOK();
}
public static String get(String key) {
return getCacheByStrategy().get(key);
}
public static String get(String key, String cacheName) {
return getCacheByName(cacheName).get(key);
}
。。。
4.创建redis监控接口用于定义redis的监控方式,提供默认实现并同策略接口一样支持自定义实现的热加载,创建执行监控的线程类
public interface IRedisMonitor {
void healthCheck();
}
@Component(WecareSsoConstant.DEFAULT_REDIS_MONITOR)
public class DefaultRedisMonitor implements IRedisMonitor {
private static final int MAX_ERROR_COUNT = 2;
private static final int HEART_BEAT_FREQUENCY = 5000;
private final JedisCluster jedis = StaticSingletonFactory.getJedisSingleton();
private final ICacheStrategy cacheStrategy = StaticSingletonFactory.getCacheStrategy();
private boolean isAllNodesActivated() {
try {
Map<String, JedisPool> clusterNodes = jedis.getClusterNodes();
for (Map.Entry<String, JedisPool> entry : clusterNodes.entrySet()) {
JedisPool pool = entry.getValue();
String clusterInfo;
try (Jedis jedisResource = pool.getResource()) {
clusterInfo = jedisResource.clusterInfo();
}
if (!clusterInfo.contains("cluster_state:ok")) {
log.error("redis node:{} cluster_state:fail", entry.getKey());
log.error("clusterInfo:{}", clusterInfo);
return false;
}
}
return true;
}catch (JedisException jedisException){
log.error("redis 读取节点信息异常 jedisException:",jedisException);
}catch (Exception exception){
try {
jedis.set("testHealthCheck","true");
String testHealthCheck = jedis.get("testHealthCheck");
if ("true".equals(testHealthCheck)) {
return true;
}
}catch (Exception e){
log.error("redis 操作测试异常 exception:",e);
}
log.error("redis 读取节点信息异常 exception:",exception);
}
return false;
}
@Override
public void healthCheck() {
int threadException = 0;
int redisErrorCount = 0;
LocalDateTime lastLogTime = LocalDateTime.now().minusMinutes(1);
while (true) {
try {
Thread.sleep(HEART_BEAT_FREQUENCY);
if (isAllNodesActivated()) {
redisErrorCount = 0;
cacheStrategy.setRedisAvailable();
LocalDateTime now = LocalDateTime.now();
if (Duration.between(lastLogTime,now).toMinutes()>0) {
log.info("Redis Cluster Nodes Health Check OK");
lastLogTime = now;
}
} else {
redisErrorCount++;
if (redisErrorCount >= MAX_ERROR_COUNT) {
redisErrorCount = 0;
cacheStrategy.setRedisNotAvailable();
log.info("Redis Cluster Nodes Health Check Failed!!!");
}
}
} catch (InterruptedException interruptedException) {
log.error("redis监控线程休眠异常!", interruptedException);
if (threadException > 3) {
log.error("redis监控线程因休眠异常强制终止!", interruptedException);
break;
}
threadException++;
}
}
}
}
public class RedisMonitorThread implements Runnable {
private final IRedisMonitor monitor;
public RedisMonitorThread(IRedisMonitor monitor) {
this.monitor = monitor;
}
@Override
public void run() {
monitor.healthCheck();
}
public static void startMonitor(IRedisMonitor monitor) {
new Thread(new RedisMonitorThread(monitor),monitor.getClass().getSimpleName()+"-Thread").start();
}
}
5.创建单例转静态对象的工厂,用于将spring管理的动态单例转换为静态单例,全局提供静态方法,并定义线程初始化和类加载
@Component
public class StaticSingletonFactory implements ApplicationContextAware {
private static Map<String, ICacheStrategy> cacheStrategyMap;
private static Map<String, IRedisMonitor> redisMonitorMap;
private static JedisCluster jedis;
private static Cache<String, Map<String, String>> wecareSsoLocalCache;
private static ConfigProperty configProperty;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
configProperty = applicationContext.getBean(ConfigProperty.class);
cacheStrategyMap = applicationContext.getBeansOfType(ICacheStrategy.class);
if (configProperty.isRedisEnabled()) {
jedis = applicationContext.getBean("sdkJedisClient", JedisCluster.class);
redisMonitorMap = applicationContext.getBeansOfType(IRedisMonitor.class);
RedisMonitorThread.startMonitor(getRedisMonitor());
}
wecareSsoLocalCache = (Cache<String, Map<String, String>>) applicationContext.getBean("wecareSsoLocalCache");
}
public static JedisCluster getJedisSingleton() {
return jedis;
}
public static Cache<String, Map<String, String>> getLocalCacheSingleton() {
return wecareSsoLocalCache;
}
public static ConfigProperty getConfigProperty() {
return configProperty;
}
public static ICacheStrategy getCacheStrategy(String cacheStrategyName) {
ICacheStrategy iCacheStrategy = cacheStrategyMap.get(cacheStrategyName);
if (iCacheStrategy == null) {
throw new BizException(
"Select CacheStrategy Error, cacheStrategyName " + cacheStrategyName + " undefined !");
}
return iCacheStrategy;
}
public static ICacheStrategy getCacheStrategy() {
return hotLoading(cacheStrategyMap, WecareSsoConstant.DEFAULT_CACHE_STRATEGY, ICacheStrategy.class);
}
public static IRedisMonitor getRedisMonitor() {
return hotLoading(redisMonitorMap, WecareSsoConstant.DEFAULT_REDIS_MONITOR, IRedisMonitor.class);
}
private static <T> T hotLoading(Map<String, T> map, String defaultName, Class<T> obj) {
String className = obj.getSimpleName();
int size = map.size();
switch (size) {
case 0:
throw new BizException(className + " init Error, no implements !");
case 1:
return map.get(defaultName);
case 2:
for (Map.Entry<String, T> entry : map.entrySet()) {
if (!defaultName.equals(entry.getKey())) {
return entry.getValue();
}
}
break;
default:
break;
}
throw new BizException("Select " + className + " Error, expected 1 but found " + size);
}
}