原来这是缓存啊,被虐了!

ruoyi-vue-pro 开发指南 - myself 快速启动(适合“后端”工程师) | ruoyi-vue-pro 开发指南 (iocoder.cn) 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:勇哥java实战分享

3266f8689d94cb88ca6b26389e69e0a3.jpeg


缓存 ,消息队列,分库分表是高并发解决方案三剑客。

缓存之所以能够让系统“更快”,本质上做到了如下两点:

  • 减小 CPU 消耗

    将原来需要实时计算的内容提前算好、把一些公用的数据进行复用,这可以减少 CPU 消耗,从而提升响应性能。

  • 减小 I/O 消耗

    将原来对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,从而提升响应性能。

对于应用系统 来讲,我们经常将缓存划分为本地缓存 和分布式缓存 。

本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无法共享缓存。

分布式缓存 :和应用分离的缓存组件或服务,与本地应用隔离,多个应用可直接共享缓存。

这篇文章,聊聊本地缓存和分布式缓存,希望大家读完之后,在面对不同的业务场景时,能够做出合理的缓存选型。

1 本地缓存 JDK Map

JDK Map 经常用于缓存实现:

  • HashMap

    HashMap 是一种基于哈希表的集合类,它提供了快速的插入、查找和删除操作。可以将键值对作为缓存项的存储方式,将键作为缓存项的唯一标识符,值作为缓存项的内容。

  • ConcurrentHashMap

    ConcurrentHashMap 是线程安全的 HashMap,它在多线程环境下可以保证高效的并发读写操作。

  • LinkedHashMap

    LinkedHashMap 是一种有序的 HashMap ,它保留了元素插入的顺序,可以按照插入顺序或者访问顺序进行遍历。

  • TreeMap

    TreeMap 是一种基于红黑树的有序 Map,它可以按照键的顺序进行遍历。

笔者曾经负责艺龙红包系统,红包活动 就是存储在 ConcurrentHashMap 中 ,通过定时任务刷新缓存 。

02a4c12f8489589e6584ea96268991dd.png

核心流程:

1、红包系统启动后,初始化一个 ConcurrentHashMap 作为红包活动缓存 ;

2、数据库查询所有的红包活动 , 并将活动信息存储在 Map 中 ;

3、定时任务每隔 30 秒 ,执行缓存加载方法,刷新缓存。

为什么红包系统会将红包活动信息存储在本地内存 ConcurrentHashMap 呢 ?

  • 红包系统是高并发应用,快速将请求结果响应给前端,大大提升用户体验;

  • 红包活动数量并不多,就算全部放入到 Map 里也不会产生内存溢出的问题;

  • 定时任务刷新缓存并不会影响红包系统的业务。

笔者见过很多单体应用 都使用这种方案,该方案的特点是简洁易用,工程实现也容易 。

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

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

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

2 本地缓存框架

虽然使用 JDK Map 能快捷构建缓存,但缓存的功能还是比较孱弱的。

因为现实场景里,我们可能需要给缓存添加缓存统计 、过期失效 、淘汰策略 等功能。

于是,本地缓存框架 应运而生。

流行的 Java 缓存框架包括:Ehcache , Google Guava ,  Caffeine Cache 。

0c527ebdfc95ad5fe6e84806a68bdd74.png

下图展示了 Caffeine 框架的使用示例。

78f835799bc293d19b74fbbed5b2727b.png

虽然本地缓存框架的功能很强大,但是本地缓存的缺陷依然明显。

1、高并发的场景,应用重启之后,本地缓存就失效了,系统的负载就比较大 ,需要花较长的时间才能恢复;

2、每个应用节点都会维护自己的单独缓存,缓存同步比较头疼 。

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

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

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

3 分布式缓存

分布式缓存是指将缓存数据分布在多台机器上,以提高缓存容量和并发读写能力的缓存系统。分布式缓存通常由多台机器组成一个集群,每台机器上都运行着相同的缓存服务进程,缓存数据被均匀地分布在集群中的各个节点上。

Redis 是分布式缓存的首选,甚至我们一提到缓存,很多后端工程师首先想到的就它。

