如何设计一个二级缓存(Redis+Caffeine)架构?Redis 6.0多线程模型如何工作?

news2025/5/20 20:03:56

一、二级缓存(Redis+Caffeine)架构设计

1. 设计目标

通过「本地缓存(Caffeine)+ 分布式缓存(Redis)」的分层结构,实现:

  • 低延迟:热点数据本地缓存(内存级访问),减少网络IO;
  • 高可用:分布式缓存避免单点故障;
  • 成本优化:本地缓存存储高频小数据,Redis存储全量数据,降低内存成本。
2. 核心组件
组件作用关键配置示例
Caffeine本地缓存(JVM内存),存储高频热点数据maximumSize=10000, expireAfterWrite=5m(大小+过期策略)
Redis分布式缓存(远程内存),存储全量数据,提供持久化启用RDB/AOF,设置合理maxmemory-policy(如LRU)
缓存加载策略缓存未命中时的加载逻辑(如同步加载、异步加载)同步加载:直接查DB;异步加载:先返回旧值+后台更新
一致性机制解决双写/失效不一致问题发布订阅(更新时通知其他节点清理本地缓存)、TTL(兜底)
3. 核心流程(读请求)
命中
未命中
命中
未命中
客户端请求
查本地缓存Caffeine
返回数据
查Redis
返回数据并更新本地缓存
查数据库
返回数据并更新Redis和本地缓存
4. 注意事项
  • 缓存穿透:对空值(如DB不存在的key)也缓存(设置短TTL);
  • 缓存击穿:热点key过期时,使用互斥锁(如Redis的setnx)避免大量请求打穿DB;
  • 内存控制:Caffeine通过maximumSizemaximumWeight限制内存,避免OOM;
  • 一致性:写操作时先更新DB,再删除/更新缓存(推荐「先删缓存,再更新DB」+ 延迟双删)。
方案痛点
一、核心不一致场景分析

可能导致不一致的典型场景:

  1. 写操作顺序问题:更新数据库后未及时更新/删除缓存,导致后续读请求读取到旧缓存值;
  2. 并发写冲突:多个线程同时更新数据库和缓存,导致缓存与数据库最终状态不一致;
  3. 本地缓存隔离:分布式系统中,不同节点的本地缓存(Caffeine)可能因未同步而持有旧数据。

二、缓存失效策略设计
1. 主动失效(优先策略)

通过显式删除/更新缓存确保一致性,适用于对一致性要求高的场景(如订单状态变更)。

流程设计(写操作)

graph TD
    A[更新数据库] --> B{删除Redis缓存}
    B --> C[发布「缓存失效」事件(Redis Pub/Sub)]
    C --> D[各节点订阅事件后删除本地Caffeine缓存]

关键实现细节

  • 先更新数据库,再删除缓存(而非先删缓存):避免因数据库更新失败导致缓存被删后无数据可用;
  • Redis Pub/Sub通知:写操作完成后,通过Redis发布事件(如频道cache_invalidate),内容为失效的key列表;
  • 本地缓存监听:每个服务节点订阅该频道,收到事件后立即调用Caffeine.invalidate(key)删除本地缓存。
2. 被动过期(兜底策略)

通过设置**TTL(生存时间)**作为最终一致性的保障,适用于对一致性要求稍低但性能敏感的场景(如商品详情)。

配置示例

  • Caffeine:设置expireAfterWrite=5m(写入5分钟后过期),避免长期持有旧数据;
  • Redis:设置TTL=10m(比Caffeine长,确保Redis过期前本地缓存已先过期),防止本地缓存失效后Redis仍存旧值。
3. 防并发不一致:延迟双删

