Redis+Caffeine两级缓存

news2025/1/18 11:45:33

1、前言

在高性能的服务架构设计中,缓存是一个不可或缺的环节。在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库。在提升访问速度的同时,也能降低数据库的压力。

随着不断的发展,这一架构也产生了改进,在一些场景下可能单纯使用Redis类的远程缓存已经不够了,还需要进一步配合本地缓存使用,例如Guava cache或Caffeine,从而再次提升程序的响应速度与服务性能。于是,就产生了使用本地缓存作为一级缓存,再加上远程缓存作为二级缓存的两级缓存架构。

在先不考虑并发等复杂问题的情况下,两级缓存的访问流程可以用下面这张图来表示:
在这里插入图片描述
优点与问题
那么,使用两级缓存相比单纯使用远程缓存,具有什么优势呢?
本地缓存基于本地环境的内存,访问速度非常快,对于一些变更频率低、实时性要求低的数据,可以放在本地缓存中,提升访问速度
使用本地缓存能够减少和Redis类的远程缓存间的数据交互,减少网络I/O开销,降低这一过程中在网络通信上的耗时
但是在设计中,还是要考虑一些问题的,例如数据一致性问题。首先,两级缓存与数据库的数据要保持一致,一旦数据发生了修改,在修改数据库的同时,本地缓存、远程缓存应该同步更新。

另外,如果是分布式环境下,一级缓存之间也会存在一致性问题,当一个节点下的本地缓存修改后,需要通知其他节点也刷新本地缓存中的数据,否则会出现读取到过期数据的情况,这一问题可以通过类似于Redis中的发布/订阅功能解决。

此外,缓存的过期时间、过期策略以及多线程访问的问题也都需要考虑进去,不过我们今天暂时先不考虑这些问题,先看一下如何简单高效的在代码中实现两级缓存的管理。

2、准备工作

在简单梳理了一下要面对的问题后,下面开始两级缓存的代码实战,我们整合号称最强本地缓存的Caffeine作为一级缓存、性能之王的Redis作为二级缓存。首先建一个springboot项目,引入缓存要用到的相关的依赖:

<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.1</version>
</dependency>
spring:
    redis:
        host: 127.0.0.1
        port: 6379
        database: 0
        timeout: 10000ms
        lettuce:
       pool:
       max-active: 8
       max-wait: -1ms
       max-idle: 8
       min-idle: 0

在下面的例子中,我们将使用RedisTemplate来对redis进行读写操作,RedisTemplate使用前需要配置一下ConnectionFactory和序列化方式,这一过程比较简单就不贴出代码了.
下面我们在单机环境下,将按照对业务侵入性的不同程度,分三个版本来实现两级缓存的使用。

3 V1.0版本

我们可以通过手动操作Caffeine中的Cache对象来缓存数据,它是一个类似Map的数据结构,以key作为索引,value存储数据。在使用Cache前,需要先配置一下相关参数:

 @Configuration
    public class CaffeineConfig {
        @Bean
        public Cache<String,Object> caffeineCache(){
            return Caffeine.newBuilder()
                .initialCapacity(128)//初始大小
                .maximumSize(1024)//最大数量
                .expireAfterWrite(60, TimeUnit.SECONDS)//过期时间
                .build();
        }
    }

简单解释一下Cache相关的几个参数的意义:
initialCapacity:初始缓存空大小
maximumSize:缓存的最大数量,设置这个值可以避免出现内存溢出
expireAfterWrite:指定缓存的过期时间,是最后一次写操作后的一个时间,这里
此外,缓存的过期策略也可以通过expireAfterAccess或refreshAfterWrite指定。

在创建完成Cache后,我们就可以在业务代码中注入并使用它了。在没有使用任何缓存前,一个只有简单的Service层代码是下面这样的,只有crud操作:

@Service
@AllArgsConstructor
public class OrderServiceImpl implements OrderService {
        private final OrderMapper orderMapper;

        @Override
        public Order getOrderById(Long id) {
            Order order = orderMapper.selectOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getId, id));
            return order;
        }

        @Override
        public void updateOrder(Order order) {
            orderMapper.updateById(order);
        }

        @Override
        public void deleteOrder(Long id) {
            orderMapper.deleteById(id);
        }
    }

接下来,对上面的OrderService进行改造,在执行正常业务外再加上操作两级缓存的代码,先看改造后的查询操作:

    public Order getOrderById(Long id) {
        String key = CacheConstant.ORDER + id;
        Order order = (Order) cache.get(key,
            k -> {
                //先查询 Redis
                Object obj = redisTemplate.opsForValue().get(k);
                if (Objects.nonNull(obj)) {
                    log.info("get data from redis");
                    return obj;
                }

                // Redis没有则查询 DB
                log.info("get data from database");
                Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>()
                    .eq(Order::getId, id));
                redisTemplate.opsForValue().set(k, myOrder, 120, TimeUnit.SECONDS);
                return myOrder;
            });
        return order;
    }