下图是神州专车订单的 Redis 集群架构 。将 Redis 集群拆分成四个分片,每个分片包含一主一从,主从可以切换。应用 A 根据不同的缓存 key 访问不同的分片。

b5f3e8a6b0eb8019c29e7a00be818e32.png

与本地缓存相比,分布式缓存具有以下优点:

1、容量和性能可扩展

通过增加集群中的机器数量,可以扩展缓存的容量和并发读写能力。同时,缓存数据对于应用来讲都是共享的。

2、高可用性

由于数据被分布在多台机器上,即使其中一台机器故障,缓存服务也能继续提供服务。

但是分布式缓存的缺点同样不容忽视。

1、网络延迟

分布式缓存通常需要通过网络通信来进行数据读写,可能会出现网络延迟等问题,相对于本地缓存而言,响应时间更长。

2、复杂性

分布式缓存需要考虑序列化、数据分片、缓存大小等问题,相对于本地缓存而言更加复杂。


举一个真实的案例,这次案例让笔者对于分布式缓存的认知提上了另一个台阶。

2014年,同事开发了比分直播的系统,所有的请求都是从分布式缓存 Memcached 中获取后直接响应。常规情况下,从缓存中查询数据非常快,但在线用户稍微多一点,整个系统就会特别卡。

通过 jstat 命令发现 GC 频率极高,几次请求就将新生代占满了,而且 CPU 的消耗都在 GC 线程上。初步判断是缓存值过大导致的,果不其然,缓存大小在 300k 到 500k 左右。

解决过程还比较波折,分为两个步骤:

  1. 修改新生代大小 ,从原来的 2G 修改成 4G,并精简缓存数据大小 (从平均 300k 左右降为 80k 左右);

  2. 把缓存拆成两个部分 ,第一部分是全量数据 ,第二部分是增量数据 (数据量很小)。页面第一次请求拉取全量数据,当比分有变化的时候,通过 websocket 推送增量数据。

经过这次优化,笔者理解到:缓存虽然可以提升整体速度,但是在高并发场景下,缓存对象大小依然是需要关注的点,稍不留神就会产生事故。另外我们也需要合理地控制读取策略,最大程度减少 GC 的频率 , 从而提升整体性能。

4 多级缓存

开源中国网站最开始完全是用本地缓存框架 Ehcache 。后来随着访问量的激增,出现了一个可怕的问题:“因为 Java 程序更新很频繁,每次更新的时候都要重启。一旦重启后,整个 Ehcache 缓存里的数据都被清掉。重启后若大量访问进来的话,开源中国的数据库基本上很快就会崩掉”。

于是,开源中国开发了多级缓存框架  J2Cache ,使用了多级缓存 Ehcache + Redis 。

多级缓存有如下优势:

  1. 离用户越近,速度越快;

  2. 减少分布式缓存查询频率,降低序列化和反序列化的 CPU 消耗;

  3. 大幅度减少网络 IO 以及带宽消耗。

本地缓存做为一级缓存,分布式缓存做为二级缓存,首先从一级缓存中查询,若能查询到数据则直接返回,否则从二级缓存中查询,若二级缓存中可以查询到数据,则回填到一级缓存中,并返回数据。若二级缓存也查询不到,则从数据源中查询,将结果分别回填到一级缓存,二级缓存中。

32c3a570cf2763027365d83f686ba200.png

2018年,笔者服务的一家电商公司需要进行 app 首页接口的性能优化。笔者花了大概两天的时间完成了整个方案,采取的是两级缓存模式,同时利用了 Guava 的惰性加载机制,整体架构如下图所示:

0223e02d1c2c14fa3c26a2ec07936f13.png

缓存读取流程如下:

1、业务网关刚启动时,本地缓存没有数据,读取 Redis 缓存,如果 Redis 缓存也没数据,则通过 RPC 调用导购服务读取数据,然后再将数据写入本地缓存和 Redis 中;若 Redis 缓存不为空,则将缓存数据写入本地缓存中。

2、由于步骤1已经对本地缓存预热,后续请求直接读取本地缓存,返回给用户端。

3、Guava 配置了 refresh 机制,每隔一段时间会调用自定义 LoadingCache 线程池(5个最大线程,5个核心线程)去导购服务同步数据到本地缓存和 Redis 中。

