Redis从入门到精通(十六)多级缓存(一)Caffeine、JVM进程缓存

文章目录

  • 第6章 多级缓存
    • 6.1 什么是多级缓存?
    • 6.2 搭建测试项目
      • 6.2.1 项目介绍
      • 6.2.2 新增商品表
      • 6.2.3 编写商品相关代码
      • 6.2.4 启动服务并测试
      • 6.2.5 导入商品查询页面,配置反向代理
    • 6.3 JVM进程缓存
      • 6.3.1 Caffeine
      • 6.3.2 实现JVM进程缓存
        • 6.3.2.1 需求分析
        • 6.3.2.2 代码实现

第6章 多级缓存

6.1 什么是多级缓存?

传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图:

但这样的策略存在以下两个问题:

  • 请求要经过Tomcat处理,那Tomcat的性能成为整个系统的瓶颈;
  • Redis缓存失效时,会对数据库产生冲击。

而多级缓存就是要充分利用请求处理的各个环节,分别添加缓存,减轻Tomcat压力,如图:

  • 1)浏览器访问静态资源时,优先读取浏览器本地缓存;访问非静态资源时,才访问服务端。
  • 2)请求到达Nginx后,优先读取Nginx本地缓存
  • 3)如果Nginx本地缓存未命中,则去查询Redis
  • 4)如果Redis查询未命中,则将请求发送到Tomcat,并优先查询JVM进程缓存
  • 5)如果JVM进程缓存未命中,则查询数据库

在以上多级缓存架构中,Nginx服务已经不再是一个反向代理服务器,而是一个需要编写本地缓存查询、Redis查询、Tomcat查询业务逻辑的业务服务器。

进一步优化,业务Nginx服务也会搭建集群来提高并发,还有专门的Nginx服务来做反向代理,同时Tomcat服务也会部署成集群模式,如图:

综上,多级缓存的关键有两个:

  • 1)在业务Nginx中编写业务逻辑,实现Nginx本地缓存、Redis、Tomcat的查询,这里会用到OpenResty框架结合Lua语言;
  • 2)在Tomcat中实现JVM进程缓存。

6.2 搭建测试项目

6.2.1 项目介绍

这里继续沿用【第4章 Redis实战】系列文章中编写的测试项目,代码下载地址见文末。↓↓↓

该项目的结构如下:

其中的业务包括:

  • 获取验证码;用户登录;用户签到;用户签到统计。
  • 查询商户列表;根据ID查询商户详情;新增商户;修改商户;图片上传。
  • 新增探店笔记;根据ID查询笔记详情;点赞/取消点赞功能;查询点赞排行榜;查询关注用户的探店笔记列表。
  • 关注或者取消关注功能;查询共同关注好友。
  • 添加普通优惠券;添加秒杀优惠券;秒杀下单。

6.2.2 新增商品表

在数据库创建一个商品表tb_item并初始化几条数据:

