关于系统数据缓存的思考以及设计

news/2025/5/22 2:12:55/

文章目录

  • 引言
  • 案例
    • A项目
    • B项目
  • 分析
  • 我的实现
  • 总结

引言

缓存,这是一个经久不衰的话题,它通过“空间换时间”的战术不仅能够极大提升处理查询性能还能很好的保护底层资源。最近针对系统数据缓存的优化后,由于这是一个通用的场景并且有了一点心得因此在这里分享出来。

案例

无论是传统Web后端应用还是大数据平台服务应用,本质上都是一个Java进程并且应用相关数据一般都是存在Mysql。从抽象的角度来看,所有请求基本都要经过以下几个流程:参数校验、鉴权、请求处理、请求响应。这几个流程特别是前几个往往需要依赖存在MySQL中的系统数据,例如判断请求中携带的的服务ID在系统中是否有对应的服务、获取服务对应的认证信息进行身份校验、请求处理时常常依赖的一些数据如某个变动非常低频的小表或者一些配置在MySQL中的系统配置等,此时如果是你,会怎么去设计的这个缓存?我先举两个负责过的项目之前是如何设计的

A项目

服务启动的时候读取MySQL中的系统数据并以HashMap的格式存在Java进程中,不会自动更新,在有配置变动时循环调用所有服务器的ip接口触发重新加载数据。具体流程如下
在这里插入图片描述

这种设计的优点如下

  1. 实现简单,不依赖外部组件
  2. 有数据变更立马更新所有缓存数据,基本不会在缓存中读到旧数据的逻辑

以上是这种设计的优点,可能也是设计者这么设计的原因。但是随着维护会出现以下问题

  1. 所有服务器的ip是配置在apollo中的,因此每次扩容机器时都要将新的ip增加到apollo中,否则可能会出现缓存不更新现象
  2. 在服务从ECS迁移到K8s后,服务的ip是动态分配的并且每次重启后都不一样,因此当有配置变动时只能手动重启服务或者执行curl调用接口来触发各个节点进行配置更新

B项目

服务启动的时候读取MySQL中的系统数据并以HashMap的格式存在Java进程中,并记录当前更新时间,在查询缓存时会判断此时距离上次更新时间是否已经超过30秒(硬编码),如果超过则重新触发查询MySQL更新缓存。具体流程如下
在这里插入图片描述

这种设计的优点如下

  1. 服务之间无需通信,相比A服务来说是会自己主动更新

除了上面这一个优点貌似旧没有了,值得吐槽的点很多,大致列举一下它实现的问题

  1. 针对每一个需要更新的缓存都新建一个自己独立的类来实现CommandLineRunner接口
  2. 硬编码30s过期的逻辑以及查询时再去做更新动作(类似第一次惩罚)
  3. 更新缓存时的逻辑是,先全量查询Mysql对应的表,然后循环对比缓存中的数据,有新增的就追加到缓存,再去跟缓存比较是否有删除的,有的话再去缓存中删除

分析

这两个服务在更新系统数据缓存上都存在不少问题,现在再回过头来思考🤔下,我们理想的系统数据缓存的实现应该是什么样子的?我大概整理了一下大致如下

  1. 服务启动时要全量读取对应的数据以Map格式存在Java进程的内存中
  2. 服务应该是定时自行去Mysql同步最新的数据,并且定时周期是允许可配置的
  3. 一些重要配置变更时触发各个服务立马去Mysql同步数据(可选,需要参考具体业务场景)

基于这个场景我对比了下当前比较热门的缓存工具Guava和Caffeine,最终发现它们都不适合咱们的这个场景。它们的设计更偏向于解决缓存热点数据的问题,简单来说就是咱们的场景是Java内存有10G,而存在Mysql中的数据有100M,我们需要将这100M的数据存在Java内存中进行请求加速,需要解决的是如何全量加载到内存的问题。而Guava和Caffeine的设计要解决的问题时,如何更有效的合理的利用内存,例如Java内存只有10G,而存在MySQL的数据有100G,因此需要将这100G中的热点数据存在Java内存中来提升性能和降低高频访问Mysql,它具体要考虑的问题大致有以下几点

  1. 通过LRU算法或者变种来保留热点数据
  2. 通过大小限制以及时间限制来剔除掉缓存数据从而保证Java内存不会被撑爆
  3. 在有新数据写入或者数据更新时,同步更新缓存中的数据

通过这些你会发现其实这个场景真不适合用Guava和Caffeine,这个过程中也翻阅了一些大佬的实现,但是发现都有点跑偏了,有种为了用Caffeine而强行用的味道。例如 https://www.vincentli.top/2020/09/01/jvm-local-cache-case-caffeine/,这里本质上还是跟B项目的实现一样,只不过用Caffeine替换了Java的HashMap罢了。在这里补充说下,Caffeine是非常优秀的缓存工具,现在很多优秀的开源组件例如 Pulsar 中也会用它来加速查询,但盲目的使用是不可取的,并且这肯定也不是Caffeine设计者想看到的。

我的实现

上面说了这么多,我分享下自己的实现供大家参考以及批判。在这里通过一个定时线程池实现即可,在启动时触发一次并在后续周期性的刷新配置即可,核心代码也就两三行。先看下流程
在这里插入图片描述