优化后,性能表现很好,平均耗时在 5ms 左右。最开始我以为出现问题的几率很小,可是有一天晚上,突然发现 app 端首页显示的数据时而相同,时而不同。

也就是说:虽然 LoadingCache 线程一直在调用接口更新缓存信息,但是各个 服务器本地缓存中的数据并非完成一致。说明了两个很重要的点:

1、惰性加载仍然可能造成多台机器的数据不一致

2、LoadingCache 线程池数量配置的不太合理,  导致了线程堆积

最终,我们的解决方案是:

1、惰性加载结合消息机制来更新缓存数据,也就是:当导购服务的配置发生变化时,通知业务网关重新拉取数据,更新缓存。

2、适当调大 LoadigCache 的线程池参数,并在线程池埋点,监控线程池的使用情况,当线程繁忙时能发出告警,然后动态修改线程池参数。

5 总结

Fred Brooks 在 1987 年所发表的一篇关于软件工程的经典论文《没有银弹:软件工程的本质性与附属性工作 》。

论文强调真正的银弹并不存在,而所谓的银弹则是指没有任何一项技术或方法可以能让软件工程的生产力在十年内提高十倍。

通俗来讲:在技术领域中没有一种通用的解决方案可以解决所有问题 。技术本质上是为了解决问题而存在的,每个问题都有其独特的环境和限制条件,没有一种通用的技术或工具可以完美地解决所有问题。

缓存是把双刃剑 ,一方面我们享受缓存带来的系统性能提升,另一方面引入缓存会提高系统复杂度,因为你要考虑缓存的失效、更新、一致性等问题。

在面临缓存选型时,一定要结合业务场景,研发效率,运维成本,人力模型,技术储备等因素,做出合理的选择。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

72090e8fe2b5ac250d374caca4fe9e89.png

已在知识星球更新源码解析如下:

a0af11d2986e72b1b1a8ecb504cf11ee.jpeg

33e8ffbf79bb539f7654658b7a579b79.jpeg

2977b840aa972ff2ad92a804d3ff9163.jpeg

d1163125597ec320dbe3a5f505a3e89f.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
毫米波雷达SLAM实战:如何在无GPS的地下隧道实现精准三维建图(附Python代码) 本文深入探讨了在无GPS信号的地下隧道等拒止环境中,如何利用毫米波雷达SLAM技术实现精准三维建图。文章系统分析了毫米波雷达点云数据稀疏、噪声多的挑战,并提供了从环境配置、核心算法(如特征提取、ICP配准、回环检测)到全局优化的完整实战解决方案,附有可运行的Python代码,帮助开发者构建鲁棒的自主导航与建图系统。 阅读详情

相关推荐

从FinFET到NanoSheet:一文看懂TSMC制程技术的架构革命与性能飞跃

本文深入解析了TSMC从FinFET到NanoSheet的制程技术架构革命。FinFET通过三维立体结构解决了短沟道效应,而NanoSheet采用环绕栅极设计,实现了更强的静电控制与设计灵活性,结合EUV光刻技术,共同推动了芯片性能与能效的飞跃,为下一代AI与高性能计算奠定基石。

i3j4k5的博客 291

【JVM虚拟机面试宝典】JVM的内存结构是怎么样的?在JVM中会发生内存溢出的区域有那些?— day06

【JVM虚拟机面试宝典】JVM的内存结构是怎么样的?在JVM中会发生内存溢出的区域有那些?— day06

四季轮换叶,一路招摇胜 993

​​EMC设计实战:BCI(大电流注入)硬件设计规范

LIN接口:集成TVS管(如PESD5V0S1BL)与共模电感,耐压等级≥20V(8kV HBM)。输入滤波:10μF电解电容(100kHz截止)+ 0.1μF陶瓷电容(10MHz截止)。​:全频段(1MHz-400MHz)注入200mA电流,LIN通信误码率<0.01%。对敏感电路(如传感器)采用隔离电源(如ADuM5000),隔离电压≥3kVrms。​:连续波(CW)与调幅波(AM,调制深度80%)组合,覆盖全频段干扰场景。​:选择低频段(1-100MHz)感量≥100μH的绕线电感,抑制共模电流。

