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://xiahunao.cn/news/2812980.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Python算法题集_子集

Python算法题集_子集 题78&#xff1a;子集1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【递归】2) 改进版一【双层下标循环】3) 改进版二【双层枚举循环】4) 改进版三【双层下标循环位运算】5) 改进版四【单行双层循环位运算】6) 改进版五【…

算法沉淀——动态规划之子数组、子串系列(上)(leetcode真题剖析)

算法沉淀——动态规划之子数组、子串系列 01.最大子数组和02.环形子数组的最大和03.乘积最大子数组04.乘积为正数的最长子数组长度 01.最大子数组和 题目链接&#xff1a;https://leetcode.cn/problems/maximum-subarray/、 给你一个整数数组 nums &#xff0c;请你找出一个具…

golang学习6,glang的web的restful接口传参

1.get传参 //get请求 返回json 接口传参r.GET("/getJson/:id", controller.GetUserInfo) 1.2.接收处理 package controllerimport "github.com/gin-gonic/gin"func GetUserInfo(c *gin.Context) {_ c.Param("id")ReturnSucess(c, 200, &quo…

DSP,QX320F280049C,完整版使用手册,数据手册

32位双核CPU&#xff0c; 主频150MHz 支持FPU、VCU、TPU flash 1MB SRAM 500KB 3个3MSPS&#xff0c;12位 ADC 24个增强型ePWM 16个高分辨率HRPWM&#xff08;150PS&#xff09;

顶顶通呼叫中心中间件-如何使处于机器人话术中的通话手动转接到坐席分机上讲解(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件使用httpapi实现电话转接操作过程讲解(mod_cti基于FreeSWITCH) 需要了解呼叫中心中间件可以点以下链接了解顶顶通小孙 1、使用httpapi接口转接 一、打开web版的ccadmin并且找到接口测试 打开web-ccadmin并且登录&#xff0c;登录完成之后点击运维调试-再…

Linux使用Docker部署在线协作白板WBO并结合内网穿透发布公网远程访问

文章目录 前言1. 部署WBO白板2. 本地访问WBO白板3. Linux 安装cpolar4. 配置WBO公网访问地址5. 公网远程访问WBO白板6. 固定WBO白板公网地址 前言 WBO在线协作白板是一个自由和开源的在线协作白板&#xff0c;允许多个用户同时在一个虚拟的大型白板上画图。该白板对所有线上用…

【JavaEE】_前端POST请求借助form表单向后端传参

对于POST请求&#xff0c;可以通过body部分来传递参数&#xff1b; 对于通过form表单的方式将POST请求的参数传递给后端来说&#xff0c;body部分的格式就是query string的格式&#xff0c;即form表单&#xff1b; 此时请求报头部分有&#xff1a;Content-Type : application…

spring框架Bean的作用域?对需要保持会话状态的bean应使用prototype作用域?为啥?

当一个bean被定义为"prototype"作用域时&#xff0c;每次请求该bean时都会创建一个新的实例&#xff0c;而不是像"singleton"作用域那样共享同一个实例。 对于需要保持会话状态的bean&#xff0c;如果使用"singleton"作用域&#xff0c;会导致所…

MATLAB中的稀疏矩阵和密集矩阵

在MATLAB中&#xff0c;矩阵可以表示为密集或稀疏格式。通常&#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;而手机…

后端程序员入门react笔记(五)ajax请求

常见的ajax Ajax 最原始的方式&#xff0c;基于原生的js XmlHttpRequest 多个请求之间如果有先后关系&#xff0c;会存在很多层回调的问题&#xff0c;也是基于原生js Jquery Ajax 基于原生XHR封装&#xff0c;依赖Jquery框架&#xff0c;由jquery 框架去封装原生的XML(Xml)封…

GaussDB SQL调优:选择合适的分布列

一、背景 GaussDB是华为公司倾力打造的自研企业级分布式关系型数据库&#xff0c;该产品具备企业级复杂事务混合负载能力&#xff0c;同时支持优异的分布式事务&#xff0c;同城跨AZ部署&#xff0c;数据0丢失&#xff0c;支持1000扩展能力&#xff0c;PB级海量存储等企业级数…

策略模式:封装行为策略,灵活切换实现多态业务逻辑

文章目录 一、引言二、应用场景三、模式定义与实现四、优缺点分析总结 一、引言 ​ 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了算法族&#xff0c;并分别封装起来&#xff0c;让它们之间可以互相替换。这种模式使得算法的变化…

Java+SpringBoot+Vue+MySQL构建银行客户管理新平台

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

电感电流波形分析

电感电流波形分析 首先&#xff0c;当电感充电时候&#xff08;红色回路&#xff09;电感左右两端是左正右负 假设在初始状态下&#xff0c;电容两端电压是0V&#xff0c;可以看出来A点电位是400V&#xff0c;B和C两端电容也都是0V 根据电感表达式di/dtUL/L400V/L 所以看得出…

【OnlyOffice】 桌面应用编辑器,版本8.0已发布,PDF表单、RTL支持、Moodle集成、本地界面主题

ONLYOFFICE桌面编辑器v8.0是一款功能强大、易于使用的办公软件&#xff0c;适用于个人用户、企业团队和教育机构&#xff0c;帮助他们高效地处理文档工作并实现协作。无论是在Windows、macOS还是Linux平台上&#xff0c;ONLYOFFICE都能提供无缝的编辑和共享体验。 目录 ONLYOFF…

华为高级路由技术 2023-2024

2023-2024 一、2.26路由协议版本优先级和度量主和备路由最长匹配原则递归路由和默认路由 一、2.26 路由协议版本 &#xff08;1&#xff09;RIP&#xff1a; IPv4网&#xff1a;RIPv1&#xff0c;RIPv2&#xff08;v1和v2 不兼容&#xff09; IPv6网&#xff1a;RIPng(Next g…

【网站项目】475商城系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

羊大师解读,羊奶都拥有哪些特点?

羊大师解读&#xff0c;羊奶都拥有哪些特点&#xff1f; 羊奶除了上述提到的丰富营养成分和健康优势外&#xff0c;还有以下一些特点&#xff1a; 接近母乳&#xff1a;羊奶的分子结构与母乳非常相似&#xff0c;特别是其含有大量的乳清蛋白&#xff0c;这使得羊奶成为婴儿和…

动态规划课堂2-----路径问题

目录 引言&#xff1a; 例题1&#xff1a;不同路径 例题2&#xff1a;不同路径II 例题3&#xff1a;礼物的最⼤价值 例题4&#xff1a;下降路径最⼩和 例题5&#xff1a;最小路径和 结语&#xff1a; 引言&#xff1a; 在学习完动态规划斐波那契数列模型后&#xff0c;…