在Cache的get方法中,会先从缓存中进行查找,如果找到缓存的值那么直接返回。如果没有找到则执行后面的方法,并把结果加入到缓存中。

因此上面的逻辑就是先查找Caffeine中的缓存,没有的话查找Redis,Redis再不命中则查询数据库,写入Redis缓存的操作需要手动写入,而Caffeine的写入由get方法自己完成。

在上面的例子中,设置Caffeine的过期时间为60秒,而Redis的过期时间为120秒,下面进行测试,首先看第一次接口调用时,进行了数据库的查询:

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

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

相关文章

Sui生态域名服务SuiNS正式开放域名竞拍

Sui Name Service&#xff08;SuiNSSui Name Service&#xff08;是Sui推出的开放且分布式域名服务。SuiNS使用户可以竞拍以.sui结尾的专属域名&#xff0c;以建立链上身份。 在上线之际&#xff0c;SuiNS推出实时竞拍&#xff0c;为用户提供公平获取Sui生态专属域名的机会。若…

YUM在线升级功能

文章目录 YUM在线升级功能利用YUM进行查询、安装、升级与删除功能查询功能使用案例 安装/升级功能删除功能 YUM的配置文件修改软件源产生的问题与解决之道使用案例 YUM的软件群组功能使用案例 全系统自动升级 管理的抉择&#xff1a;RPM还是Tarball基础服务案例&#xff1a;以A…

E8-怎么监听表单里的日期控件被修改过

起因 业务部门每周六例会&#xff0c;业务部门请假的&#xff0c;如果包含星期六&#xff0c;需要老板审批。 我思路是当开始日期或结束日期被修改时&#xff0c;判断请假日期中是否包括周六&#xff0c;根据是束包含周六&#xff0c;去设置某个控件的值&#xff0c;后续步骤…

【Zero to One系列】springcloud微服务集成nacos,形成分布式系统

前期回顾&#xff1a; 【Zero to One系列】在WSL linux系统上&#xff0c;使用docker运行Mysql与Nacos 1、Nacos配置设置 先在nacos创建命名空间&#xff0c;如下图操作&#xff1a; 创建完成后&#xff0c;服务列表和配置列表&#xff0c;就都会出现如图的tab&#xff1a; 然…

南京邮电大学电工电子基础B实验八(译码与动态显示电路)

文章目录 一、 实验目的二、 主要仪器设备及软件三、 实验原理四、 实验任务与设计过程实验任务&#xff1a;设计过程&#xff1a; 五、 实验步骤与仿真结果1&#xff0e;用BCD七段显示译码器显示本人学号的后四位&#xff08;0709&#xff09;2&#xff0e;用BCD七段显示译码器…

API电商 ERP 数据管理

没有 API&#xff0c;应用之间的通信将会被扼杀&#xff1b;软件开发者将不断重写并执行相同功能的软件&#xff1b;创新的脚步将会放缓。 API 随处可见。大到一个软件系统&#xff0c;小到几行程序&#xff0c;只要具备了一定的特征&#xff0c;都可以被称作 API。那么&#…

产品手册是团队营销的心脏,让企业宣传更上一层楼

产品手册是企业团队营销的重要工具之一&#xff0c;能够帮助企业更好地宣传产品并提高销售业绩。在现代企业市场竞争日益激烈的情况下&#xff0c;如何制作一份优秀的产品手册成为了企业团队营销的重要课题。本文将从以下几个方面探讨产品手册对团队营销的意义以及如何制作一份…

RocksDB笔记 -- 整体架构

RocksDB是由Facebook开发的存储引擎, 它最初的目标是用于快速存储, 特别是Flash存储. 一个基于C开发keys-values存储引擎库. 整体架构 RocksDB由这三个基本结构组成: memtable, sstfile 和 logfile. 其中: memtable是一个内存数据结构, 新的写入会插入到memtable中, 同时可选…

前端加密对抗——CDP远程调用Debug断点函数python代码实现

文章目录 前端加密对抗——CDP远程调用Debug断点函数python代码实现实现请求断点处函数mitmproxy实现加解密 前端加密对抗——CDP远程调用Debug断点函数python代码实现 前几天看了看tools推送的前端加密的文章&#xff1a;前端加密对抗Part2-通过CDP远程调用Debug断点函数觉得…

NR 5G 系统消息MIB和SIB详解