m0_50833886的博客 1941

芋道源码 yudao-cloud:构建全功能后台管理系统的技术探索

芋道,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。master分支为 JDK 8 + Spring Boot 2.7,分支为 JDK 17/21 + Spring Boot 3.2管理后台的电脑端:Vue3 提供 element-plus、vben(ant-design-vue) 两个版本,Vue2 提供 element-ui 版本管理后台的移动端:采用 uni-app 方案,一份代码多终端适配,同时支持 APP、小程序、H5!

chenchuang0128的博客 1万+

【OpenAirInterface知识-3】OAI端到端部署之5G基站部署

在上一篇文章中已经完成了5G核心网的部署,本篇文章将介绍OAI 5G基站的部署过程。

起名字难上难的博客 3424

基于h5端的视频录制和回溯功能的实现

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

发果叁 3193

使用 Docker 高效搭建本地开发环境(详细教程)

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT 源码解析作业调度中间件 E...

芋艿V 216

华为防火墙 策略路由实现多ISP接入Internet

PC3上网访问Server1时走ISP1。PC4上网访问Server1时走ISP2。1、配置接口IP并加入相应的安全区域。二、ISP2 运营商 R2路由器。一、ISP1 运营商 R1路由器。

一直被模仿,从未被超越 3016

看看人家的快速开发平台,确实清新优雅!

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT 源码解析作业调度中间件 E...

芋艿V 2917

uniapp(全端兼容) - 超详细接入百度统计功能,给uniapp全平台各端项目引入添加百度统计的插件统计流量详细的步骤教程(提供最新的百度统计源代码,详细的示例代码复制粘贴即可快速搞定!)

uniapp h5 app 使用加入接入百度统计,uniapp百度统计,uniapp详细引入百度统计的教程,uniapp接入百度统计,uniapp如何实现加入百度的统计代码,uniapp怎么植入百度统计详细教程,uniapp如何对接百度统计,uniapp百度统计不生效怎么办,uniapp使用百度统计不生效常见问题,uniapp引入百度统计后没有生效,uniapp h5网页接入百度统计代码后没有统计无效,uniapp最新接入使用百度统计功能,uniapp增加百度统计代码,uniapp app如何接入百度统计,

✅ 精选推荐文章 1万+

ABAP Cloud 里的高性能日志:CL_BALI_LOG、XCO_CP_BAL 与 AML 的性能对比与选型指南

ABAP日志框架性能对比与选型建议 本文对比了ABAP Cloud中三种主流日志框架的性能表现与适用场景: 标准BALI:适合明确需要保存日志的场景,保存路径短,性能较好 XCO_CP_BAL:API更现代但性能较低,适合需要即时可见性的场景 AML:开源框架,擅长消息缓冲,适合批量处理时暂存消息 测试结果表明: 纯记录不落库时AML最快 需要保存时BALI性能最优 second connection不提升性能但能确保日志留存 选型建议: 高频批处理优先AML+BALI组合 故障诊断考虑XCO的即时可见性

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 217

真正带你搞懂 RecyclerView 的缓存机制,再也不怕面试被虐了

本文由 寒鸦飞尽 授权投稿https://juejin.im/post/5d6382cd6fb9a06b2262c0f8前言RecyclerView大概是Android开...

singwhatiwanna 4817

关于Fluent模拟中的动态接触角UDF实现,详细设计

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1147

真正带你搞懂-RecyclerView-的缓存机制,再也不怕面试被虐了

mState.mLayoutStep = State.STEP_ANIMATIONS; onExitLayoutOrScroll(); resumeRequestLayout(false); } 这里onLayoutChildren方法是必走的,而mLayout是RV的成员变量,也就是LayoutManager,接下来我们去LinearLayoutManager里看看onLayoutChildren方法做了什么。 //已省略无关代码 @Override public void onLayoutChildre

m0_65321931的博客 3937

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

表弟面试被虐,我教他缓存连招,借机蹭了波奈雪的茶

