news 2026/5/11 22:07:42

高性能服务架构缓存设计:Redis+Caffeine

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
高性能服务架构缓存设计:Redis+Caffeine

👉这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:

  • 《项目实战(视频)》:从书中学,往事上“练”

  • 《互联网高频面试题》:面朝简历学习,春暖花开

  • 《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题

  • 《精进 Java 学习指南》:系统学习,互联网主流技术栈

  • 《必读 Java 源码专栏》:知其然,知其所以然

👉这是一个或许对你有用的开源项目

国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构

RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRMAI大模型、IoT物联网等功能:

  • 多模块:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • 微服务:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK17/21+SpringBoot3、JDK8/11+Spring Boot2双版本

  • 背景

  • 为什么要使用本地缓存

  • 设计一个本地内存需要有什么功能

  • 本地缓存方案选型

    • 1. 使用ConcurrentHashMap实现本地缓存

    • 2. 基于Guava Cache实现本地缓存

    • 3. Caffeine

    • 4. Encache

  • 本地缓存问题及解决

    • 1. 缓存一致性

    • 2. 如何提高本地缓存命中率

    • 3. 本地内存的技术选型问题


背景

在高性能的服务架构设计中,缓存是一个不可或缺的环节。在实际的项目中,我们通常会将一些热点数据存储到Redis或Memcached 这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库。在提升访问速度的同时,也能降低数据库的压力。

随着不断的发展,这一架构也产生了改进,在一些场景下可能单纯使用Redis类的远程缓存已经不够了,还需要进一步配合本地缓存使用,例如Guava cache或Caffeine,从而再次提升程序的响应速度与服务性能。于是,就产生了使用本地缓存作为一级缓存,再加上远程缓存作为二级缓存的两级缓存架构。

在先不考虑并发等复杂问题的情况下,两级缓存的访问流程可以用下面这张图来表示:

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

为什么要使用本地缓存

  • 本地缓存基于本地环境的内存,访问速度非常快,对于一些变更频率低、实时性要求低的数据,可以放在本地缓存中,提升访问速度

  • 使用本地缓存能够减少和Redis类的远程缓存间的数据交互,减少网络I/O开销,降低这一过程中在网络通信上的耗时

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

设计一个本地内存需要有什么功能

  • 存储,并可以读、写;

  • 原子操作(线程安全),如ConcurrentHashMap

  • 可以设置缓存的最大限制;

  • 超过最大限制有对应淘汰策略,如LRU、LFU

  • 过期时间淘汰,如定时、懒式、定期;

  • 持久化

  • 统计监控

本地缓存方案选型

1. 使用ConcurrentHashMap实现本地缓存

缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中线程安全的ConcurrentHashMap,但是要实现缓存,还需要考虑淘汰、最大限制、缓存过期时间淘汰等等功能;

优点是实现简单,不需要引入第三方包,比较适合一些简单的业务场景。缺点是如果需要更多的特性,需要定制化开发,成本会比较高,并且稳定性和可靠性也难以保障。对于比较复杂的场景,建议使用比较稳定的开源工具。

2. 基于Guava Cache实现本地缓存

Guava是Google团队开源的一款 Java 核心增强库,包含集合、并发原语、缓存、IO、反射等工具箱,性能和稳定性上都有保障,应用十分广泛。Guava Cache支持很多特性:

  • 支持最大容量限制

  • 支持两种过期删除策略(插入时间和访问时间)

  • 支持简单的统计功能

  • 基于LRU算法实现

使用代码如下:

