Springboot 多级缓存设计与实现

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java全栈-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站AI学习网站。   

目录

前言

冗余设计理念

多级缓存概述

开启浏览器缓存

① 配置 Cache-Control

② 配置 Expires

③ 配置 ETag

④ 配置 Last-Modified

整体配置

2.2 开启 Nginx 缓存

① 定义缓存配置

② 启用缓存

③ 设置缓存有效期

④ 配置反向代理

⑤ 重新加载配置

2.3 使用分布式缓存

① 添加依赖

② 配置 Redis 连接信息

③ 启动缓存

④ 使用缓存

2.4 使用本地缓存

① 添加依赖

② 配置 Caffeine 缓存

③ 自定义 Caffeine 配置类(可选步骤)

④ 开启缓存

⑤ 使用注解进行缓存操作


前言

对于高并发系统来说,有三个重要的机制来保障其高效运行,它们分别是:缓存、限流和熔断。而缓存是排在最前面也是高并发系统之所以高效运行的关键手段,那么问题来了:缓存只使用 Redis 就够了吗?

冗余设计理念

当然不是,不要把所有鸡蛋放到一个篮子里,成熟的系统在关键功能实现时一定会考虑冗余设计,注意这里的冗余设计不是贬义词。

冗余设计是在系统或设备完成任务起关键作用的地方,增加一套以上完成相同功能的功能通道(or 系统)、工作元件或部件,以保证当该部分出现故障时,系统或设备仍能正常工作,以减少系统或者设备的故障概率,提高系统可靠性。

例如,飞机的设计,飞机正常运行只需要两个发动机,但在每台飞机的设计中可能至少会设计四个发动机,这就有冗余设计的典型使用场景,这样设计的目的是为了保证极端情况下,如果有一个或两个发动机出现故障,不会因为某个发动机的故障而引起重大的安全事故。

多级缓存概述

缓存功能的设计也是一样,我们在高并发系统中通常会使用多级缓存来保证其高效运行,其中的多级缓存就包含以下这些:

  1. 浏览器缓存:它的实现主要依靠 HTTP 协议中的缓存机制,当浏览器第一次请求一个资源时,服务器会将该资源的相关缓存规则(如 Cache-Control、Expires 等)一同返回给客户端,浏览器会根据这些规则来判断是否需要缓存该资源以及该资源的有效期。
  2. Nginx 缓存:在 Nginx 中配置中开启缓存功能。
  3. 分布式缓存:所有系统调用的中间件都是分布式缓存,如 Redis、MemCached 等。
  4. 本地缓存:JVM 层面,单系统运行期间在内存中产生的缓存,例如 Caffeine、Google Guava 等。

以下是它们的具体使用。

开启浏览器缓存

在 Java Web应用中,实现浏览器缓存可以使用 HttpServletResponse 对象来设置与缓存相关的响应头,以开启浏览器的缓存功能,它的具体实现分为以下几步。

① 配置 Cache-Control

Cache-Control 是 HTTP/1.1 中用于控制缓存策略的主要方式。它可以设置多个指令,如 max-age(定义资源的最大存活时间,单位秒)、no-cache(要求重新验证)、public(指示可以被任何缓存区缓存)、private(只能被单个用户私有缓存存储)等,设置如下:

response.setHeader("Cache-Control", "max-age=3600, public"); // 缓存一小时
② 配置 Expires

设置一个绝对的过期时间,超过这个时间点后浏览器将不再使用缓存的内容而向服务器请求新的资源,设置如下:

response.setDateHeader("Expires", System.currentTimeMillis() + 3600 * 1000); // 缓存一小时
③ 配置 ETag

ETag(实体标签)一种验证机制,它为每个版本的资源生成一个唯一标识符。当客户端发起请求时,会携带上先前接收到的 ETag,服务器根据 ETag 判断资源是否已更新,若未更新则返回 304 Not Modified 状态码,通知浏览器继续使用本地缓存,设置如下:

String etag = generateETagForContent(); // 根据内容生成ETag
response.setHeader("ETag", etag);
④ 配置 Last-Modified

指定资源最后修改的时间戳,浏览器下次请求时会带上 If-Modified-Since 头,服务器对比时间戳决定是否返回新内容或发送 304 状态码,设置如下:

long lastModifiedDate = getLastModifiedDate();
response.setDateHeader("Last-Modified", lastModifiedDate);
整体配置

在 Spring Web 框架中,可以通过 HttpServletResponse 对象来设置这些头信息。例如,在过滤器中设置响应头以启用缓存:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletResponse httpResponse = (HttpServletResponse) response;// 设置缓存策略httpResponse.setHeader("Cache-Control", "max-age=3600");// 其他响应头设置...chain.doFilter(request, response);
}

