缓存监控--来源于网络

news/2025/10/10 16:45:42/文章来源:https://www.cnblogs.com/codechange/p/19133385

前缀key设计,按照不同的业务区分了不同的业务场景的前缀Key

public class RedisKeyConstants {public static final String    REDIS_GAMEGROUP_NEW_KEY              = "newgamegroup";public static final String    REDIS_GAMEGROUP_DETAIL_KEY          = "gamegroup:detail";public static final String    REDIS_KEY_IUNIT_STRATEGY_COUNT      = "activity:ihandler:strategy:count";public static final String    CONTENT_DISTRIBUTE_CURRENT          = "content:distribute:current";public static final String    RECOMMEND_NOTE                      = "recommend:note";
}public class RedisUtils {public static final String    COMMON_REDIS_KEY_SPLIT    = ":";public static String buildRedisKey(String key, Object... params) {if (params == null || params.length == 0) {return key;}for (Object param : params) {key += COMMON_REDIS_KEY_SPLIT + param;}return key;}
}

监控实现,通过 Aspect 的切面功能对 Redis 的指定操作进行拦截,如上图中的 Set 操作等,可以按需扩展到其他操作,针对前缀 key 的提取支持两个维度,默认场景和自定义场景,其中处理优先级为 自定义场景 > 默认场景,考虑自定义场景的灵活性,相关的自定义前缀通过配置中心实时生效