“表哥,我被虐了。” 表弟哭丧着脸对我说道。 “怎么滴,连跪?哥带你飞。” “不是LOL,我这不秋招嘛,那个面试官逮着我问,把我都问蒙了。” 表弟一脸懵逼,怀疑人生。 “问啥了啊,我平时教你的唱跳 rap 都没用上?” 我幸灾乐祸的说道,这小子平时就跟我吹,什么天天考第一,进大厂跟玩儿一样,这不傻了吧。 “一来就问我一堆缓存的问题,什么是缓存啊,什么读写策略,如何保证缓存高可用啊,缓存穿透怎么处理啊,巴拉巴拉一堆,我记不得了都,整得我脑瓜子疼,你说我一个还没毕业的憨憨,至于嘛?” 表弟闷闷不乐,继续说道:

yes 329

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Discuz! 的主动缓存和被动缓存

Discuz!的缓存同样分了主动缓存和被动缓存。本文详细讲述两种缓存的异同!Discuz!的缓存同样分了主动缓存和被动缓存。从功能上来说,主动缓存一般用到管理员对全站的设置,等等需要手动更新的地方,这些地方的数据都有一个特点,那就是它们的更新可能性很小,平时不需要自动更新;Discuz!的被动缓存,一般分布在诸如帖子内容显示,用户信息更新这些地方,这些地方的更新基本上都是因为用户使

风随意静 3131

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

在Discuz!NT中进行缓存分层(本地缓存+memcached)(转)

在以前的两篇文章(Discuz!NT 缓存设计简析,Discuz!NT中集成Memcached分布式缓存)中,介绍了Discuz!NT中的缓存设计思路以及如何引入Memcached,当然前者是IIS进程的缓存(本地缓存),后者是分布式内存对象缓存系统。 两者通过Discuz!NT中的memcached.config文件中的ApplyMemCached结点的值来决定使用哪一种缓存方...

weixin_30355437的博客 84

Discuz!NT 缓存设计简析 [代震军原创]

Discuz!NT 缓存设计简析 [原创]        作为一个社区类型软件,大并发支持和高效稳定运行永远是“硬道理”,而有效安全的使用缓存恰恰能起到事倍功半的效果。而.NET本身所提供的缓存机制又显得过于“单薄”,比如说订制不太灵活方便, 缓存对象之间层次感不强, 使用时缺乏统一的管理等等。             Discuz!NT缓存产生背景:         在去年五月份我加

ulark的专栏 872

Discuz!NT中集成Memcached分布式缓存

      大约在两年前我写过一篇关于Discuz!NT缓存架构的文章,在那篇文章的结尾介绍了在IIS中如果开启多个应用程序池会造成多个缓存实例之间数据同步的问题。虽然给出了一个解决方案,但无形中却把压力转移到了磁盘I/O上(多个进程并发访问cache.config文件)。其实从那时起我就开始关注有什么更好的方案,当然今天本文中所说的Memcached,以及Velocity等这类

daizhj的专栏 1121

在Discuz!NT中进行缓存分层(本地缓存+memcached)(转)

    在以前的两篇文章(Discuz!NT 缓存设计简析, Discuz!NT中集成Memcached分布式缓存)中,介绍了Discuz!NT中的缓存设计思路以及如何引入Memcached,当然前者是IIS进程的缓存(本地缓存),后者是分布式内存对象缓存系统。      两者通过Discuz!NT中的memcached.config文件中的ApplyMemCached结点的值来决定使用哪一种

ulark的专栏 633

Discuz!开发之写入文件缓存

Discuz!拥有一套良好的缓存机制。不管是内存缓存、数据库缓存还是文件缓存,都做的非常优秀!当开发的功能需要大量的或者频繁的在数据库里读取时,而且数据库查询语句很复杂时,如果用户刷新一次页面就读取一次,那么服务器是受不了的。所以适当的将部分数据缓存下来是很有必要的。 今天就给大家介绍一下如何操作文件缓存: 先看看文件缓存函数的定义,文件: /source/function/functi

土著人宁巴的Discuz!专栏 5740
上一篇: 优秀的代码都是如何分层的?看了直呼NB!
下一篇: 我说用count(*)统计行数,面试官让我回去等消息...
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值