DROP TABLE IF EXISTS `tb_item`;
CREATE TABLE `tb_item` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',`title` VARCHAR(264) NOT NULL COMMENT '商品标题',`name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '商品名称',`price` BIGINT(20) NOT NULL COMMENT '价格(分)',`image` VARCHAR(200) NULL DEFAULT NULL COMMENT '商品图片',`category` VARCHAR(200) NULL DEFAULT NULL COMMENT '类目名称',`brand` VARCHAR(100) NULL DEFAULT NULL COMMENT '品牌名称',`spec` VARCHAR(200) NULL DEFAULT NULL COMMENT '规格',`status` INT(1) NOT NULL DEFAULT 1 COMMENT '商品状态 1-正常,2-下架,3-删除',`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,INDEX `status`(`status`) USING BTREE,INDEX `updated`(`update_time`) USING BTREE
) CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '商品表';INSERT INTO `tb_item` VALUES (1, 'RIMOWA 21寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4', 'SALSA AIR', 16900, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp', '拉杆箱', 'RIMOWA', '{\"颜色\": \"红色\", \"尺码\": \"26寸\"}', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (2, '安佳脱脂牛奶 新西兰进口轻欣脱脂250ml*24整箱装*2', '脱脂牛奶', 68600, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t25552/261/1180671662/383855/33da8faa/5b8cf792Neda8550c.jpg!q70.jpg.webp', '牛奶', '安佳', '{\"数量\": 24}', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (3, '唐狮新品牛仔裤女学生韩版宽松裤子 A款/中牛仔蓝(无绒款) 26', '韩版牛仔裤', 84600, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t26989/116/124520860/644643/173643ea/5b860864N6bfd95db.jpg!q70.jpg.webp', '牛仔裤', '唐狮', '{\"颜色\": \"蓝色\", \"尺码\": \"26\"}', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (4, '森马(senma)休闲鞋女2019春季新款韩版系带板鞋学生百搭平底女鞋 黄色 36', '休闲板鞋', 10400, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t1/29976/8/2947/65074/5c22dad6Ef54f0505/0b5fe8c5d9bf6c47.jpg!q70.jpg.webp', '休闲鞋', '森马', '{\"颜色\": \"白色\", \"尺码\": \"36\"}', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');
INSERT INTO `tb_item` VALUES (5, '花王(Merries)拉拉裤 M58片 中号尿不湿(6-11kg)(日本原装进口)', '拉拉裤', 38900, 'https://m.360buyimg.com/mobilecms/s720x720_jfs/t24370/119/1282321183/267273/b4be9a80/5b595759N7d92f931.jpg!q70.jpg.webp', '拉拉裤', '花王', '{\"型号\": \"XL\"}', 1, '2019-05-01 00:00:00', '2019-05-01 00:00:00');

再创建一个商品库存表tb_item_stock并初始化几条数据:

DROP TABLE IF EXISTS `tb_item_stock`;
CREATE TABLE `tb_item_stock`  (`item_id` BIGINT(20) NOT NULL COMMENT '商品id,关联tb_item表',`stock` INT(10) NOT NULL DEFAULT 9999 COMMENT '商品库存',`sold` INT(10) NOT NULL DEFAULT 0 COMMENT '商品销量',PRIMARY KEY (`item_id`) USING BTREE
) CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商品库存表';INSERT INTO `tb_item_stock` VALUES (1, 99996, 3219);
INSERT INTO `tb_item_stock` VALUES (2, 99999, 54981);
INSERT INTO `tb_item_stock` VALUES (3, 99999, 189);
INSERT INTO `tb_item_stock` VALUES (4, 99999, 974);
INSERT INTO `tb_item_stock` VALUES (5, 99999, 18649);

6.2.3 编写商品相关代码

首先创建tb_itemtb_item_stock表对应的实体类:

// com.star.redis.dzdp.pojo.Item/**** 商品* @author hsgx* @since 2024/4/12 10:01*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("tb_item")
public class Item implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.AUTO)private Long id;private String title;private String name;private Double price;private String image;private String category;private String brand;private String spec;private Integer status;private Date crateTime;private Date updateTime;}
// com.star.redis.dzdp.pojo.ItemStock/**** 商品库存表* @author hsgx* @since 2024/4/12 10:05*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("tb_item_stock")
public class ItemStock {private static final long serialVersionUID = 1L;@TableId(value = "item_id", type = IdType.AUTO)private Long itemId;private Integer stock;private Integer sold;}

然后创建商品Item实体对应的ItemController类-IItemService接口-ItemServiceImpl实现类-ItemMapper接口,创建商品库存ItemStock实体对应的IItemStockService接口-ItemStockServiceImpl实现类-ItemStockMapper接口,均使用MyBatis-Plus来实现。

接下来在ItemController类中实现两个接口,其接口文档和代码如下:

项目说明
功能根据ID查询商品信息
请求方法GET
请求路径/item/{id}
请求方法id:Long,商品ID
请求方法Item:商品信息
项目说明
功能根据ID查询商品库存信息
请求方法GET
请求路径/item/stock/{id}
请求方法id:Long,商品ID
请求方法Item:商品库存信息
// com.star.redis.dzdp.controller.ItemController@Slf4j
@RestController
@RequestMapping("/item")
public class ItemController {@Resourceprivate IItemService itemService;/*** 根据ID查询商品信息* @author hsgx* @since 2024/4/12 10:17* @param id* @return com.star.redis.dzdp.pojo.Item*/@GetMapping("/{id}")public Item queryById(@PathVariable("id") Long id) {return itemService.getById(id);}/*** 根据ID查询商品库存信息* @author xiaowd* @since 2024/4/12 14:22* @param id* @return com.star.redis.dzdp.pojo.ItemStock*/@GetMapping("/stock/{id}")public ItemStock queryStockById(@PathVariable("id") Long id) {return itemStockService.getById(id);}}