@Slf4j
@Aspect
@Order(0)
@Component
public class RedisMonitorAspect {private static final String PREFIX_CONFIG = "redis.monitor.prefix";private static final Set<String> PREFIX_SET = new HashSet<>();@Resourceprivate MonitorComponent monitorComponent;static {// 更新前缀匹配的名单String prefixValue = VivoConfigManager.getString(PREFIX_CONFIG, "");refreshConf(prefixValue);// 增加配置变更的回调VivoConfigManager.addListener(new VivoConfigListener() {@Overridepublic void eventReceived(PropertyItem propertyItem, ChangeEventType changeEventType) {if (StringUtils.equalsIgnoreCase(propertyItem.getName(), PREFIX_CONFIG)) {refreshConf(propertyItem.getValue());}}});}/*** 更新前缀匹配的名单* @param prefixValue*/private static void refreshConf(String prefixValue) {if (StringUtils.isNotEmpty(prefixValue)) {String[] prefixArr = StringUtils.split(prefixValue, ",");Arrays.stream(prefixArr).forEach(item -> PREFIX_SET.add(item));}}@Pointcut("execution(* com.vivo.joint.dal.common.redis.dao.RedisDao.set*(..))")public void point() {}@Around("point()")public Object around(ProceedingJoinPoint pjp) throws Throwable {//业务逻辑异常情况直接抛到业务层处理Object result = pjp.proceed();try {if (VivoConfigManager.getBoolean("joint.center.redis.monitor.switch", true)) {Object[] args = pjp.getArgs();if (null != args && args.length > 0) {String redisKey = String.valueOf(args[0]);if (VivoConfigManager.getBoolean("joint.center.redis.monitor.send.log.switch", true)) {LOGGER.info("更新redis的缓存 {}", redisKey);}String monitorKey = null;// 先指定前缀匹配if (!PREFIX_SET.isEmpty()) {for (String prefix : PREFIX_SET) {if (StringUtils.startsWithIgnoreCase(redisKey, prefix)) {monitorKey = prefix;break;}}}if (StringUtils.isEmpty(monitorKey) && StringUtils.contains(redisKey, ":")) {// 需要考虑前缀的格式,保证数据写入不能膨胀monitorKey = StringUtils.substringBeforeLast(redisKey, ":");}monitorComponent.sendRedisMonitorData(monitorKey);}}} catch (Exception e) {}return result;}
}案例
public static final String REDISKEY_USER_POPUP_PLAN = "popup:user:plan";public PopupWindowPlan findPlan(FindPlanParam param) {String openId = param.getOpenId();String imei = param.getImei();String gamePackage = param.getGamePackage();Integer planType = param.getPlanType();String appId = param.getAppId();// 1、获取缓存的数据PopupWindowPlan cachedPlan = getPlanFromCache(openId, imei, gamePackage, planType);if (cachedPlan != null) {monitorPopWinPlan(cachedPlan);return cachedPlan;}// 2、未命中换成后从持久化部分获取对应的 PopupWindowPlan 对象// 3、保存到Redis换成setPlanToCache(openId, imei, gamePackage, plan);return cachedPlan;}// 从缓存中获取数据的逻辑private PopupWindowPlan getPlanFromCache(String openId, String imei, String gamePackage, Integer planType) {String key = RedisUtils.buildRedisKey(RedisKeyConstants.REDISKEY_USER_POPUP_PLAN, openId, imei, gamePackage, planType);String cacheValue = redisDao.get(key);if (StringUtils.isEmpty(cacheValue)) {return null;}try {PopupWindowPlan plan = objectMapper.readValue(cacheValue, PopupWindowPlan.class);return plan;} catch (Exception e) {}return null;}// 保存数据到缓存当中private void setPlanToCache(String openId, String imei, String gamePackage, PopupWindowPlan plan, Integer planType) {String key = RedisUtils.buildRedisKey(RedisKeyConstants.REDISKEY_USER_POPUP_PLAN, openId, imei, gamePackage, planType);try {String serializedStr = objectMapper.writeValueAsString(plan);redisDao.set(key, serializedStr, VivoConfigManager.getInteger(ConfigConstants.POPUP_PLAN_CACHE_EXPIRE_TIME, 300));} catch (Exception e) {}}

**如监控实现部分所述,通过 Redis Key 的前缀聚合监控,能够发现某一类业务场景的 Redis 的写请求数,进而发现 Redis 的无效使用场景。

上述案例是典型的Redis的缓存使用场景:1.访问 Redis 缓存;2.若命中则直接返回结果;3、如未命中则查询持久化存储获取数据并写入 Redis 缓存。

从业务监控的大盘发现前缀 popup:user:plan 存在大量的 set 操作命令,按照缓存读多写少的原则,该场景标明该缓存的设计是无效的。

通过业务分析后,发现在游戏的业务场景中 用户维度+游戏维度 不存在5分钟重复访问缓存的场景,确认缓存的无效**

本地缓存caffeine

public final class Caffeine<K, V> {/*** caffeine的实例名称*/String instanceName;/*** caffeine的实例维护的Map信息*/static Map<String, Cache> cacheInstanceMap = new ConcurrentHashMap<>();@NonNullpublic <K1 extends K, V1 extends V> Cache<K1, V1> build() {requireWeightWithWeigher();requireNonLoadingCache();@SuppressWarnings("unchecked")Caffeine<K1, V1> self = (Caffeine<K1, V1>) this;Cache localCache =  isBounded() ? new BoundedLocalCache.BoundedLocalManualCache<>(self) : new UnboundedLocalCache.UnboundedLocalManualCache<>(self);if (null != localCache && StringUtils.isNotEmpty(localCache.getInstanceName())) {cacheInstanceMap.put(localCache.getInstanceName(), localCache);}return localCache;}
}static Cache<String, List<String>> accountWhiteCache = Caffeine.newBuilder().applyName("accountWhiteCache").expireAfterWrite(VivoConfigManager.getInteger("trade.account.white.list.cache.ttl", 10), TimeUnit.MINUTES).recordStats().maximumSize(VivoConfigManager.getInteger("trade.account.white.list.cache.size", 100)).build();public static StatsData getCacheStats(String instanceName) {Cache cache = Caffeine.getCacheByInstanceName(instanceName);CacheStats cacheStats = cache.stats();StatsData statsData = new StatsData();statsData.setInstanceName(instanceName);statsData.setTimeStamp(System.currentTimeMillis()/1000);statsData.setMemoryUsed(String.valueOf(cache.getMemoryUsed()));statsData.setEstimatedSize(String.valueOf(cache.estimatedSize()));statsData.setRequestCount(String.valueOf(cacheStats.requestCount()));statsData.setHitCount(String.valueOf(cacheStats.hitCount()));statsData.setHitRate(String.valueOf(cacheStats.hitRate()));statsData.setMissCount(String.valueOf(cacheStats.missCount()));statsData.setMissRate(String.valueOf(cacheStats.missRate()));statsData.setLoadCount(String.valueOf(cacheStats.loadCount()));statsData.setLoadSuccessCount(String.valueOf(cacheStats.loadSuccessCount()));statsData.setLoadFailureCount(String.valueOf(cacheStats.loadFailureCount()));statsData.setLoadFailureRate(String.valueOf(cacheStats.loadFailureRate()));Optional<Eviction> optionalEviction = cache.policy().eviction();optionalEviction.ifPresent(eviction -> statsData.setMaximumSize(String.valueOf(eviction.getMaximum())));Optional<Expiration> optionalExpiration = cache.policy().expireAfterWrite();optionalExpiration.ifPresent(expiration -> statsData.setExpireAfterWrite(String.valueOf(expiration.getExpiresAfter(TimeUnit.SECONDS))));optionalExpiration = cache.policy().expireAfterAccess();optionalExpiration.ifPresent(expiration -> statsData.setExpireAfterAccess(String.valueOf(expiration.getExpiresAfter(TimeUnit.SECONDS))));optionalExpiration = cache.policy().refreshAfterWrite();optionalExpiration.ifPresent(expiration -> statsData.setRefreshAfterWrite(String.valueOf(expiration.getExpiresAfter(TimeUnit.SECONDS))));return statsData;
}public static void sendReportData() {try {if (!VivoConfigManager.getBoolean("memory.caffeine.data.report.switch", true)) {return;}// 1、获取所有的cache实例对象Method listCacheInstanceMethod = HANDLER_MANAGER_CLASS.getMethod("listCacheInstance", null);List<String> instanceNames = (List)listCacheInstanceMethod.invoke(null, null);if (CollectionUtils.isEmpty(instanceNames)) {return;}String appName = System.getProperty("app.name");String localIp = getLocalIp();String localPort = String.valueOf(NetPortUtils.getWorkPort());ReportData reportData = new ReportData();InstanceData instanceData = new InstanceData();instanceData.setAppName(appName);instanceData.setIp(localIp);instanceData.setPort(localPort);// 2、遍历cache实例对象获取缓存监控数据Method getCacheStatsMethod = HANDLER_MANAGER_CLASS.getMethod("getCacheStats", String.class);Map<String, StatsData> statsDataMap = new HashMap<>();instanceNames.stream().forEach(instanceName -> {try {StatsData statsData = (StatsData)getCacheStatsMethod.invoke(null, instanceName);statsDataMap.put(instanceName, statsData);} catch (Exception e) {}});// 3、构建上报对象reportData.setInstanceData(instanceData);reportData.setStatsDataMap(statsDataMap);// 4、发送Http的POST请求HttpPost httpPost = new HttpPost(getReportDataUrl());httpPost.setConfig(requestConfig);StringEntity stringEntity = new StringEntity(JSON.toJSONString(reportData));stringEntity.setContentType("application/json");httpPost.setEntity(stringEntity);HttpResponse response = httpClient.execute(httpPost);String result = EntityUtils.toString(response.getEntity(),"UTF-8");EntityUtils.consume(response.getEntity());logger.info("Caffeine 数据上报成功 URL {} 参数 {} 结果 {}", getReportDataUrl(), JSON.toJSONString(reportData), result);} catch (Throwable throwable) {logger.error("Caffeine 数据上报失败 URL {} ", getReportDataUrl(), throwable);}
}

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

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

相关文章

20232319 2025-2026-1 《网络与系统攻防技术》实验一实验报告

1.实验内容篡改程序流程——直接修改可执行文件,跳转至getShell; 栈溢出攻击——利用foo函数的缓冲区溢出漏洞,覆盖返回地址触发getShell; Shellcode注入——构造恶意输入注入自定义Shellcode并执行。2.实验过程 一…

2025 年兽药厂家最新推荐榜:级企业技术专利与服务能力全景解析,养殖户选品权威指南

当前兽药行业正处于技术迭代与合规升级的关键期,养殖业对绿色无抗、精准高效兽药的需求日益迫切,但市场乱象仍存:部分产品有效成分不足、生产流程不合规,既影响养殖效益,更威胁食品安全。尤其近五年,一批具备创新…

2025 最新隔音板源头厂家口碑推荐榜:阻尼 / 聚酯纤维等全品类适配,资深企业与新锐品牌精选聚酯纤维/墙面/降噪/玻镁/顶部隔音板厂家推荐

随着建筑声学需求的升级,隔音板已成为住宅、医疗、商业等场景的刚需材料,但市场乱象却让选择愈发艰难。部分厂商以次充好,用劣质基材降低成本,导致隔音量不足行业标准 30% 以上,且甲醛超标问题频发;另有品牌缺乏…

Google play 内部测试流程

前往Google Play Console 创建Email List去Internal testing创建新的 release 创建过程不再赘述创建好之后去 Testers 页面 勾选之前的 Email List,并点击下面的 Copy Link 按钮在手机上打开刚刚复制的 Link (确保你…

软工第三次作业

软工第三次作业 - 结对编程 一、结对信息姓名1:伊尔番艾山 学号1:3123004762 姓名2:梁子恒 学号2:312300746 GitHub仓库:https://github.com/IrErV123/Primary-Math-Exercise-Generator二、PSP表格PSP2.1 Persona…

全球化部署几种方案

目录背景跨国混合部署的挑战商城和履约解耦的挑战定时任务。跨系统集成的复杂性。商城和中台分离的架构设计参考资料 背景 跨国混合部署的挑战系统和其他系统可能不在一个国家/地区。 比如把数据采集相关的服务部署到海…

10.WPF布局 - 实践

10.WPF布局 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Co…

066_尚硅谷_运算符优先级

066_尚硅谷_运算符优先级1.运算符优先级

基于MATLAB的路面裂缝检测识别

一、系统架构设计 1. 功能模块划分 graph TDA[图像采集] --> B[预处理模块]B --> C[裂缝检测引擎]C --> D[特征提取]D --> E[分类识别]E --> F[结果可视化]F --> G[数据存储] 2. 硬件配置建议模块 推…

HDU6794:Tokitsukaze and Multiple

实质上是最多能找到多少个连续子段和为 \(p\) 的倍数 太聪明了

当下环境通缩分析

📅 通缩的起点与当前态势 关于这次通缩周期的起点,根据多个宏观经济指标的表现,可以梳理出以下时间线:关键指标持续下行:本次通缩压力是逐步累积的结果。一个非常重要的观察点是生产者价格指数(PPI),它至2025…

使用qt读取系统字体库,并进行英文名称映射

映射函数:// 创建中文字体到英文名称的映射表 QMap<QString, QString> createFontMapping() {QMap<QString, QString> mapping;mapping[QString::fromLocal8Bit("宋体")] = "simsun.ttc&q…

gitlab配置aws的s3作为docker镜像的存储

编辑 /etc/gitlab/gitlab.rb 文件external_url http://192.168.60.118 registry_external_url http://192.168.60.118:5005 gitlab_rails[registry_enabled] = true gitlab_rails[registry_host] = "192.168.60.…

阿克曼函数

点击查看代码 #include <iostream> using namespace std; int ack(int x,int y) {if (x==0){return y + 1;}else if (x > 0 && y == 0){return ack(x - 1, 1);}else if (x > 0 && y > …

shell脚本监控ssl证书到期时间

一、需求 说明:(1)读取域名列表文件。(2)获取域名到期时间,进行告警后邮件提醒。 #!/bin/bash ## 第1步 配置文件 # 颜色定义 RED=\033[0;31m GREEN=\033[0;32m YELLOW=\033[1;33m BLUE=\033[0;34m PURPLE=\033…

AI如何通过卫星图像识别刺猬栖息地

剑桥大学研究人员利用卫星图像和机器学习技术,通过识别黑莓灌木丛来绘制刺猬潜在栖息地地图,为保护这种濒危物种提供创新解决方案。AI如何通过卫星图像识别刺猬栖息地 你不能从太空中直接发现刺猬,但通过寻找黑莓灌…

04-补充mybatis-plus的Service接口

04-补充mybatis-plus的Service接口$(".postTitle2").removeClass("postTitle2").addClass("singleposttitle");常用方法查询相关方法以前是我们自己实现的Service接口和方法先在mp给我们…

LeetCode热题100-75、跳跃游戏

LeetCode热题100-75、跳跃游戏给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回…

rust 模块和引用

rust的模块声明有两种方式: 1、mod.rs文件方式 在 2018 年后的版本中已逐渐被新的模块系统取代,Rust 官方文档中已删除对 mod.rs 的介绍,主要因为该功能在 Rust 1.30 版本后被新的模块命名约定替代。 目录结构为: …

moectf2025-reverse-wp

moectf2025-reverse-wp upx 壳是什么?upx是什么?upx可以用来干什么?用自己的upx脱壳 upx -d +文件地址然后就去ida里面编译但是找不到main函数 我们直接shift+F12查看字符串然后点进去鼠标移到这里 按X 然后定位到主…