【Redis】笔记|第10节|京东HotKey实现多级缓存架构

缓存架构

京东HotKey架构

代码结构

代码详情

功能点:(如代码有错误,欢迎讨论纠正)

  1. 多级缓存,先查HotKey缓存,再查Redis,最后才查数据库
  2. 热点数据重建逻辑使用分布式锁,二次查询
  3. 更新缓存采用读写锁提升性能
  4. 利用HotKey系统推送热点Key到应用服务端(HotKey客户端角色)
  5. 适用读多写少的场景
/*** 常量**/
public class Constants {public static final String PRODUCT_CACHE = "product:cache:";public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;public static final String EMPTY_CACHE = "{}";public static final String LOCK_PRODUCT_HOT_CACHE_PREFIX = "lock:product:hot_cache:";public static final String LOCK_PRODUCT_UPDATE_PREFIX = "lock:product:update:";
}
/**初始化京东HotKey**/
@Component
public class HotkeyInitializer {@PostConstructpublic void initHotkey() {ClientStarter.Builder builder = new ClientStarter.Builder();// 注意,setAppName很重要,它和dashboard中相关规则是关联的。ClientStarter starter = builder.setAppName("redis-multi-cache").setEtcdServer("http://127.0.0.1:2379").setCaffeineSize(10).build();starter.startPipeline();}
}
/*** 控制器**/
@RestController
@RequestMapping("/api/product/")
public class ProductController {@Autowiredprivate ProductService productService;@PostMapping(value = "/create")public Product createProduct(@RequestBody Product product) {return productService.createProduct(product);}@PostMapping(value = "/update")public Product updateProduct(@RequestBody Product product) {return productService.updateProduct(product);}@GetMapping("/get/{productId}")public Product getProduct(@PathVariable Long productId) {return productService.getProduct(productId);}}
/*** DAO层**/
@Repository
public class ProductDao {public Product createProduct(Product product) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("创建商品成功");return product;}public Product updateProduct(Product product) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("修改商品成功");return product;}public Product getProduct(Long productId) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("查询商品成功");Product product = new Product();product.setId(productId);product.setName("test");return product;}}
/*** 商品实体**/
@Data
public class Product implements Serializable {private Long id; // 商品IDprivate String name; // 商品名称private String category; // 商品分类(如 "电子产品"、"图书")private BigDecimal price; // 价格(精确到分,使用BigDecimal避免浮点精度问题)private int stock; // 库存数量private String description; // 商品描述private LocalDateTime createTime; // 创建时间private LocalDateTime updateTime; // 更新时间
}
/*** 商品服务**/
@Service
public class ProductService {@Autowiredprivate ProductDao productDao;@Autowiredprivate Redisson redisson;//写锁public Product createProduct(Product product) {Product productResult = null;RReadWriteLock readWriteLock = redisson.getReadWriteLock(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId());RLock writeLock = readWriteLock.writeLock();writeLock.lock();try {productResult = productDao.createProduct(product);redisson.getBucket(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId()).set(JSON.toJSONString(productResult), Duration.ofSeconds(genProductCacheTimeout()));JdHotKeyStore.smartSet(Constants.PRODUCT_CACHE + productResult.getId(), productResult);} finally {writeLock.unlock();}return productResult;}//写锁public Product updateProduct(Product product) {Product productResult = null;RReadWriteLock readWriteLock = redisson.getReadWriteLock(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId());RLock writeLock = readWriteLock.writeLock();writeLock.lock();try {productResult = productDao.updateProduct(product);redisson.getBucket(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId()).set(JSON.toJSONString(productResult), Duration.ofSeconds(genProductCacheTimeout()));JdHotKeyStore.smartSet(Constants.PRODUCT_CACHE + productResult.getId(), productResult);} finally {writeLock.unlock();}return productResult;}public Product getProduct(Long productId) {Product product = null;String productCacheKey = Constants.PRODUCT_CACHE + productId;product = getProductFromCache(productCacheKey);if (product != null) {return product;}//热点缓存重建,加分布式锁,第一个请求写缓存成功后,做二次读取缓存操作,其余请求都可以走RedisRLock hotCacheLock = redisson.getLock(Constants.LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);hotCacheLock.lock();try {product = getProductFromCache(productCacheKey);if (product != null) {return product;}RReadWriteLock readWriteLock = redisson.getReadWriteLock(Constants.LOCK_PRODUCT_UPDATE_PREFIX + productId);RLock rLock = readWriteLock.readLock();rLock.lock();try {product = productDao.getProduct(productId);if (product != null) {redisson.getBucket(productCacheKey).set(JSON.toJSONString(product),Duration.ofSeconds(genProductCacheTimeout()));JdHotKeyStore.smartSet(productCacheKey, product);} else {redisson.getBucket(productCacheKey).set(Constants.EMPTY_CACHE, Duration.ofSeconds(genEmptyCacheTimeout()));}} finally {rLock.unlock();}} finally {hotCacheLock.unlock();}return product;}//随机超时时间,防止缓存击穿(失效)private Integer genProductCacheTimeout() {return Constants.PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;}//空数据的超时时间private Integer genEmptyCacheTimeout() {return 60 + new Random().nextInt(30);}//从缓存获取数据private Product getProductFromCache(String productCacheKey) {//检测是不是热点Key并上报if (JdHotKeyStore.isHotKey(productCacheKey)) {Object productObj = JdHotKeyStore.get(productCacheKey);if (productObj != null) {//如果是热点Key并且有值就直接返回return (Product) JdHotKeyStore.get(productCacheKey);} else {Product product = getProductFromRedis(productCacheKey);//按文档如果是热点Key,第一次返回的Value是空的,要主动设置一次到本地缓存JdHotKeyStore.smartSet(productCacheKey, product);return product;}} else {return getProductFromRedis(productCacheKey);}}private Product getProductFromRedis(String productCacheKey) {Product product = null;RBucket<String> bucket = redisson.getBucket(productCacheKey);String productStr = bucket.get();if (!StringUtils.isEmpty(productStr)) {if (Constants.EMPTY_CACHE.equals(productStr)) {bucket.expire(Duration.ofSeconds(genEmptyCacheTimeout()));return new Product();}product = JSON.parseObject(productStr, Product.class);bucket.expire(Duration.ofSeconds(genProductCacheTimeout())); //读延期}return product;}}
@SpringBootApplication
public class RedisMultiCacheApplication {public static void main(String[] args) {SpringApplication.run(RedisMultiCacheApplication.class, args);}@Beanpublic Redisson redisson() {// 此为单机模式Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);return (Redisson) Redisson.create(config);}}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.12</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example.mutilcache</groupId><artifactId>redis-multi-cache</artifactId><version>0.0.1-SNAPSHOT</version><name>redis-multi-cache</name><description>redis-multi-cache</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.jd.platform.hotkey</groupId><artifactId>hotkey-client</artifactId><version>0.0.4-SNAPSHOT</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.48.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></path></annotationProcessorPaths></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

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

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

相关文章

php apache构建 Web 服务器

虚拟机配置流程winsever2016配置Apache、Mysql、php_windows server 2016配置web服务器-CSDN博客 PHP 和 Apache 通过 ​​模块化协作​​ 共同构建 Web 服务器&#xff0c;以下是它们的交互机制和工作流程&#xff1a; ​​一、核心组件分工​​ 组件角色​​Apache​​Web …

二分查找排序讲解

一、二分查找&#xff08;Binary Search&#xff09; 核心思想&#xff1a; 前提&#xff1a;数组必须是 有序的&#xff08;比如从小到大排列&#xff09;。目标&#xff1a;在数组中快速找到某个数&#xff08;比如找 7&#xff09;。方法&#xff1a;每次排除一半的数&…

【Redis实战:缓存与消息队列的应用】

在现代互联网开发中&#xff0c;Redis 作为一款高性能的内存数据库&#xff0c;广泛应用于缓存和消息队列等场景。本文将深入探讨 Redis 在这两个领域的应用&#xff0c;并通过代码示例比较两个流行的框架&#xff08;Redis 和 RabbitMQ&#xff09;的特点与适用场景&#xff0…

[拓扑优化] 1.概述

常见的拓扑优化方法有&#xff1a;均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有&#xff1a;有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…

【openssl】升级为3.3.1,避免安全漏洞

本文档旨在形成 对Linux系统openssl版本进行升级 的搭建标准操作过程&#xff0c;搭建完成后&#xff0c;实现 openssl 达到3.3以上版本&#xff0c;避免安全漏洞 效果。 一、查看当前版本 版本不高于3.1的&#xff0c;均需要升级。 # 服务器上运行以下命令&#xff0c;查看…

基于正点原子阿波罗F429开发板的LWIP应用(6)——SNTP功能和lwiperf测速

说在开头 正点原子F429开发板主芯片采用的是STM32F429IGT6&#xff0c;网络PHY芯片采用的是LAN8720A(V1)和YT8512C(V2)&#xff0c;采用的是RMII连接&#xff0c;PHY_ADDR为0&#xff1b;在代码中将会对不同的芯片做出适配。 CubeMX版本&#xff1a;6.6.1&#xff1b; F4芯片组…

C:\Users\中文名修改为英文名

C:\Users\中文名修改为英文名 背景操作步骤 背景 买了台新电脑&#xff0c;初始化好不知道啥操作把自己的登录用户名改成了中文&#xff0c;有些安装的软件看见有中文直接就水土不服了。 操作步骤 以下称中文用户名为张三。 正常登录张三用户 进入用户管理页面修改用户名&a…

YOLOv12环境配置,手把手教你使用YOLOv12训练自己的数据集和推理(附YOLOv12网络结构图),全文最详细教程

文章目录 前言一、YOLOv12代码下载地址1.YOLOv12模型结构图 二、YOLO环境配置教程1.创建虚拟环境2.激活虚拟环境3.查询自己电脑可支持最高cuda版本是多少&#xff08;无显卡的同学可以跳过这个步骤&#xff09;4.pytorch安装5.验证 PyTorch GPU 是否可用&#xff08;没有显卡的…

ES6(ES2015)特性全解析

ES6&#xff08;ECMAScript 2015&#xff09;是 JavaScript 语言发展史上的一个重要里程碑&#xff0c;它引入了许多新的语法特性和功能&#xff0c;提升了代码的可读性、可维护性和开发效率。 1. 块级作用域变量&#xff1a;let 和 const ES6 引入了 let 和 const 关键字&am…

jvm 垃圾收集算法 详解

垃圾收集算法 分代收集理论 垃圾收集器的理论基础&#xff0c;它建立在两个分代假说之上&#xff1a; 弱分代假说&#xff1a;绝大多数对象都是朝生夕灭的。强分代假说&#xff1a;熬过越多次垃圾收集过程的对象就越难以消亡。 这两个分代假说共同奠定了多款常用的垃圾收集…

数字孪生+AR/VR的融合创新

目录 引言&#xff1a;工业元宇宙的兴起与技术基石数字孪生&#xff1a;工业元宇宙的数字底座 2.1 数字孪生的概念与关键要素 2.2 数字孪生在工业领域的应用 2.3 数字孪生的技术架构 (Mermaid Graph) AR/VR&#xff1a;工业元宇宙的沉浸式体验层 3.1 AR/VR 的概念与技术原理…

图解C#教程 第五版 第4章 类型、存储和变量 笔记

第4章 类型、存储和变量 笔记 4.1 C# 程序是一组类型声明 C程序是一组函数和数据类型&#xff0c;C程序是一组函数和类&#xff0c; 而C#程序是一组类型声明&#xff0c;具有如下特征&#xff1a; C# 程序或 DLL 的源代码是一组类型声明类型声明中必须有一个包含 Main 方法…

SpringBoot整合SSM

1. SSM整合步骤 今天带大家学习一下基于SpringBoot的SSM整合案例&#xff0c;话不多说&#xff0c;咱们开始&#xff0c;要实现SSM整合&#xff0c;有以下这么几步 导入依赖创建yml配置文件dao层静态页面测试类进行测试 1.1 导入依赖 <?xml version"1.0" enco…

多面体模型-学习笔记2

1&#xff09; 多面体模型被应用于解决程序变换问题&#xff0c;并有效地推动了程 序自动并行化等技术的发展。与传统的解决程序变换的方法相比&#xff0c;多面体模型 具有许多优势[5]。多面体模型提供了一种强大的抽象&#xff0c;将每个语句的动态语句执 行实例视作一个多面…

基于django+vue的健身房管理系统-vue

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.8数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;PyCharm 系统展示 会员信息管理 员工信息管理 会员卡类型管理 健身项目管理 会员卡管理 摘要 健身房管理…

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…

Spring AI中使用ChatMemory实现会话记忆功能

文章目录 1、需求2、ChatMemory中消息的存储位置3、实现步骤1、引入依赖2、配置Spring AI3、配置chatmemory4、java层传递conversaionId 4、验证5、完整代码6、参考文档 1、需求 我们知道大型语言模型 &#xff08;LLM&#xff09; 是无状态的&#xff0c;这就意味着他们不会保…

Java 高级泛型实战:8 个场景化编程技巧

文章目录 一、通配符高级应用&#xff1a;灵活处理类型关系二、泛型方法与类型推断三、泛型类的嵌套使用四、受限泛型与边界条件五、泛型与反射结合六、泛型在函数式接口中的应用七、类型擦除与桥接方法八、自定义泛型注解总结 在Java编程中&#xff0c;泛型不仅是类型安全的保…

[蓝桥杯 2024 国 B] 立定跳远

问题描述 在运动会上&#xff0c;小明从数轴的原点开始向正方向立定跳远。项目设置了 n 个检查点 a1,a2,...,an且 ai≥ai−1>0。小明必须先后跳跃到每个检查点上且只能跳跃到检查点上。同时&#xff0c;小明可以自行再增加 m 个检查点让自己跳得更轻松。在运动会前&#xf…

2025年全国I卷数学压轴题解答

第19题第3问: b b b 使得存在 t t t, 对于任意的 x x x, 5 cos ⁡ x − cos ⁡ ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx−cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min ⁡ t max ⁡ x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…