系统信息分类 系统信息与各个信道的映射图示&#xff1a; 在5G高层中&#xff0c;系统信息可以分为三类&#xff1a; 最少系统信息&#xff08;Minimum System Information&#xff0c;MSI&#xff09;&#xff1a; MSI包括MIB和RMSIMIB的RRC消息 MasterInformationBlockMI…

DTU902 工控机 边缘计算网关

边缘计算网关&#xff08;Edge Computing Gateway&#xff09;是一种连接边缘设备和云端服务器的中间件设备&#xff0c;它可以处理和存储大量的数据&#xff0c;提高数据处理和传输的效率。边缘计算网关可以将数据预处理和过滤&#xff0c;减少数据传输到云端的负荷&#xff0…

2023 最新版IntelliJ IDEA 2023.1创建Java Web前(vue3)后端(spring-boot3)分离 项目详细步骤(图文详解)

文章目录 &#x1f6a9; 接上篇&#x1f3f3;‍&#x1f308; 项目构建所需的相关工具JavaIDEAmavenNodeJSVueVisual Studio Code &#x1f30c; 后端项目创建详细步骤&#x1f6eb; 1、开始创建新项目&#x1f6eb; 2、输入项目名称、选择项目存储位置、项目管理工具&#xff…

HTTP 教程1

HTTP 协议一般指 HTTP&#xff08;超文本传输协议&#xff09;。 超文本传输协议&#xff08;英语&#xff1a;HyperText Transfer Protocol&#xff0c;缩写&#xff1a;HTTP&#xff09;是一种用于分布式、协作式和超媒体信息系统的应用层协议&#xff0c;是因特网上应用最为…

CPO技术重塑光模块:行业变革与突破

随着OpenAI的ChatGPT重磅面世&#xff0c;在短短时间内&#xff0c;内容生成式人工智能消费级应用掀起一波新的科技浪潮。ChatGPT用户数也在短短两个月内破亿,成为史上活跃用户破亿速度最快的软件。 可以预料的是,未来算力和数据需求将迎来爆发式的增长,且传统可插拔光模块技术…

3.2 动态规划算法的基本要素

博主简介&#xff1a;一个爱打游戏的计算机专业学生博主主页&#xff1a; 夏驰和徐策所属专栏&#xff1a;算法设计与分析 学习目标&#xff1a; 如果我要学习动态规划算法的基本要素&#xff0c;我会采取以下步骤&#xff1a; 1. 理解概念&#xff1a;首先&#xff0c;我会研…

【程序】基于matlab使用脉冲压缩估计范围和多普勒

一、前言 本例显示了脉冲压缩的效果&#xff0c;其中发射的脉冲被调制并与接收的信号相关联。雷达和声纳系统使用脉冲压缩&#xff0c;通过缩短回波持续时间来提高信噪比&#xff08;SNR&#xff09;和距离分辨率。此示例还演示了多普勒处理&#xff0c;其中目标的径向速度由目…

Windows10系统下YOLOv5配置(Tesla P40 24GB、CUDA10.2)

操作系统&#xff1a;Windows10 显卡&#xff1a;Tesla P40 24GB CUDA版本&#xff1a;10.2 YOLOv5版本&#xff1a;4.0 一、下载 CUDA&cuDNN 下载相应版本的CUDA按默认一路安装到底 下载相应版本的cuDNN&#xff0c;解压&#xff0c;将bin中、include中、lib中文件…

CV | ⑩分钟实现视频人脸情绪生成(论文+代码)

本博客主要讲解了Emotionally Enhanced Talking Face Generation&#xff08;情感增强的谈话人脸生成&#xff09;论文概括与项目实现&#xff0c;以及代码理解。 Emotionally Enhanced Talking Face Generation Paper :https://arxiv.org/pdf/2303.11548.pdf Code: GitHub - s…

java民俗传统文化宣传分享网站springboot+vue

本传统文化网站有管理员和用户两个角色&#xff0c;管理员有&#xff0c;个人中心&#xff0c;用户管理&#xff0c;文章类型管理&#xff0c;文章信息管理&#xff0c;投票信息管理&#xff0c;留言板管理&#xff0c;系统管理。用户有个人中心&#xff0c;我的收藏&#xff0…

C++数据结构:二叉树之三(二叉搜索树扩展)

文章目录 前言一、搜索父节点二、搜索子节点三、搜索前驱后继节点四、计算二叉树的高度五、测试总结 前言 我们接着写二叉树&#xff0c;在前文链接&#xff1a;《二叉树之二》中&#xff0c;我们生成了如下的有序二叉树&#xff0c;并且实现了插入、删除和四种遍历方法。今天…