缓存降级代码结构设计
缓存降级设计思想
接前文缺陷点
- 本地探针应该增加计数器,多次异常再设置,避免网络波动造成误判。
- 耦合度过高,远端缓存和本地缓存应该平行关系被设计为上下游关系了。
- 公用的远端缓存的操作方法应该私有化,避免集成方代码误操作,导致受到攻击。
- 探针改为轮训请求,类似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…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...