缓存降级代码结构设计


缓存降级设计思想

接前文缺陷点

  1. 本地探针应该增加计数器,多次异常再设置,避免网络波动造成误判。
  2. 耦合度过高,远端缓存和本地缓存应该平行关系被设计为上下游关系了。
  3. 公用的远端缓存的操作方法应该私有化,避免集成方代码误操作,导致受到攻击。
  4. 探针改为轮训请求,类似jedis底层心跳检测。
  5. 抽象多层策略,提供集成方自定义实现配置来决定采用什么方式进行降级,缓存工厂或者控制器来决定Redis是否异常和异常后走本地缓存还是数据库还是zk等策略。
  6. 底层应该做好职责分离,异常往上抛,由上层根据用户配置的策略做对应的处理逻辑。
  7. 业务层采用模板模式,对Redis降级后的业务逻辑提供有结构性的模板方法,并要支持用户灵活重写,支持重写的方法名不要取的具体,应取的抽象。
1.创建缓存操作接口定义缓存数据的增删改查方法,底层由redis工具类实现作为一级缓存,另一个实现可以选择本地缓存工具类或第三方数据存储工具类实现,作为二级缓存在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();@Overridepublic 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();@Overridepublic 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);}。。。
}

2.创建缓存策略接口用于实现根据策略选择获取什么缓存核心进行操作,并提供一个默认策略实现,再在工厂中提供热加载方法,如果存在自定义实现则优先获取自定义策略实现
public interface ICacheStrategy {// 根据策略自动选择具体缓存实现的方法IWecareCache getCacheByStrategy();// 根据指定缓存名称获取缓存实现的方法IWecareCache getCacheByName(String cacheName);// 查询redis运行状态是否OKboolean 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);// 可用缓存工具-自动注入@Autowiredprivate Map<String, IWecareCache> cacheMap;//默认缓存策略-如果redis异常则使用jvm缓存//--如果不需要降级,请实现自定义策略始终返回redis缓存即可@Overridepublic IWecareCache getCacheByStrategy() {IWecareCache cacheByDefaultStrategy = getCacheByDefaultStrategy();if (cacheByDefaultStrategy == null) {log.error("no config cache");throw new BizException("no config cache");}return cacheByDefaultStrategy;}@Overridepublic IWecareCache getCacheByName(String cacheName) {return cacheMap.get(cacheName);}@Overridepublic boolean isRedisOK() {return redisIsLive.get();}// 默认redis状态实现-通过AtomicBoolean控制@Overridepublic void setRedisAvailable() {redisIsLive.set(true);}// 默认redis状态实现-通过AtomicBoolean控制@Overridepublic 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();}// 根据缓存名称获取指定缓存实现--默认拥有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);}。。。

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();// 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;}@Overridepublic 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;}@Overridepublic 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;// redis监控实现private static Map<String, IRedisMonitor> redisMonitorMap;private static JedisCluster jedis;// 本地缓存private static Cache<String, Map<String, String>> wecareSsoLocalCache;// 配置参数private static ConfigProperty configProperty;//  静态工厂bean初始化--赋值顺序要按照使用顺序,且代码避免循环依赖@Overridepublic 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);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/104775.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

lvgl 界面管理器

lv_scr_mgr lvgl 界面管理器 适配 lvgl 8.3 降低界面之间的耦合使用较小的内存&#xff0c;界面切换后会自动释放内存内存泄漏检测 使用方法 在lv_scr_mgr_port.h 中创建一个枚举&#xff0c;用于界面ID为每个界面创建一个页面管理器句柄将界面句柄添加到 lv_scr_mgr_por…

机器学习基础之《回归与聚类算法(2)—欠拟合与过拟合》

一、背景 1、上一篇说正规方程的时候&#xff0c;实际情况中使用很少&#xff0c;主要原因它不能解决过拟合。 2、训练集上表现的好&#xff0c;测试集上表现不好—过拟合 二、欠拟合和过拟合 1、欠拟合 训练集&#xff1a;有3个训练集&#xff0c;告诉机器都是天鹅 机器学…

Docker 构建Python镜像时,pip使用国内地址的dockerfile模版

一、问题现象 构建镜像时&#xff0c;使用pip命令打包报错&#xff1a; 二、问题根因 因国内无法访问pip的配置文件中的仓库地址 三、解决办法 这个办法同样适用于&#xff1a;物理机&#xff0c;这个地址是阿里云的 pip config set global.index-url http://mirrors.aliy…

WebAPI+EF连接SQL Server数据库

右击解决方案-添加-新建项目-选择“类库&#xff08;.NET Framework&#xff09;”,新建的项目取名叫WebApi1.EF 添加EF&#xff1a; 新建一个ADO实体数据模型 选择DBFirst 数据源选择MySql 填写数据库地址及账号密码 选择实体框架版本 选择在数据库中的表User 到此配置完成&am…

稚晖君项目复刻:L-ink门禁卡(1)——环境搭建与第一个项目创建

行文目录 前言其他文章正文开始STM32CubeMX安装STM32CubeMX安装L0的固件支持包Clion安装OpenOCD安装MinGw安装arm-none-eabi-gcc安装Clion配置 创建STM32工程创建STM32CubeMX工程更改芯片型号 参考文献 前言 其实关于稚晖君的L-ink门禁卡在我本科阶段就已经刷过好几次了&#x…

基于STM32_DHT11单总线温湿度传感器驱动

基于STM32_DHT11单总线温湿度传感器驱动 文章目录 基于STM32_DHT11单总线温湿度传感器驱动前言一、DHT11&#xff1f;二、原理1.时序1.主机复位信号和 DHT11 响应信号2.信号‘0’的表示3.信号‘1’的表示4.整个数据信号收发流程 2.数据结构 三、驱动1 .h文件&#xff1a;2 .c文…

如何正确的关闭Redis服务器

Redis官方原生版本是在Linux平台上开发和测试的&#xff0c;但是大多数初学者都是使用Windows系统来学习如何开发的。因此&#xff0c;官方提供了一个叫做“Microsoft Open Tech Redis”的项目&#xff0c;该项目专门为Windows平台提供了一个官方支持的Redis版本&#xff0c;但…

智慧公厕:打破传统,解决城市痛点@中期科技

近年来&#xff0c;随着城市化进程的加速推进&#xff0c;智慧公厕成为人民生活质量提升的重要组成部分。作为一个富有创新和科技感的解决方案&#xff0c;智慧公厕不仅满足了人们对公共环境的要求&#xff0c;还提供了一系列便利的服务&#xff0c;让人们的生活更加舒适、便捷…

最新在线IDE流行度最新排名(每月更新)

2023年10月在线IDE流行度最新排名 TOP 在线IDE排名是通过分析在线ide名称在谷歌上被搜索的频率而创建的 在线IDE被搜索的次数越多&#xff0c;人们就会认为它越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&#xff0c;那么TOP ODE索引可以帮助您决定在软件开发项目中…

Nginx解析漏洞

常见的解析漏洞&#xff1a; IIS 5.x/6.0解析漏洞 IIS 7.0/IIS 7.5/ Nginx <0.8.3畸形解析漏洞 Nginx <8.03 空字节代码执行漏洞 Apache解析漏洞 Nginx文件解析漏洞 对于任意文件名&#xff0c;例如:cd.jpg在后面添加/x.php后&#xff0c;即可将文件作为php解析。 原理…

Flink中的时间和窗口

Flink中的时间和窗口 在传统的批处理系统中&#xff0c;我们可以等到一批数据全部都到齐了之后&#xff0c;对其做相关的计算&#xff1b;但是在实时处理系统中&#xff0c;数据是源源不断的&#xff0c;正常情况下&#xff0c;我们就得来一条处理一条。那么&#xff0c;我们应…

函数栈帧的创建与销毁(保姆级讲解)

局部变量是怎么创建的? 在为main函数开辟栈帧空间时&#xff0c;在一定范围内初始化成0CCCCC&#xff0c;再把里面0CCCC的一些开辟空间给局部变量使用。 为什么局部变量的值是随机值? 因为我们在为main函数开辟栈帧空间时&#xff0c;会将一定范围内空间初始成0CCCCCC里面…

Avalonia使一个弹窗弹到指定位置

1.项目下载地址&#xff1a;https://gitee.com/confusedkitten/avalonia-demo 2.UI库Semi.Avalonia&#xff0c;项目地址 https://github.com/irihitech/Semi.Avalonia 3.样式预览 4.PositionControl.axaml <UserControl xmlns"https://github.com/avaloniaui&quo…

华为数通方向HCIP-DataCom H12-831题库(单选题:201-220)

第201题 如图所示,路由器所有的接口开启OSPF,链路的Cost值如图中标识。若在R2的OSPF进程中通过命令import-route direct type 1引入直连路由,则R1到达10.0.2.2 /32的Cost值是以下哪一选项? A、150 B、151 C、200 D、201 答案:C 解析: Loopback0的cost值默认为0,R1-R2的…

AI 律助 Alpha GPT 线上实操发布会,重磅发布!

数字化时代,随着人工智能的迅猛发展,各行各业都在积极探索通过智能化工具实现工作效率翻升的可能性。“ ChatGPT 类产品”是未来办公应用软件发展的重要趋势之一,但如何将 ChatGPT 真正应用于法律人的工作,赋能效率提升?法律行业同样面临着新的挑战和机遇。 破局的关键是实现技…

DevExpress Reporting中文教程 - 如何在macOS等系统中生成导出报表文档

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 在本文中&#xff0c;我们将讨论如何在.NET MA…

基于Java的二手车交易管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

访问控制列表ACL讲解——想偷偷访问数据,我ACL可不同意

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 梦想从未散场&#xff0c;传奇永不落幕&#xff0c;博主会持续更新优质网络知识、Python知识、Linux知识以及各种小技巧&#xff0c;愿你我共同在CSDN进步 目录 一、ACL的基本概念 1. ACL是什么 2. 为什么需…

从零开始学习 Java:简单易懂的入门指南之线程同步(三十五)

线程同步 1.线程同步1.1卖票【应用】1.2卖票案例的问题1.3同步代码块解决数据安全问题【应用】1.4同步方法解决数据安全问题【应用】1.5Lock锁【应用】1.6死锁 2.生产者消费者2.1生产者和消费者模式概述【应用】2.2生产者和消费者案例【应用】2.3生产者和消费者案例优化【应用】…

Linux-提高CPU、内存使用率shell脚本

目录 1、提升CPU利用率 &#xff08;1&#xff09;构造CPU达到100% &#xff08;2&#xff09; 结束命令 2、提高内存利用率 可以申请的内存 申请内存空间&#xff1a; 完成后释放内存&#xff1a; 3、 使用ChaosBlade工具 cpu注入&#xff1a; 内存注入&#xff1a; …