接前文缺陷点
- 本地探针应该增加计数器,多次异常再设置,避免网络波动造成误判。
- 耦合度过高,远端缓存和本地缓存应该平行关系被设计为上下游关系了。
- 公用的远端缓存的操作方法应该私有化,避免集成方代码误操作,导致受到攻击。
- 探针改为轮训请求,类似jedis底层心跳检测。
- 抽象多层策略,提供集成方自定义实现配置来决定采用什么方式进行降级,缓存工厂或者控制器来决定Redis是否异常和异常后走本地缓存还是数据库还是zk等策略。
- 底层应该做好职责分离,异常往上抛,由上层根据用户配置的策略做对应的处理逻辑。
- 业务层采用模板模式,对Redis降级后的业务逻辑提供有结构性的模板方法,并要支持用户灵活重写,支持重写的方法名不要取的具体,应取的抽象。
public interface IWecareCache {
/**
* 根据key,获取到对应的value值
* 注: 若key不存在, 则返回null
*
* @param key key-value对应的key
* @return 该key对应的值。
*/
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 {
/**
* 本地缓存记录str的key
*/
private static final String STRING_KEY = "StringKey@";
private final Cache<String, Map<String, String>> wecareLocalCache = StaticSingletonFactory.getLocalCacheSingleton();
@Override
public String get(String key) {
// 先判断是否key对应的map是否存在
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);
}
。。。
}
public interface ICacheStrategy {
// 根据策略自动选择具体缓存实现的方法
IWecareCache getCacheByStrategy();
// 根据指定缓存名称获取缓存实现的方法
IWecareCache getCacheByName(String cacheName);
// 查询redis运行状态是否OK
boolean isRedisOK();
// 设置redis状态为正常
void setRedisAvailable();
// 设置redis状态为异常
void setRedisNotAvailable();
}
// SDK缓存策略默认实现,根据redis监听状态进行远端缓存和本地缓存的热切换
@Component(WecareSsoConstant.DEFAULT_CACHE_STRATEGY)
public class DefaultCacheStrategy implements ICacheStrategy {
// redis是否可用--策略读取,监控更新
private final AtomicBoolean redisIsLive = new AtomicBoolean(false);
// 可用缓存工具-自动注入
@Autowired
private Map<String, IWecareCache> cacheMap;
//默认缓存策略-如果redis异常则使用jvm缓存
//--如果不需要降级,请实现自定义策略始终返回redis缓存即可
@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();
}
// 默认redis状态实现-通过AtomicBoolean控制
@Override
public void setRedisAvailable() {
redisIsLive.set(true);
}
// 默认redis状态实现-通过AtomicBoolean控制
@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);
}
}
}
public class WecareCacheUtil {
private static final ICacheStrategy cacheStrategy = StaticSingletonFactory.getCacheStrategy();
// 根据策略获取缓存实现
private static IWecareCache getCacheByStrategy() {
return cacheStrategy.getCacheByStrategy();
}
// 根据缓存名称获取指定缓存实现--默认拥有REDIS和JVM两个
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);
}
// 根据名称操作指定缓存实现,业务需要在正常流程时预先在jvm存储用户信息。
public static String get(String key, String cacheName) {
return getCacheByName(cacheName).get(key);
}
。。。
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();
// redis集群为6主多备,因此检测到任意一个主节点宕机就算集群failed,集群failed超过阈值则设置redis集群状态不可用,此方法是否可监控连接数满导致的异常还有待测试
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++;
}
}
}
}
// 监控线程类:提供启动redis监控的方法
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();
}
}
@Component
public class StaticSingletonFactory implements ApplicationContextAware {
// 缓存策略实现
private static Map<String, ICacheStrategy> cacheStrategyMap;
// redis监控实现
private static Map<String, IRedisMonitor> redisMonitorMap;
private static JedisCluster jedis;
// 本地缓存
private static Cache<String, Map<String, String>> wecareSsoLocalCache;
// 配置参数
private static ConfigProperty configProperty;
// 静态工厂bean初始化--赋值顺序要按照使用顺序,且代码避免循环依赖
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
configProperty = applicationContext.getBean(ConfigProperty.class);
// 策略集合赋值
cacheStrategyMap = applicationContext.getBeansOfType(ICacheStrategy.class);
if (configProperty.isRedisEnabled()) {
// 确认开启使用redis功能
jedis = applicationContext.getBean("sdkJedisClient", JedisCluster.class);
// 监控集合赋值
redisMonitorMap = applicationContext.getBeansOfType(IRedisMonitor.class);
// 在赋值jedis客户端后启动监听线程
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);
}
// 获取redis监控实现
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);
}
}