6.2.4 启动服务并测试

6.2.5 导入商品查询页面,配置反向代理

这里已经准备好了一个Nginx反向代理服务器和静态资源,下载地址见文末。↓↓↓

将该文件夹拷贝到一个非中文目录下,然后修改conf/nginx.conf文件以配置反向代理:

# nginx-1.18.0/conf/nginx.conf#user  nobody;
worker_processes  1;events {worker_connections  1024;
}http {include       mime.types;default_type  application/octet-stream;sendfile        on;#tcp_nopush     on;keepalive_timeout  65;server {listen       8082;server_name  localhost;location /api {proxy_pass http://127.0.0.1:8081/dzdp;}location / {root   html;index  index.html index.htm;}error_page   500 502 503 504  /50x.html;location = /50x.html {root   html;}}
}

打开cmd窗口,执行start nginx.exe命令运行服务。然后在浏览器访问http://localhost:8082/item.html?id=1,显示如下页面:

在上述页面中打开控制台,可以看到ajax向后台发起请求,并成功拿到数据:

至此,测试项目搭建完毕。

6.3 JVM进程缓存

6.3.1 Caffeine

缓存一般可以分为两类:

  • 分布式缓存,例如Redis:

    • 优点:存储容量更大、可靠性更好、可以在集群间共享
    • 缺点:访问缓存有网络开销
    • 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
  • 进程本地缓存,例如HashMap、GuavaCache:

    • 优点:读取本地内存,没有网络开销,速度更快
    • 缺点:存储容量有限、可靠性较低、无法共享
    • 场景:性能要求较高,缓存数据量较小

Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部使用的缓存就是Caffeine。

Caffeine的GitHub地址:https://github.com/ben-manes/caffeine

要使用Caffeine,首先要引入Caffeine的依赖:

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.7.0</version>
</dependency>

下面是Caffeine的基本API的使用案例:

@Test
public void testCaffeine() {// 构建cache对象Cache<String, String> cache = Caffeine.newBuilder().build();// 存数据cache.put("name", "Jack");// 取数据String name = cache.getIfPresent("name");System.out.println("name = " + name);// 取数据:先查缓存,如果未命中则执行Lambda表达式// 参数1:缓存的key// 参数2:Lambda表达式的参数即缓存的keyString age = cache.get("age", key -> {return "25";});System.out.println("age = " + age);
}

运行以上代码,结果如下:

name = Jack
age = 25

Caffeine提供了三种缓存清除策略:

  • 基于容量:设置缓存的数量上限
// 构建cache对象
Cache<String, String> cache = Caffeine.newBuilder().maximumSize(2) //设置缓存数量上限为2.build();
  • 基于时间:设置缓存的有效时间
Cache<String, String> cache = Caffeine.newBuilder()// 设置缓存有效期为10秒,从最后一次写入开始计时.expireAfterWrite(Duration.ofSeconds(10)).build();
  • 基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。

在默认情况下,当一个缓存元素过期时,Caffeine不会立即将其清除。而是在一次读或写操作后,或者在空闲时间完成对失效数据的清除。

6.3.2 实现JVM进程缓存

6.3.2.1 需求分析

利用Caffeine实现以下需求:

  • 给根据ID查询商品信息的业务添加缓存,缓存未命中时查询数据库;
  • 给根据ID查询商品库存信息的业务添加缓存,缓存未命中时查询数据库;
  • 缓存初始大小为100;
  • 缓存上限为10000。
6.3.2.2 代码实现

首先定义两个Caffeine缓存对象,分别保存商品信息、商品库存的缓存数据:

// com.star.redis.dzdp.config.CaffeineConfig/**** Caffeine缓存配置* @author hsgx* @since 2024/4/12 14:29*/
@Configuration
public class CaffeineConfig {@Beanpublic Cache<Long, Item> itemCache() {return Caffeine.newBuilder().initialCapacity(1000).maximumSize(10000).build();}@Beanpublic Cache<Long, ItemStock> itemStockCache() {return Caffeine.newBuilder().initialCapacity(1000).maximumSize(10000).build();}
}

接着修改ItemController类的queryById()方法和queryStockById()方法:

