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.rhkb.cn/news/307278.html

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

相关文章

C语言--结构体大小

基本数据类型占用的字节数分别为:char(1),short(2),int(4),long(4),long long(8),float(4),double(8)。 分析一下下面结构体占用的字节数。 struct A { int a; }; struct B { char a; int b; }; int main() { printf("sizeof(struct A)%d\n", sizeof(struct A));//测…

BI数据分析软件:行业趋势与功能特点剖析

随着数据量的爆炸性增长&#xff0c;企业对于数据的需求也日益迫切。BI数据分析软件作为帮助企业实现数据驱动决策的关键工具&#xff0c;在当前的商业环境中扮演着不可或缺的角色。本文将从行业趋势、功能特点以及适用场景等方面&#xff0c;深入剖析BI数据分析软件&#xff0…

Docker容器tomcat中文名文件404错误不一定是URIEncoding,有可能是LANG=zh_CN.UTF-8引起

使用Docker部署tomcat&#xff0c;出现中文名文件无法读取&#xff0c;访问就是404错误。在网上搜索一通&#xff0c;都说是在tomcat的配置文件server.xml中修改一下URIEncoding为utf-8就行&#xff0c;但是我怎么测试都不行。最终发现&#xff0c;是Docker启动时&#xff0c;传…

关于Excel中自动填充的功能,看这篇文章就差不多了

序言 这篇文章介绍了Excel的自动填充功能。你将学习如何在Excel 365、2021、2019、2016、2013及更低版本中填充一系列数字、日期和其他数据,创建和使用自定义列表。这篇文章还让你确保你知道关于填充柄的一切,因为你可能会惊讶于这个小选项的强大。 当时间紧迫时,每一分钟…

(二)ffmpeg 下载安装以及拉流推流示例

一、ffmpeg下载安装 官网&#xff1a;https://www.ffmpeg.org/ 源码下载地址&#xff1a;https://www.ffmpeg.org/download.html#releases 下载源码压缩包 下载完成之后解压并在该目录下打开命令窗口 安装依赖环境&#xff1a; sudo apt-get install build-essential nasm …

防火墙操作!

当小编在Linux服务器上部署好程序以后&#xff0c;但是输入URL出现下述情况&#xff0c;原来是防火墙的原因&#xff01;&#xff01; 下面是一些防火墙操作&#xff01; 为保证系统安全&#xff0c;服务器的防火墙不建议关闭&#xff01;&#xff01; 但是&#xff0c;我们可…

麒麟 V10 离线 安装 k8s 和kuboard

目录 安装文件准备 主机准备 主机配置 修改主机名&#xff08;三个节点分别执行&#xff09; 配置hosts&#xff08;所有节点&#xff09; 关闭防火墙、selinux、swap、dnsmasq(所有节点) 安装依赖包&#xff08;所有节点&#xff09; 系统参数设置(所有节点) 时间同步…

C#Winform使用扩展方法自定义富文本框(RichTextBox)字体颜色

实现效果 调用方法 rtxtLog.AppendTextColorful(richTextBox1,DateTime.Now.ToString(), Color.Red); 完整代码如下 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using Sys…

CentOS7 boa服务器的搭建和配置

环境是CentOS7&#xff0c;但方法不局限于此版系统&#xff0c;应该是通用的。 具体步骤如下&#xff1a; 1. 下载boa源码 下载地址: Boa Webserver 下载后&#xff0c;进入压缩包所在目录&#xff0c;进行解压&#xff1a; tar xzf boa-0.94.13.tar.gz 2. 安装需要的工具b…

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

虚拟路由器冗余协议&#xff08;VRRP&#xff09;是一种被广泛使用的网络协议&#xff0c;旨在增强网络的可靠性和可用性。对于网络管理员和工程师来说&#xff0c;了解VRRP是确保网络能够实现无缝故障转移和保持不间断连接的关键。本文将深入探讨VRRP的基础知识&#xff0c;包…

研究了一款Vue2开发的Markdown编辑器

最近突然喜欢开始写作了&#xff0c;写笔记&#xff0c;写日记&#xff0c;写总结&#xff0c;各种写。所以&#xff0c;想要打造一个自己喜欢的编辑器&#xff0c;于是开始研究。 首先来看看我从Github丄扒拉到的这个开源的代码&#xff1a; 运行起来以后效果是这样的&…

Vue项目打包配置生产环境去掉console.log语句的方法

一、Vue2项目 使用webpack内置的 terser 工具&#xff0c;在vue.config.js文件加上相应的配置即可。 二、Vue3项目 同样是使用 terser 工具&#xff0c;不过vite没有内置terser&#xff0c;需要手动安装依赖 安装完后在vite.config.js文件加上相应的配置即可。 2024-4-9

Xlinx相关原语讲解导航页面

原语就是对FPGA底层器件的直接调用&#xff0c;与IP功能是类似的&#xff0c;将原语的参数变成IP配置时的GUI界面参数&#xff0c;可能会更加直观。IP的缺陷在于繁杂&#xff0c;比如SelectIO IP内部包含IDDR、ODDR等等IO转换的功能&#xff0c;如果只想使用单沿转双沿一个功能…

多因子模型的数据处理

优质博文&#xff1a;IT-BLOG-CN 数据处理的基本目的是从多量的、可能是杂乱无章的、难以理解的数据中抽取并推导出有价值、有意义的数据。特别是金融数据&#xff0c;存在数据缺失&#xff0c;不完整以及极端异常值等问题&#xff0c;对于我们的分析和建模影响很多。 对于我…

逆境突围:网络安全寒冬下售前的效率革命与AI赋能

在网络安全行业遭遇严冬之际&#xff0c;众多企业纷纷勒紧裤腰带&#xff0c;竭力挺过这段寒风凛冽的时期。我朋友所在的网安公司亦未能幸免于这场经济寒潮的冲击&#xff0c;不少售前同事无奈地提前踏上了职业生涯的“毕业”之路&#xff0c;留下的人则如同雪中孤松&#xff0…

【配电网故障定位】基于二进制蝗虫优化算法的配电网故障定位 12节点配电系统故障定位【Matlab代码#75】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 配电网故障定位2. 二进制蝗虫优化算法3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1a;资源获取】 1. 配电网故障定位 配电系统故障定位&#xff0c;即在配电网络发生故障的时候&am…

视频号小店究竟有什么秘密,值得商家疯狂入驻,商家必看!

大家好&#xff0c;我是电商花花。 我们都知道视频号和抖音本身都是一个短视频平台&#xff0c;但是随着直播电商的发展&#xff0c;背后的流量推动逐步显露出强大的红利市场和变现机会。 视频号小店流量大和赚钱之外&#xff0c;还非常适合普通人创业。 这也使得越来越多的…

【JAVA基础篇教学】第十篇:Java中Map详解说明

博主打算从0-1讲解下java基础教学&#xff0c;今天教学第十篇&#xff1a;Java中Map详解说明。 在 Java 编程中&#xff0c;Map 接口代表了一种键值对的集合&#xff0c;每个键对应一个值。Map 接口提供了一系列操作方法&#xff0c;可以方便地对键值对进行增删改查等操作。本…

【微信小程序】canvas开发笔记

【微信小程序】canvasToTempFilePath:fail fail canvas is empty 看说明书 最好是先看一下官方文档点此前往 如果是canvas 2d 写canvas: this.canvas,&#xff0c;如果是旧版写canvasId: ***, 解决问题 修改对应的代码&#xff0c;如下所示&#xff0c;然后再试试运行&#x…

要不是情绪管理得当,我早就拍桌子走人了!!!——早读(逆天打工人爬取热门微信文章解读)

身体是革命的本钱&#xff01; 引言Python 代码第一篇 洞见 中年以后 换个脾气 就是换条命第二篇 人民日报 来了新闻早班车要闻社会政策 结尾 年轻时的我们 如同初生的牛犊 无所畏惧 岁月却教会我们谨慎与尊重 引言 左边突出的神经开始疯狂跳动 不断提醒我 你该休息了 不能再放…