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

news2025/6/11 7:59:38

缓存架构

京东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 {

    @PostConstruct
    public 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 {

    @Autowired
    private 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; // 商品ID
    private 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 {

    @Autowired
    private ProductDao productDao;

    @Autowired
    private 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;
        }
        //热点缓存重建,加分布式锁,第一个请求写缓存成功后,做二次读取缓存操作,其余请求都可以走Redis
        RLock 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);
    }

    @Bean
    public 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.coloradmin.cn/o/2405979.html

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

相关文章

基于规则的自然语言处理

基于规则的自然语言处理 规则方法形态还原&#xff08;针对英语、德语、法语等&#xff09;中文分词切分歧义分词方法歧义字段消歧方法分词带来的问题 词性标注命名实体分类机器翻译规则方法的问题 规则方法 以规则形式表示语言知识&#xff0c;强调人对语言知识的理性整理&am…

使用MounRiver Studio Ⅱ软件写一个CH592F芯片的ADC采集程序,碰到的问题

MounRiver Studio Ⅱ 默认是不开启浮点计算的&#xff0c;所以有些浮点功能不能用&#xff0c;碰到问题是 while (1) {DelayMs (100);tmp Read_Temperature (0);sprintf (tempBuffer, "temp:%.2f\r\n", tmp); // 格式化温度值到字符串。使用%f要开启相应的…

简约商务年终工作总结报告PPT模版分享

简约精致扁平化商务通用动画PPT模版&#xff0c;简约大气素雅商务PPT模版&#xff0c;商务PPT模版&#xff0c;商业计划书PPT模版&#xff0c;IOS风商务通用PPT模版&#xff0c;公司介绍企业宣传PPT模版&#xff0c;创业融资PPT模版&#xff0c;创意低多边形PPT模版&#xff0c…

深度学习学习率优化方法——pytorch中各类warm up策略

warm-up具体原理以及为什么这么做在之前的博客有介绍&#xff0c;这里直接介绍如何直接使用pytorch中的warm-up策略&#xff0c;在pytorch中对于warm-up所有支持的方法都有描述&#xff0c;可以直接阅读1。 深度学习中各类学习率优化方法(AdaGrad/RMSprop/Adam/Warm-UP)原理及其…

分类数据集 - 场景分类数据集下载

数据集介绍&#xff1a;自然场景分类数据集&#xff0c;真实场景高质量图片数据&#xff1b;适用实际项目应用&#xff1a;自然场景下场景分类项目&#xff0c;以及作为通用场景分类数据集场景数据的补充&#xff1b;数据集类别&#xff1a;buildings、forest、glacier、mounta…

leetcode.多数元素

169. 多数元素 - 力扣&#xff08;LeetCode&#xff09; import java.util.HashMap;public class LeetCode169 {public int majorityElement(int[] nums) {int count nums.length;int res count/2;Scanner scanner new Scanner(System.in);HashMap<Integer,Integer> …

Server - 使用 Docker 配置 PyTorch 研发环境

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/148421901 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 建议使…

2025年渗透测试面试题总结-腾讯[实习]安全研究员(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]安全研究员 1. 自我介绍 2. SQL二次注入原理 3. 二次注入修复方案 4. SQL注入绕WAF&#xff…

01-VMware16虚拟机详细安装

官网地址&#xff1a;https://www.vmware.com/cn.html 1.1 打开下载好的 .exe 文件&#xff0c; 双击安装。 1.2 点击下一步 1.3 先勾选我接受许可协议中的条款&#xff0c;然后点击下一步 1.4 自定义安装路径&#xff0c;注意这里的文件路径尽量不要包含中文&#xff0c;完成…

sql列中数据通过逗号分割的集合,按需求剔除部分值

前置 不会REGEXP 方法的需要在这里学习一下下 记sql字段逗号分隔&#xff0c;通过list查询 功能点 现有一个表格中一列存储的是标签的集合&#xff0c;通过逗号分割 入下&#xff1a; 其中tag_ids是逗号分割的标签&#xff0c;现在需要删除标签组中的一些标签&#xff0c;因…