针对高并发场景下“删除缓存→更新数据库→新请求读缓存”的时间窗口问题,采用延迟双删策略:
作用:
前置删除:减少用户读到脏数据的时间窗,比如只有后置删除的情况下,数据更新了,缓存还没更新,先删一次减少脏数据时间窗。
后置删除:这个删除才是缓存-数据库最终一致的关键,这里是保证数据库完成操作后,发出补偿操作删除缓存来达成最终一致性。
其实如果只保证最终一致性的话,只需要后删,可以看业务体验是否能接受加入前置删除。

// 伪代码示例(Java)
public void updateData(String key, Object newData) {
    // 1. 先删除本地缓存和Redis缓存
    caffeineCache.invalidate(key);
    redisTemplate.delete(key);

    // 2. 更新数据库
    database.update(newData);

    // 3. 延迟(如1秒)后再次删除缓存(避免步骤2执行期间有新请求将旧数据加载到缓存)
    new Thread(() -> {
        try {
            Thread.sleep(1000);
            caffeineCache.invalidate(key);
            redisTemplate.delete(key);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
}

三、一致性增强补充
  • 缓存加载互斥:读缓存未命中时,使用Redis分布式锁(如RedissonRLock)限制只有一个线程加载数据库数据,避免大量请求同时打穿数据库;
  • 空值缓存:对数据库不存在的key(缓存穿透场景),在Caffeine和Redis中缓存null(设置短TTL,如30秒),避免重复查询数据库;
  • 监控报警:通过Prometheus+Grafana监控缓存命中率、过期次数、Pub/Sub消息延迟,及时发现一致性异常。

四、代码示例(Spring Boot整合)
1. Caffeine配置(主动失效)
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
public class CaffeineConfig {
    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
                .maximumSize(10000) // 最大容量
                .expireAfterWrite(5, TimeUnit.MINUTES) // 写后5分钟过期(被动兜底)
                .build();
    }
}
2. Redis Pub/Sub监听(主动通知)
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;

@Component
public class RedisCacheListener implements MessageListener {
    @Resource
    private Cache<String, Object> caffeineCache;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String invalidKey = new String(message.getBody());
        // 收到事件后删除本地缓存
        caffeineCache.invalidate(invalidKey); 
    }
}
3. 写操作服务类(延迟双删)
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class DataService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private Cache<String, Object> caffeineCache;

    public void updateData(String key, Object newData) {
        // 1. 首次删除缓存
        caffeineCache.invalidate(key);
        redisTemplate.delete(key);

        // 2. 更新数据库(伪代码)
        database.update(newData);

        // 3. 延迟1秒后二次删除
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                caffeineCache.invalidate(key);
                redisTemplate.delete(key);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

二、Redis 6.0多线程模型解析

1. 背景:单线程的瓶颈

Redis 5.0及之前采用「单线程+IO多路复用」模型,优势是避免多线程竞争(如锁开销),但瓶颈在于:

  • 网络IO处理能力受限于单线程(尤其是高并发场景,如10万+ QPS);
  • 大键值对的序列化/反序列化可能阻塞主线程。
2. 多线程的核心设计

Redis 6.0引入多线程处理网络IO,但命令执行仍保持单线程(保证原子性)。具体流程:

graph TD
    A[主线程] --> B[监听端口,接收新连接]
    B --> C[将连接分配给IO线程池(默认4个线程)]
    C --> D[IO线程读取请求数据(read)并解析]
    D --> E[将解析后的命令放回主线程任务队列]
    E --> F[主线程单线程执行命令(核心逻辑)]
    F --> G[IO线程将响应写回客户端(write)]
3. 关键特性
  • IO线程仅处理网络IO:不参与命令执行,避免多线程竞争;
  • 可配置线程数:通过io-threads(默认4)和io-threads-do-reads(是否启用读IO多线程)控制;
  • 向后兼容:单线程模式仍可用(关闭io-threads-do-reads)。
4. 优势与限制
  • 优势:提升网络IO吞吐量(实测QPS可提升50%+);
  • 限制:命令执行仍是单线程,无法利用多核CPU处理计算密集型操作(如大量KEYS命令)。

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

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

相关文章

【OpenGL学习】(一)创建窗口

文章目录 【OpenGL学习】&#xff08;一&#xff09;创建窗口 【OpenGL学习】&#xff08;一&#xff09;创建窗口 GLFW OpenGL 本身只是一套图形渲染 API&#xff0c;不提供窗口创建、上下文管理或输入处理的功能。 GLFW 是一个支持创建窗口、处理键盘鼠标输入和管理 OpenGL…

AI大语言模型评测体系演进与未来展望

随着人工智能技术的飞速发展,大语言模型(LLMs)已成为自然语言处理领域的核心研究方向。2025年最新行业报告显示,当前主流模型的评测体系已从单一任务评估转向多维度、全链路的能力剖析。例如,《全球首个大语言模型意识水平”识商”白盒DIKWP测评报告》通过数据、信息、知识…

微服务项目->在线oj系统(Java版 - 5)

相信自己,终会成功 微服务代码: lyyy-oj: 微服务 目录 C端代码 用户题目接口 修改后用户提交代码(应用版) 用户提交题目判题结果 代码沙箱 1. 代码沙箱的核心功能 2. 常见的代码沙箱实现方式 3. 代码沙箱的关键问题与解决方案 4. 你的代码如何与沙箱交互&#xff1f; …

get请求使用数组进行传参

get请求使用数组进行传参,无需添加中括号 mvc接口要添加参数名&#xff0c;使用array承接。不能用list, 否则会报错 这里是用apifox模拟前端调用。 前端调用代码 // 根据项目ID和角色ID查询相关审批人 export function findRelativeApproverByProjectIdAndRoleId(roleIds, p…

【MySQL成神之路】MySQL常用语法总结

目录 MySQL 语法总结 数据库操作 表操作 数据操作 查询语句 索引操作 约束 事务控制 视图操作 存储过程和函数 触发器 用户和权限管理 数据库操作 创建数据库&#xff1a; CREATE DATABASE database_name; 选择数据库&#xff1a; USE database_name; 删除数…

Linux动静态库制作与原理

什么是库 库是写好的现有的&#xff0c;成熟的&#xff0c;可以复用的代码。现实中每个程序都要依赖很多基础的底层库&#xff0c;不可能每个人的代码都从零开始&#xff0c;因此库的存在意义非同寻常。 本质上来说库是一种可执行代码的二进制形式&#xff0c;可以被操作系统…

ffmpeg 把一个视频复制3次

1. 起因&#xff0c; 目的: 前面我写过&#xff0c;使用 python 把一个视频复制3次但是速度太慢了&#xff0c;我想试试看能否改进。而且我想换一种新的视频处理思路&#xff0c;并试试看速度如何。 2. 先看效果 效果就是能行&#xff0c;而且速度也快。 3. 过程: 代码 1…

GPT/Claude3国内免费镜像站更新 亲测可用

无限次使用&#xff1a;无限制的提问次数&#xff0c;不设上限&#xff0c;随心所欲。 无需魔法、稳定流畅&#xff1a;操作简便&#xff0c;无需复杂设置&#xff0c;即可享受稳定流畅的服务。 手机和电脑均能用&#xff1a;轻松适配手机和电脑&#xff0c;使用体验更佳。 …

Python:操作Excel按行写入

Python按行写入Excel数据,5种实用方法大揭秘! 在日常的数据处理和分析工作中,我们经常需要将数据写入到Excel文件中。Python作为一门强大的编程语言,提供了多种库和方法来实现将数据按行写入Excel文件的功能。本文将详细介绍5种常见的Python按行写入Excel数据的方法,并附上…

Redis进阶知识

Redis 1.事务2. 主从复制2.1 如何启动多个Redis服务器2.2 监控主从节点的状态2.3 断开主从复制关系2.4 额外注意2.5拓扑结构2.6 复制过程2.6.1 数据同步 3.哨兵选举原理注意事项 4.集群4.1 数据分片算法4.2 故障检测 5. 缓存5.1 缓存问题 6. 分布式锁 1.事务 Redis的事务只能保…

12.vue整合springboot首页显示数据库表-实现按钮:【添加修改删除查询】

vue整合springboot首页显示数据库表&#xff1a;【添加修改删除查询】 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是node.js和vue的使用。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性。【帮帮志系…

bisheng系列(一)- 本地部署(Docker)

目录 一、导读 二、说明 1、镜像说明 2、本节内容 三、docker部署 1、克隆代码 2、运行镜像 3、可能的错误信息 四、页面测试 1、注册用户 2、登陆成功 3、添加模型 一、导读 环境&#xff1a;Ubuntu 24.04、Windows 11、WSL 2、Python 3.10 、bisheng 1.1.1 背景…

如何用Python批量解压ZIP文件?快速解决方案

如何用Python批量解压ZIP文件&#xff1f;快速解决方案 文章目录 **如何用Python批量解压ZIP文件&#xff1f;快速解决方案**代码结果详细解释 话不多说&#xff0c;先上干货&#xff01;&#xff01;&#xff01; 代码 import os import zipfiledef unzip_file(dir_path: str…

DriveGenVLM:基于视觉-语言模型的自动驾驶真实世界视频生成

《DriveGenVLM: Real-world Video Generation for Vision Language Model based Autonomous Driving》2024年8月发表&#xff0c;来自哥伦比亚大学的论文。 自动驾驶技术的进步需要越来越复杂的方法来理解和预测现实世界的场景。视觉语言模型&#xff08;VLM&#xff09;正在成…

企业标准信息公共服务平台已开放标准通编辑器访问入口

标准通 数字化标准编辑器 专业、高效、便捷 企业标准信息公共服务平台 近日&#xff0c;企业标准信息公共服务平台已开放标准通编辑器访问入口&#xff0c;可进入官网指定版块使用&#xff01; 核心功能亮点 解决企业痛点 传统标准编制&#xff0c;需反复核对格式、逐条…

进阶-数据结构部分:1、数据结构入门

飞书文档https://x509p6c8to.feishu.cn/wiki/HRLkwznHiiOgZqkqhLrcZNqVnLd 一、存储结构 顺序存储 链式存储 二、常用数据结构 2.1、栈 先进后出 场景&#xff1a; 后退/前进功能&#xff1a;网页浏览器中的后退和前进按钮可以使用栈来实现。在浏览网页时&#xff0c;每次…

React 19中useContext不需要Provider了。

文章目录 前言一、React 19中useContext移除了Provider&#xff1f;二、使用步骤总结 前言 在 React 19 中&#xff0c;useContext 的使用方式有所更新。开发者现在可以直接使用 作为提供者&#xff0c;而不再需要使用 <Context.Provider>。这一变化简化了代码结构&…

Json schema校验json字符串(networknt/json-schema-validator库)

学习链接 json-schema官网 - 英文 jsonschemavalidator 可在线校验网站 networknt的json-schema-validator github地址 networknt的json-schema-validator 个人gitee地址 - 里面有md文档说明和代码示例 JSON Schema 入门指南&#xff1a;如何定义和验证 JSON 数据结构 JS…

交易所开发:构建功能完备的金融基础设施全流程指南

交易所开发&#xff1a;构建功能完备的金融基础设施全流程指南 ——从技术架构到合规安全的系统性解决方案 一、开发流程&#xff1a;从需求分析到运维优化 开发一款功能完备的交易所需要遵循全生命周期管理理念&#xff0c;涵盖市场定位、技术实现、安全防护和持续迭代四大阶…

Axure疑难杂症:统计分析页面引入Echarts示例动态效果

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:统计分析页面引入Echarts示例动态效果 主要内容:echart示例引入、大小调整、数据导入 应用场景:统计分析页面…