@Resource
private Cache<Long, Item> itemCache;
@Resource
private Cache<Long, ItemStock> itemStockCache;@GetMapping("/{id}")
public Item queryById(@PathVariable("id") Long id) {//return itemService.getById(id);// 添加缓存return itemCache.get(id, key -> {return itemService.getById(id);});
}@GetMapping("/stock/{id}")
public ItemStock queryStockById(@PathVariable("id") Long id) {//return itemStockService.getById(id);// 添加缓存return itemStockCache.get(id, key -> {return itemStockService.getById(id);});
}

本节完,下一节将正式进入多级缓存的实现。

本节所涉及的代码和资源可从git仓库下载:https://gitee.com/weidag/redis_learning.git

更多内容请查阅分类专栏:Redis从入门到精通

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析(已完结)
  • MyBatis3源码深度解析(已完结)
  • 再探Java为面试赋能(持续更新中…)

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

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

相关文章

基于springboot仿雀语的文档管理系统

项目介绍 本项目借鉴了雀语的一些UI设计&#xff0c;实现了文档在线管理的功能&#xff0c;知识库可以对不同分类的文档建立不同的库&#xff0c;知识库里面左边可以维护菜单菜单目录&#xff0c;右边实现在线预览。该项目可以防止用户下载和复制文档&#xff0c;只支持在线预…

TSINGSEE青犀AI智能分析网关V4吸烟/抽烟检测算法介绍及应用

抽烟检测AI算法是一种基于计算机视觉和深度学习技术的先进工具&#xff0c;旨在准确识别并监测个体是否抽烟。该算法通过训练大量图像数据&#xff0c;使模型能够识别出抽烟行为的关键特征&#xff0c;如烟雾、手部动作和口部形态等。 在原理上&#xff0c;抽烟检测AI算法主要…

Java 语言程序设计(基础篇)原书第10版 梁勇著 PDF 文字版电子书

简介 Java 语言程序设计&#xff08;基础篇&#xff09;原书第 10 版 是 Java 语言的经典教材&#xff0c;中文版分为基础篇和进阶篇&#xff0c;主要介绍程序设计基础、面向对象程序设计、GUI 程序设计、数据结构和算法、高级 Java 程序设计等内容。本书通过示例讲解问题求解…

蓝桥杯嵌入式(G431)备赛——最后一晚查漏补缺

蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——初始化cubeMX 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——LED 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——按键模块设计 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——LCD按键 蓝桥杯…

杂货铺 | Linux虚拟机Ubuntu操作系统下设置共享文件夹(以及找不到hgfs文件夹怎么办)

文章目录 &#x1f4da;步骤一&#xff1a;配置共享文件夹&#x1f4da;步骤二&#xff1a;配置挂载环境&#x1f4da;步骤三&#xff1a;解决权限问题&#x1f4da;步骤四&#xff1a;解决重启失效问题 &#x1f4da;步骤一&#xff1a;配置共享文件夹 建立本地共享文件夹&…

07.QT信号和槽-2

一、自定义信号和槽 在Qt中&#xff0c;允许⾃定义信号的发送⽅以及接收⽅&#xff0c;即可以⾃定义信号函数和槽函数。但是对于⾃定义的信号函数和槽函数有⼀定的书写规范。 1.基本语法 1.1 自定义信号 &#xff08;1&#xff09;⾃定义信号函数必须写到"signals"…

2024 DTC大会精彩演讲:DBdoctor,基于eBPF重新定义数据库可观测 (附PPT下载和演讲视频)

由中国DBA联盟&#xff08;ACDU&#xff09;和墨天轮社区联合主办的第十三届数据技术嘉年华&#xff08;DTC&#xff09;于北京盛大召开。4月13日上午海信聚好看云平台负责人张纪宽受邀在『数据库生态软件』分论坛发表主题演讲《DBdoctor&#xff1a;利用eBPF技术实现数据库智能…

微信小程序公共组件封装使用

1.在components目录下创建公共组件&#xff0c;以navbar为例 2.完成组件功能 3.调用&#xff0c;如果很多地方都会用到&#xff0c;建议放全局&#xff0c;如果不是则放在需要引用的文件中 3.1全局引用&#xff0c;在app.json做全局引用配置 3.2局部引用&#xff0c;在需要引入…