代码实现如下

public class CacheManager implements CommandLineRunner {private static final Long DEFAULT_INTERVAL = 36000L;private final ScheduledExecutorService apiSystemConfigExecutorService =  Executors.newSingleThreadScheduledExecutor();private static Map<String, String> apiSysConfig = new HashMap<>();@Overridepublic void run(String... args) throws Exception {apiSystemConfigExecutorService.scheduleAtFixedRate(this::loadApiSysConfigDataFromDB,0, Optional.ofNullable(PropertyConfigurer.getLong("SYSTEM_CONFIG_UPDATE_PERIOD")).orElse(DEFAULT_INTERVAL), TimeUnit.SECONDS);}public void loadApiSysConfigDataFromDB(){ApisDao apisDao = SpringContextHolder.getBean(ApisDao.class);List<ApiSysConfigEntity> sysConfigEntityList = apisDao.selectSystemConfig();if (null == sysConfigEntityList || sysConfigEntityList.size() == 0){return;}logger.info("load ApiSysConfigEntity from api_sys_config in mysql ,the size of api in cache  is " + sysConfigEntityList.size());apiSysConfig = sysConfigEntityList.stream().collect(Collectors.toMap(ApiSysConfigEntity::getName, ApiSysConfigEntity::getValue));}
}

总结

在工作中只要我们观察和思考,就会发现其实是存在不少值得完善的地方,此时应该考虑对它们进行完善,否则如果长期维护一个“丑陋”的系统,你的思维、以及审美也会随之跟着降低,以至于久而久之就觉得这种设计也挺好的,甚至后续再有类似的场景时你还是会选择这种设计。工作的本质也是一场修行,在对系统进行改进完善的过程也是自我完善的过程,简称“借物得道”,同时如果读者针对这个场景有更合适的设计也欢迎在下方一起讨论。


http://www.ppmy.cn/news/1424474.html

相关文章

再拓信创版图-Smartbi 与东方国信数据库完成兼容适配认证

近日&#xff0c;思迈特商业智能与数据分析软件 [简称&#xff1a;Smartbi Insight] V11与北京东方国信科技股份有限公司 &#xff08;以下简称东方国信&#xff09;CirroData-OLAP分布式数据库V2.14.1完成兼容性测试。经双方严格测试&#xff0c;两款产品能够达到通用兼容性要…

计算机网络(王道考研)笔记个人整理——第二章

第二章 物理层主要任务&#xff1a;确定与传输媒体有关的一些特性 机械特性&#xff1a;物理连接的特性 规定物理连接时所采用的规格、接口形状、引线数目、引脚数量和排列情况 电气特性 规定传输二进制位时&#xff0c;线路上信号的电压范围、阻抗匹配、传输速率和距离限制等…

VectorMap论文阅读

1. 摘要 自动驾驶系统需要对周围环境具有很好的理解&#xff0c;包括动态物体和静态高精度语义地图。现有方法通过离线手动标注来解决语义构图问题&#xff0c;这些方法存在严重的可扩展性问题。最近的基于学习的方法产生稠密的分割预测结果&#xff0c;这些预测不包含单个地图…

Oracle——领先的企业级数据库解决方案

一、WHAT IS ORACLWE&#xff1a; ORACLE 数据库系统是美国 ORACLE 公司&#xff08;甲骨文&#xff09;提供的以分布式数据库为核心的一组软件产品&#xff0c;是目前最流行的客户/服务器(CLIENT/SERVER)或B/S 体系结构的数据库之一&#xff0c;ORACLE 通常应用于大型系统的数…

Leetcode - 周赛393

目录 一&#xff0c;3114. 替换字符可以得到的最晚时间 二&#xff0c;3115. 素数的最大距离 三&#xff0c;3116. 单面值组合的第 K 小金额 四&#xff0c; 3117. 划分数组得到最小的值之和 一&#xff0c;3114. 替换字符可以得到的最晚时间 本题是一道模拟题&#xff0c;…

51单片机串口输出问题(第一个字符重复,自动循环输出第一个字符)

遇到的问题描述 51单片机使用串口发送数据时出现只循环发送字符串的第一个字符的情况。就算发送的是第一个字符也有时候一直发送。 串口函数代码 参考串口发送注意 #include <reg52.h> //此文件中定义了单片机的一些特殊功能寄存器void UsartInit() {SCON0X50; /…

Windows上构建 Chisel-Bootcamp

windows环境构建本地Chisel-Bootcamp 安装摘要Chisel-boocamp环境搭建安装java安装Anaconda安装scala 下载Chisel-bootcamp 环境Reference 安装摘要 在windows上安装chisel-boocamp&#xff0c;与linux过程类似。 安装java8安装anaconda安装scala下载Chisel-bootcamp环境 Ch…

并发场景下 缓存击穿 穿透 雪崩如何解决

最近建了一个技术交流群&#xff0c;欢迎志同道合的同学加入&#xff0c;群里主要讨论&#xff1a;分享业务解决方案、深度分析面试题并解答工作中遇到的问题&#xff0c;同时也能为我提供写作的素材。 群号 208236931&#xff0c;欢迎进群交流学习&#xff0c;一起进步、进步、…