缓存降级代码结构设计
缓存降级设计思想
接前文缺陷点
- 本地探针应该增加计数器,多次异常再设置,避免网络波动造成误判。
- 耦合度过高,远端缓存和本地缓存应该平行关系被设计为上下游关系了。
- 公用的远端缓存的操作方法应该私有化,避免集成方代码误操作,导致受到攻击。
- 探针改为轮训请求,类似jedis底层心跳检测。
- 抽象多层策略,提供集成方自定义实现配置来决定采用什么方式进行降级,缓存工厂或者控制器来决定Redis是否异常和异常后走本地缓存还是数据库还是zk等策略。
- 底层应该做好职责分离,异常往上抛,由上层根据用户配置的策略做对应的处理逻辑。
- 业务层采用模板模式,对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);}
}
相关文章:
缓存降级代码结构设计
缓存降级设计思想 接前文缺陷点 本地探针应该增加计数器,多次异常再设置,避免网络波动造成误判。耦合度过高,远端缓存和本地缓存应该平行关系被设计为上下游关系了。公用的远端缓存的操作方法应该私有化,避免集成方代码误操作&…...

一文深入理解高并发服务器性能优化
我们现在已经搞定了 C10K并发连接问题 ,升级一下,如何支持千万级的并发连接?你可能说,这不可能。你说错了,现在的系统可以支持千万级的并发连接,只不过所使用的那些激进的技术,并不为人所熟悉。…...
pytorch中的归一化函数
在 PyTorch 的 nn 模块中,有一些常见的归一化函数,用于在深度学习模型中进行数据的标准化和归一化。以下是一些常见的归一化函数: nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d: 这些函数用于批量归一化 (Batch Normalization…...

【管理运筹学】第 10 章 | 排队论(1,排队论的基本概念)
文章目录 引言一、基本概念1.1 排队过程1.2 排队系统的组成和特征1.3 排队模型的分类1.4 系统指标1.5 系统状态 引言 开一点排队论的内容吧,方便做题。 排队论(Queuing Theory)也称随机服务系统理论,是为解决一系列排队问题&…...

【Express】服务端渲染(模板引擎 EJS)
EJS(Embedded JavaScript)是一款流行的模板引擎,可以用于在Express中创建动态的HTML页面。它允许在HTML模板中嵌入JavaScript代码,并且能够生成基于数据的动态内容。 下面是一个详细的讲解和示例,演示如何在Express中…...

Linux CentOS8安装gitlab_ce步骤
1 下载安装包 wget --content-disposition https://packages.gitlab.com/gitlab/gitlab-ce/packages/el/8/gitlab-ce-15.0.2-ce.0.el8.x86_64.rpm/download.rpm2 安装gitlab yum install policycoreutils-python-utilsrpm -Uvh gitlab-ce-15.0.2-ce.0.el8.x86_64.rpm3 更新配…...

RabbitMq启用TLS
Windows环境 查看配置文件的位置 选择使用的节点 查看当前节点配置文件的配置 配置TLS 将证书放到同配置相同目录中 编辑配置文件添加TLS相关配置 [{ssl, [{versions, [tlsv1.2]}]},{rabbit, [{ssl_listeners, [5671]},{ssl_options, [{cacertfile,"C:/Users/17126…...

CakePHP 3.x/4.x反序列化RCE链
最近网上公开了cakephp一些反序列化链的细节,但是没有公开poc,并且网上关于cakephp的反序列化链比较少,于是自己跟一下 ,构造pop链。 CakePHP简介 CakePHP是一个运用了诸如ActiveRecord、Association Data Mapping、Front Contr…...
练习之C++[3]
文章目录 1.模板类2.模板声明3.string类 1.模板类 模板可以具有非类型参数,用于指定大小,可以根据指定的大小创建动态结构所以可用来创建动态增长和减小的数据结构模板运行时不检查数据类型,也不保证类型安全,相当于类型的宏替换…...
[MT8766][Android12] 修改WIFI热点默认名称、密码、IP地址以及默认开启热点
文章目录 开发平台基本信息问题描述解决方法 开发平台基本信息 芯片: MTK8766 版本: Android 12 kernel: msm-4.19 问题描述 最近做了一款没有屏幕显示的智能盒子,要想操控这款设备就只能通过adb投屏,如果默认不允许有线连接,那么要怎么实…...

【嵌入式】堆栈与单片机内存
堆栈 在片内RAM中,常常要指定一个专门的区域来存放某些特别的数据 它遵循顺序存取和后进先出(LIFO/FILO)的原则,这个RAM区叫堆栈。 其实堆栈就是单片机中的一些存储单元,这些存储单元被指定保存一些特殊信息,比如地址࿰…...

十大排序算法Java实现及时间复杂度
文章目录 十大排序算法选择排序冒泡排序插入排序希尔排序快速排序归并排序堆排序计数排序基数排序桶排序时间复杂度 参考资料 十大排序算法 选择排序 原理 从待排序的数据元素中找出最小或最大的一个元素,存放在序列的起始位置, 然后再从剩余的未排序元…...
[Go]配置国内镜像源
配置 Windows 选一个 go env -w GOPROXYhttps://goproxy.cn,direct go env -w GOPROXYhttps://mirrors.aliyun.com/goproxy,direct查看环境配置 go env...
Java知识点补充
静态方法 vs 实例方法: 静态方法(使用 static 关键字声明):属于类,不依赖于对象实例,可以通过类名直接调用。 实例方法(不使用 static 关键字声明):属于类的实例…...

Webpack和JShaman相比有什么不同?
Webpack和JShaman相比有什么不同? Webpack的功能是打包,可以将多个JS文件打包成一个JS文件。 JShaman专门用于对JS代码混淆加密,目的是让JavaScript代码变的不可读、混淆功能逻辑、加密代码中的隐秘数据或字符,是用于代码保护的…...

WEB应用程序编程接口API
使用Web API Web API是网站的一部分,用于与使用具体URL请求特定信息的程序交互。这种请求称为API调用。请求的数据格式以易于处理的格式(JSON,CSV)返回。 Git和GitHub Git是一个分布式版本控制系统,帮助人们管理为项目所做的工作…...

进阶JAVA篇- BigDecimal 类的常用API(四)
目录 API 1.0 BigDecimal 类说明 1.1 为什么浮点数会计算不精确呢? 1.2 如何创建 BigDecimal 类型的对象 1.2.1具体来介绍三种方式来创建: 1.2.2 结合三种创建方法,一起来分析一下。 1.3 BigDecimal 类中的 valueOf(Strin…...

UE4 顶点网格动画播放后渲染模糊问题
问题描述:ABC格式的顶点网格动画播放结束后,改模型看起来显得很模糊有抖动的样子 解决办法:关闭逐骨骼动态模糊...
centos 磁盘挂载与解挂
磁盘挂载 查看已挂载的磁盘 df -TH查看磁盘分区,对比第一步,看哪些磁盘没有挂载,例如发现/dev/sdb的磁盘没有在第一步中显示 fdisk -l磁盘分区(/dev/sdb为上一步骤中没有挂载的磁盘) fdisk /dev/sdb执行上一命令后…...

C语言 位操作
定义 位操作提高程序运行效率,减少除法和取模的运算。在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。 左移 后空缺自动补0 右移 分为逻辑右移和算数右移 逻辑右移 不管什么类型&am…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...