以上就是在 Java Web 应用程序中利用 HTTP 协议特性控制浏览器缓存的基本方法。

开启 Nginx 缓存

Nginx 中开启缓存的配置总共有以下 5 步。

① 定义缓存配置

在 Nginx 配置中定义一个缓存路径和配置,通过 proxy_cache_path 指令完成,例如,以下配置:

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;

其中:

  • /path/to/cache:这是缓存文件的存放路径。
  • levels=1:2:定义缓存目录的层级结构。
  • keys_zone=my_cache:10m:定义一个名为 my_cache 的共享内存区域,大小为 10MB。
  • max_size=10g:设置缓存的最大大小为 10GB。
  • inactive=60m:如果在 60 分钟内没有被访问,缓存将被清理。
  • use_temp_path=off:避免在文件系统中进行不必要的数据拷贝。
② 启用缓存

在 server 或 location 块中,使用 proxy_cache 指令来启用缓存,并指定要使用的 keys zone,例如,以下配置:

server {  ...  location / {  proxy_cache my_cache;  ...  }  
}
③ 设置缓存有效期

使用 proxy_cache_valid 指令来设置哪些响应码的缓存时间,例如,以下配置:

location / {  proxy_cache my_cache;  proxy_cache_valid 200 304 12h;  proxy_cache_valid any 1m;  ...  
}
④ 配置反向代理

确保你已经配置了反向代理,以便 Nginx 可以将请求转发到后端服务器。例如,以下配置:

location / {  proxy_pass http://backend_server;  ...  
}
⑤ 重新加载配置

保存并关闭 Nginx 配置文件后,使用 nginx -s reload 命令重新加载配置,使更改生效。

Redis+Caffeine实现应用层二级缓存

在SpringBoot中实现多级缓存需要解决两个关键问题:缓存数据的读取顺序和数据的一致性。以下是实现多级缓存的步骤:

