【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 {

    复制代码
     @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/>
    </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>
相关推荐
wenjie学长13 分钟前
[UE学习笔记]—划时代意义的两大功能—lumen和Nanite
笔记·学习·ue·三维数字化
摇滚侠23 分钟前
Spring Boot 3零基础教程,WEB 开发 国际化 Spring Boot + Thymeleaf 笔记45
spring boot·笔记·后端
你想考研啊30 分钟前
一、redis安装(单机)和使用
前端·数据库·redis
洲覆1 小时前
Redis 驱动适配 Reactor 模式
开发语言·网络·数据库·redis
缘友一世1 小时前
Redis未授权访问漏洞:从原理到高级利用
数据库·redis·缓存
三角叶蕨1 小时前
Redis极简入门 整合springboot
java·redis
Dontla1 小时前
React useCallback介绍(用来缓存函数的引用,避免每次渲染都重新创建函数)主要用于性能优化
react.js·缓存·性能优化
degen_1 小时前
第二次进入PEICORE流程
c语言·笔记
Voyager_42 小时前
算法学习记录03——二叉树学习笔记:从两道题看透后序位置的关键作用
笔记·学习·算法
我先去打把游戏先5 小时前
ESP32学习笔记(基于IDF):ESP32连接MQTT服务器
服务器·笔记·单片机·嵌入式硬件·学习·esp32