springcloud第4季 springcloud-alibaba之nacos篇

一 nacos 1.1 nacos作用介绍 nacos是一个分布式的配置中心和注册发现中心。 nacos是 dynamic naming configuration service nacosconfigbus 实现动态刷新&#xff1b;nacosconsul 1.2 各个注册中心对比 注册中心CAP模型控制台管理社区活跃度EureakaAp支持低zkcp不支持中…

初学python记录:力扣2923. 找到冠军 I

题目&#xff1a; 一场比赛中共有 n 支队伍&#xff0c;按从 0 到 n - 1 编号。 给你一个下标从 0 开始、大小为 n * n 的二维布尔矩阵 grid 。对于满足 0 < i, j < n - 1 且 i ! j 的所有 i, j &#xff1a;如果 grid[i][j] 1&#xff0c;那么 i 队比 j 队 强 &…

什么是队头阻塞以及如何解决

前言 通常我们提到队头阻塞&#xff0c;指的可能是TCP协议中的队头阻塞&#xff0c;但是HTTP1.1中也有一个类似TCP队头阻塞的问题&#xff0c;下面各自介绍一下。 TCP队头阻塞 队头阻塞&#xff08;head-of-line blocking&#xff09;发生在一个TCP分节丢失&#xff0c;导致…

Springboot+Vue项目-基于Java+MySQL的校园管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

裸机开发之汇编、寄存器

一、什么是汇编&#xff1f;为什么学汇编&#xff1f; 在之前写控制代码的时候就在想&#xff1a;底层是怎么控制的&#xff1f;后来经过学习知道之前所编写的代码都是应用层代码&#xff0c;顾名思义就是在系统写好的底层之上调用系统函数。原以为底层是指写系统写好的底层函数…

VRRP(虚拟路由冗余协议)详解

VRRP-------虚拟路由冗余协议 在一个网络中&#xff0c;要做为一个合格的网络首先就要具备几种冗余&#xff0c;增加网络的可靠性。 这几种冗余分别为&#xff1a;线路冗余&#xff0c;设备冗余&#xff0c;网关冗余&#xff0c;UPS冗余 VRRP该协议就是解决网关冗余的。在二层…

面经:MapReduce编程模型与优化策略详解

作为一名专注于大数据处理与分布式计算的博主&#xff0c;我深知MapReduce作为一款经典的分布式计算框架&#xff0c;在海量数据处理领域所起的关键作用。本篇博客将结合我个人的面试经历&#xff0c;深入剖析MapReduce编程模型与优化策略&#xff0c;分享面试必备知识点&#…

Vue 移动端(H5)项目怎么实现页面缓存(即列表页面进入详情返回后列表页面缓存且还原页面滚动条位置)keep-alive缓存及清除keep-alive缓存

一、需求 产品要求&#xff1a;Vue移动端项目进入列表页&#xff0c;列表页需要刷新&#xff0c;而从详情页返回列表页&#xff0c;列表页则需要缓存并且还原页面滚动条位置 二、实现思路 1、使用Vue中的keep-alive组件&#xff0c;keep-alive提供了路由缓存功能 2、因为我项…

自然语言处理NLP:文本预处理Text Pre-Processing

大家好&#xff0c;自然语言处理(NLP)是计算机科学领域与人工智能领域中的一个重要方向&#xff0c;其研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。本文将介绍文本预处理的本质、原理、应用等内容&#xff0c;助力自然语言处理和模型的生成使用。 1.文本…

MMU映射

MMU功能&#xff1a; 将虚拟地址转换物理地址 提供页属性&#xff0c;地址保护

K8S之Controller

我们在回顾下pod的启动流程&#xff1a; 用户通过kubectl&#xff0c;向api-server 发起请求api-server接受请求&#xff0c;并将数据写入etcdkube-scheduler通过watch检测到未绑定node 的pod&#xff0c;调度pod到某一node上&#xff0c;并通知给api-server&#xff0c;api-se…

Linux高级IO——多路转接之epoll

本章代码Gitee地址&#xff1a;EpollServer 文章目录 1. epoll接口1.1 epoll_create1.2 epoll_wait1.3 epoll_ctl 2. epoll原理3. epoll_server4. epoll两种工作模式 1. epoll接口 1.1 epoll_create #include <sys/epoll.h> int epoll_create(int size);参数int size理…