下一代设备健康管理解决方案:基于多源异构数据融合的智能运维架构

导语&#xff1a; 在工业4.0深度演进的关键节点&#xff0c;传统设备管理面临数据孤岛、误诊率高、运维滞后三大致命瓶颈。本文解析基于边缘智能与数字孪生的新一代解决方案架构&#xff0c;并实测验证中讯烛龙PHM-X系统如何通过多模态感知→智能诊断→自主决策闭环&#xff0c…

深入理解JavaScript设计模式之闭包与高阶函数

目录 前言小序一场失败面试面试后的觉醒 闭包变量作用域&#xff1a;谁的地盘听谁的变量的生命周期&#xff1a;该走了&#xff0c;不该走的还在闭包的更多作用&#xff1a;不只是谈恋爱&#xff0c;还能干活&#xff01;1、封装私有变量&#xff1a;你的变量我来守护2、延长变…

springboot启动mapper找不到方法对应的xml

数据源配置 目录结构 idea中mapper.java 可以找到对应的mapper.xml文件 启动却找不到 因为mapper.db1会被识别为文件名 而非目录结构 调整为这种

MQTT协议:物联网时代的通信基石

MQTT协议&#xff1a;物联网时代的通信基石 在当今快速发展的物联网&#xff08;IoT&#xff09;时代&#xff0c;设备之间的通信变得尤为重要。MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;协议作为一种轻量级的消息传输协议&#xff0c;正逐渐成为物联…

Electron通信流程

前言 今天讲Electron框架的通信流程&#xff0c;首先我们需要知道为什么需要通信。这得益于Electron的多进程模型&#xff0c;它主要模仿chrome的多进程模型如下图&#xff1a; 作为应用开发者&#xff0c;我们将控制两种类型的进程&#xff1a;主进程和渲染器进程 。 …

华为云Flexus+DeepSeek征文|华为云Flexus服务器dify平台通过自然语言转sql并执行实现电商数据分析

目录 前言 1 华为云Flexus服务器部署Dify平台 1.1 华为云Flexus服务器一键部署Dify平台 1.2 设置账号登录Dify&#xff0c;进入平台 2 构建自然语言转SQL并执行的应用 2.1 创建应用并启动工作流设计 2.2 应用框架设计 2.3 自然语言转SQL模块详解 2.4 代码执行模块实现…

IDEA中微服务指定端口启动

在使用IDEA开发SpringBoot微服务时&#xff0c;经常需要开启多个服务实例以测试负载均衡&#xff0c;以下几种方法开启不同端口。 直接在配置文件中指定 # application.propertiesserver.port8001指定VM参数 点击Modify options&#xff0c;选择Add VM options&#xff0c;值…

Spring Cloud Alibaba Seata安装+微服务实战

目录 介绍核心功能三层核心架构安装微服务实战创建三个业务数据库编写库存和账户两个Feign接口订单微服务 seata-order-service9701库存微服务 seata-store-service9702账户微服务 seata-account-service9703测试结果 总结 介绍 Spring Cloud Alibaba Seata 是一款开源的分布式…

FMC STM32H7 SDRAM

如何无痛使用片外SDRAM? stm32 已经成功初始化了 STM32H7 上的外部 SDRAM&#xff08;32MB&#xff09; 如何在开发中无痛使用SDRAM 使它像普通 RAM 一样“自然地”使用? [todo] 重要 MMT(Memory Management Tool) of STM32CubeMx The Memory Management Tool (MMT) disp…

部署DNS从服务器

部署DNS从服务器的目的 DNS域名解析服务中&#xff0c;从服务器可以从主服务器上获得指定的区域数据文件&#xff0c;从而起到备份解析记录与负载均衡的作用&#xff0c;因此通过部署从服务器可以减轻主服务器的负载压力&#xff0c;还可以提升用户的查询效率。 注意&#xf…