导入依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency><!-- 其他依赖 -->
</dependencies>
编写redis相关配置:
spring:redis:host: 127.0.0.1port: 6379password:jedis:pool:max-active: 8max-wait: -1max-idle: 500min-idle: 0lettuce:shutdown-timeout: 0
本地缓存配置类
/*** 本地缓存Caffeine配置类*/
@Configuration
public class LocalCacheConfiguration {@Bean("localCacheManager")public Cache<String, Object> localCacheManager() {return Caffeine.newBuilder()//写入或者更新5s后,缓存过期并失效, 实际项目中肯定不会那么短时间就过期,根据具体情况设置即可.expireAfterWrite(5, TimeUnit.SECONDS)// 初始的缓存空间大小.initialCapacity(50)// 缓存的最大条数,通过 Window TinyLfu算法控制整个缓存大小.maximumSize(500)//打开数据收集功能.recordStats().build();}}
Redis客户端配置类:
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();//关联template.setConnectionFactory(factory);//设置key的序列化方式
//        template.setKeySerializer();//设置value的序列化方式
//        template.setValueSerializer();return template;}
}
编写测试用的服务类接口:
public interface UserService {void add(User user);User getById(String id);User update(User user);void deleteById(String id);
}
编写测试用的服务类:

这里本地缓存也可以用注解式缓存来实现,这里就不细写啦~

import com.alibaba.fastjson.JSON;
import com.github.benmanes.caffeine.cache.Cache;
import com.wsh.springboot_caffeine.entity.User;
import com.wsh.springboot_caffeine.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.TimeUnit;@Service
public class UserServiceImpl implements UserService {/*** 模拟数据库存储数据*/private static HashMap<String, User> userMap = new HashMap<>();private final RedisTemplate<String, Object> redisTemplate;private final Cache<String, Object> caffeineCache;@Autowiredpublic UserServiceImpl(RedisTemplate<String, Object> redisTemplate,@Qualifier("localCacheManager") Cache<String, Object> caffeineCache) {this.redisTemplate = redisTemplate;this.caffeineCache = caffeineCache;}static {userMap.put("1", new User("1", "zhangsan"));userMap.put("2", new User("2", "lisi"));userMap.put("3", new User("3", "wangwu"));userMap.put("4", new User("4", "zhaoliu"));}@Overridepublic void add(User user) {// 1.保存Caffeine缓存caffeineCache.put(user.getId(), user);// 2.保存redis缓存redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);// 3.保存数据库(模拟)userMap.put(user.getId(), user);}@Overridepublic User getById(String id) {// 1.先从Caffeine缓存中读取Object o = caffeineCache.getIfPresent(id);if (Objects.nonNull(o)) {System.out.println("从Caffeine中查询到数据...");return (User) o;}// 2.如果缓存中不存在,则从Redis缓存中查找String jsonString = (String) redisTemplate.opsForValue().get(id);User user = JSON.parseObject(jsonString, User.class);if (Objects.nonNull(user)) {System.out.println("从Redis中查询到数据...");// 保存Caffeine缓存caffeineCache.put(user.getId(), user);return user;}// 3.如果Redis缓存中不存在,则从数据库中查询user = userMap.get(id);if (Objects.nonNull(user)) {// 保存Caffeine缓存caffeineCache.put(user.getId(), user);// 保存Redis缓存,20s后过期redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);}System.out.println("从数据库中查询到数据...");return user;}@Overridepublic User update(User user) {User oldUser = userMap.get(user.getId());oldUser.setName(user.getName());// 1.更新数据库userMap.put(oldUser.getId(), oldUser);// 2.更新Caffeine缓存caffeineCache.put(oldUser.getId(), oldUser);// 3.更新Redis数据库redisTemplate.opsForValue().set(oldUser.getId(), JSON.toJSONString(oldUser), 20, TimeUnit.SECONDS);return oldUser;}@Overridepublic void deleteById(String id) {// 1.删除数据库userMap.remove(id);// 2.删除Caffeine缓存caffeineCache.invalidate(id);// 3.删除Redis缓存redisTemplate.delete(id);}}

总结

多级缓存是提升高并发系统性能的关键策略之一。它不仅能够减少系统的响应时间,提高用户体验,还能有效降低后端系统的负载,防止系统过载。在实际应用中,开发者应根据系统的具体需求和资源情况,灵活设计和调整多级缓存策略,以达到最佳的性能表现。大部分情况下我们使用redis作为缓存是可以满足需求的,加入本地缓存后虽然带来了部分性能提升,但是存在数据一致性的问题,一定程度上添加了维护难度。

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

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

相关文章

StarRocks之扩容缩容

扩缩容 FE 集群 StarRocks FE 节点分为 Follower 节点和 Observer 节点。Follower 节点参与选举投票和写入&#xff0c;Observer 节点只用来同步日志&#xff0c;扩展读性能。 注意&#xff1a; 所有 FE 节点的 http_port 必须相同。 Follower FE 节点&#xff08;包括 Leader…

超真诚婚礼邀请函小程序

结婚了&#xff0c;自己写个婚礼邀请函小程序&#xff0c;含泪省下&#xffe5;49.9&#xff1b;程序员的浪漫&#xff01; 1、定位直达 2、背景音乐 3、倒计时 4、CSDN图床 页面代码如下&#xff1a; <cu-custom bgColor"bg-yellow-light" isBack"{{fal…

基于HT32的智能家居demo(蓝牙上位机)

参加合泰杯作品的部分展示&#xff0c;基于HT32的智能家居&#xff0c;这里展示灯光的相关控制&#xff0c;是用蓝牙进行的数据透传&#xff0c;参考了一些资料&#xff0c;美化封装了一下之前的上位机界面。 成果展示 点击主界面的蓝牙设置&#xff0c;进行连接&#xff0c;下…

Rust调用同级目录中的rs文件和调用下级目录中的rs文件

一、Rust调用同级目录中的rs文件 Rust新建工程demo02&#xff0c;src文件夹下面新建test.rs文件&#xff0c;这样main.rs文件与它属于同级目录中。 关键点&#xff1a;导入test文件和test文件中的Ellipse模块 mod test;//导入test模块&#xff08;文件&#xff09; use test…

iview碰到的一些问题总结

iview tabs嵌套使用问题 tabs嵌套使用的时候不是直接套用行了&#xff0c;直接套用会出现内层tab都集成到一级tab去&#xff0c;需要设置该属性指向对应 Tabs 的 name 字段(需要版本大于3.3.1) <Tabs name"tab1" ><TabPane label"标签1" tab&qu…

【程序员英语】【美语从头学】初级篇(入门)(笔记)Lesson 16 At the Shoe Store 在鞋店

《美语从头学初级入门篇》 注意&#xff1a;被 删除线 划掉的不一定不正确&#xff0c;只是不是标准答案。 文章目录 Lesson 16 At the Shoe Store 在鞋店对话A对话B笔记会话A会话B替换 Lesson 16 At the Shoe Store 在鞋店 对话A A: Do you have these shoes in size 8? B:…

FL Studio All Plugins Edition2024中文完整版Win/Mac

FL Studio All Plugins Edition&#xff0c;常被誉为数字音频工作站&#xff08;DAW&#xff09;的佼佼者&#xff0c;是音乐制作人和声音工程师钟爱的工具。它集音频录制、编辑、混音以及MIDI制作为一体&#xff0c;为用户提供了从创作到最终作品输出的完整工作流程。这个版本…

第 1 章 微信小程序与云开发从入门到实践从零开始做小程序——开发认识微信小程序

小北的参考工具书 小程序开发的图书并不少&#xff0c;这本书仍然值得你拥有&#xff01; 首先&#xff0c;这是一本全栈小程序开发教程&#xff0c;循序渐进&#xff0c;由浅入深&#xff0c;介绍了小程序开发你想了解的方方面面&#xff0c;包括近其小程序开发的各种新技术应…

如何避免在绩效考核评分时“脸盲”

对于大型企业来说&#xff0c;如何保证在绩效考核评分时准确快速地回忆起员工的日常表现&#xff0c;已经成为困扰管理者的难题。由于大型企业员工数量众多且业务模块繁杂&#xff0c;公司领导很难对每个员工的工作情况都了如指掌。这导致在绩效考核评分时&#xff0c;公司领导…

利用R语言进行典型相关分析实战

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

Python爬虫实战:从API获取数据

引言 在现代软件开发中&#xff0c;API已经成为获取数据的主要方式之一。API允许不同的软件应用程序相互通信&#xff0c;共享数据和功能。在本文中&#xff0c;我们将学习如何使用Python从API获取数据&#xff0c;并探讨其在实际应用中的价值。 目录 引言 二、API基础知识 …

辽宁博学优晨教育视频剪辑培训探索学习新意义

在当今数字化快速发展的时代&#xff0c;视频已成为信息传达的重要载体。辽宁博学优晨教育视频剪辑培训应运而生&#xff0c;不仅为学员提供了专业的学习平台&#xff0c;更在探索学习的意义方面赋予了新的内涵。 视频剪辑作为现代媒体行业的重要技能&#xff0c;其学习意义不仅…

【计算机图形学】Where2Act: From Pixels to Actions for Articulated 3D Objects

文章目录 1.论文做了件什么事儿2. 论文为什么要做这件事3. 介绍Introduction4. 相关工作预测语义表达推理几何和物体属性从被动观察中学习Affordance从交互中学习感知 5. 问题陈述6. 方法6.1 网络模块主干特征提取器可运动性评分模块运动建议模块运动评分模块 6.2 训练数据收集…

编曲学习:高叠和弦 挂留和弦 和弦实战应用

高叠和弦 挂留和弦 和弦实战应用小鹅通-专注内容付费的技术服务商https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65d4826fe4b04c10a1310517?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv 七和弦 以三和弦举例,三和弦上面叠一个三度的音,就变成了七和弦。 从下到…

消息中间件篇之Kafka-高可用机制

一、 集群模式 1. Kafka的服务器端由被称为Broker的服务进程构成&#xff0c;即一个Kafka集群由多个Broker组成。 2. 这样如果集群中某一台机器宕机&#xff0c;其他机器上的 Broker 也依然能够对外提供服务。这其实就是 Kafka 提供高可用的手段之一。 二、分区备份机制 1. 一个…

英语四级,求职的必备项?【文章底部添加可得内推码汇总表】

目录 英语四级&#xff1a;求职的砝码 提升职业竞争力 国内外机会双丰收 系统备考英语四级 实际运用英语 强调英语四级证书 在当今全球化的职场中&#xff0c;英语四级证书愈发成为求职的必备项。它不仅仅是一张证书&#xff0c;更是通向更广阔职业世界的关键通行证。本文…

【自然语言处理四-从矩阵操作角度看 自注意self attention】

自然语言处理四-从矩阵操作角度看 自注意self attention 从矩阵角度看self attention获取Q K V矩阵注意力分数softmax注意力的输出再来分析整体的attention的矩阵操作过程从矩阵操作角度看&#xff0c;self attention如何解决问题的&#xff1f;W^q^ W^k^ W^v^这三个矩阵怎么获…

前后端分离nodejs+vue高校教材征订系统47nia

本教材管理网站是为了提高用户查阅信息的效率和管理人员管理信息的工作效率&#xff0c;可以快速存储大量数据&#xff0c;还有信息检索功能&#xff0c;这大大的满足了学生、教师和管理员这三者的需求。操作简单易懂&#xff0c;合理分析各个模块的功能&#xff0c;尽可能优化…

Window系统本地搭建LightPicture网站并实现远程上传下载本地图片

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

leetcode 2867. 统计树中的合法路径数目【筛质数+贡献法】

原题链接&#xff1a;2867. 统计树中的合法路径数目 题目描述&#xff1a; 给你一棵 n 个节点的无向树&#xff0c;节点编号为 1 到 n 。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges &#xff0c;其中 edges[i] [ui, vi] 表示节点 ui 和 vi 在树中有一条边。 …