<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency> @Slf4j public class GuavaCacheTest { public static void main(String[] args) throws ExecutionException { Cache<String, String> cache = CacheBuilder.newBuilder() .initialCapacity(5) // 初始容量 .maximumSize(10) // 最大缓存数,超出淘汰 .expireAfterWrite(60, TimeUnit.SECONDS) // 过期时间 .build(); String orderId = String.valueOf(123456789); // 获取orderInfo,如果key不存在,callable中调用getInfo方法返回数据 String orderInfo = cache.get(orderId, () -> getInfo(orderId)); log.info("orderInfo = {}", orderInfo); } private static String getInfo(String orderId) { String info = ""; // 先查询redis缓存 log.info("get data from redis"); // 当redis缓存不存在查db log.info("get data from mysql"); info = String.format("{orderId=%s}", orderId); return info; } }

3. Caffeine

Caffeine是基于java8实现的新一代缓存工具,缓存性能接近理论最优。可以看作是Guava Cache的增强版,功能上两者类似,不同的是Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,在性能上有明显的优越性

使用代码如下:

<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.3</version> </dependency> @Slf4j public class CaffeineTest { public static void main(String[] args) { Cache<String, String> cache = Caffeine.newBuilder() .initialCapacity(5) // 超出时淘汰 .maximumSize(10) //设置写缓存后n秒钟过期 .expireAfterWrite(60, TimeUnit.SECONDS) //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite //.expireAfterAccess(17, TimeUnit.SECONDS) .build(); String orderId = String.valueOf(123456789); String orderInfo = cache.get(orderId, key -> getInfo(key)); System.out.println(orderInfo); } private static String getInfo(String orderId) { String info = ""; // 先查询redis缓存 log.info("get data from redis"); // 当redis缓存不存在查db log.info("get data from mysql"); info = String.format("{orderId=%s}", orderId); return info; } }

4. Encache

Encache是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。同Caffeine和Guava Cache相比,Encache的功能更加丰富,扩展性更强:

  • 支持多种缓存淘汰算法,包括LRU、LFU和FIFO

  • 缓存支持堆内存储、堆外存储、磁盘存储(支持持久化)三种

  • 支持多种集群方案,解决数据共享问题

使用代码如下:

<dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.9.7</version> </dependency> @Slf4j public class EhcacheTest { private static final String ORDER_CACHE = "orderCache"; public static void main(String[] args) { CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() // 创建cache实例 .withCache(ORDER_CACHE, CacheConfigurationBuilder // 声明一个容量为20的堆内缓存 .newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))) .build(true); // 获取cache实例 Cache<String, String> cache = cacheManager.getCache(ORDER_CACHE, String.class, String.class); String orderId = String.valueOf(123456789); String orderInfo = cache.get(orderId); if (StrUtil.isBlank(orderInfo)) { orderInfo = getInfo(orderId); cache.put(orderId, orderInfo); } log.info("orderInfo = {}", orderInfo); } private static String getInfo(String orderId) { String info = ""; // 先查询redis缓存 log.info("get data from redis"); // 当redis缓存不存在查db log.info("get data from mysql"); info = String.format("{orderId=%s}", orderId); return info; } }

本地缓存问题及解决

1. 缓存一致性

两级缓存与数据库的数据要保持一致,一旦数据发生了修改,在修改数据库的同时,本地缓存、远程缓存应该同步更新。

解决方案1: MQ

一般现在部署都是集群部署,有多个不同节点的本地缓存; 可以使用MQ的广播模式,当数据修改时向MQ发送消息,节点监听并消费消息,删除本地缓存,达到最终一致性;

解决方案2:Canal + MQ

如果你不想在你的业务代码发送MQ消息,还可以适用近几年比较流行的方法:订阅数据库变更日志,再操作缓存。Canal 订阅Mysql的 Binlog日志,当发生变化时向MQ发送消息,进而也实现数据一致性。

2. 如何提高本地缓存命中率

参考:如何提高缓存命中率

3. 本地内存的技术选型问题

  • 从易用性角度,Guava Cache、Caffeine和Encache都有十分成熟的接入方案,使用简单。

  • 从功能性角度,Guava Cache和Caffeine功能类似,都是只支持堆内缓存,Encache相比功能更为丰富

  • 从性能上进行比较,Caffeine最优、GuavaCache次之,Encache最差(下图是三者的性能对比结果)

对于本地缓存的方案中,我比较推荐Caffeine,性能上遥遥领先。

虽然Encache功能更为丰富,甚至提供了持久化和集群的功能,但是这些功能完全可以依靠其他方式实现。真实的业务工程中,建议使用Caffeine作为本地缓存,另外使用redis或者memcache作为分布式缓存,构造多级缓存体系,保证性能和可靠性。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

文章有帮助的话,在看,转发吧。 谢谢支持哟 (*^__^*)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 22:04:46

2026 流量卡办理全攻略:从下单、激活到售后,新手一遍看懂不踩坑

现在人人都离不开手机流量&#xff0c;不管是日常刷视频、追剧观影&#xff0c;还是备用机上网冲浪&#xff0c;一张划算又正规的通用流量卡&#xff0c;已经成为大众刚需。但很多新手第一次在线办理优惠号卡&#xff0c;普遍一头雾水&#xff1a;分不清流量卡是否正规靠谱、办…

作者头像 李华
网站建设 2026/5/11 22:00:27

STM32——OLED显示汉字

前言在使用 STM32 驱动 SSD1306 OLED 时&#xff0c;很多新手都会遇到汉字错位、爱心旋转、图案乱码等问题&#xff0c;而这些问题的根源&#xff0c;往往不是硬件接线&#xff0c;而是对 OLED 页寻址模式、PCtoLCD2002 取模规则、显示函数底层逻辑的不理解。今天我将基于你提供…

作者头像 李华
网站建设 2026/5/11 21:59:13

NoSQL

NoSQL&#xff08;Not Only SQL&#xff09; 是泛指非关系型数据库的统称&#xff0c;核心是放弃固定表结构、优先水平扩展 高可用 灵活 Schema&#xff0c;适合海量、高并发、非结构化 / 半结构化数据场景。一、核心特点&#xff08;vs 传统 SQL&#xff09;Schema 灵活&…

作者头像 李华
网站建设 2026/5/11 21:53:12

从数学原理到工程实践:最小二乘法的MATLAB拟合全解析

1. 最小二乘法的数学本质&#xff1a;从误差分析到最优解 当你面对一堆实验数据点&#xff0c;想要找到一条最能代表它们趋势的曲线时&#xff0c;最小二乘法就是你的最佳拍档。这个方法的核心思想其实非常直观——让所有数据点到拟合曲线的"距离"之和最小。这里的&q…

作者头像 李华