【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>
相关推荐
NUZGNAW6 分钟前
Ubuntu 安装redis和nginx
redis·nginx·ubuntu
Star在努力10 分钟前
15-C语言:第15~16天笔记
c语言·笔记·算法
ZY小袁2 小时前
MGRE综合实验
服务器·网络·笔记·网络安全·学习方法·信息与通信·p2p
一位搞嵌入式的 genius2 小时前
暑期自学嵌入式——Day10(C语言阶段)
linux·笔记·学习·嵌入式c语言
被遗忘的旋律.2 小时前
Linux驱动开发笔记(五)——设备树(上)
linux·驱动开发·笔记
霜绛3 小时前
机器学习笔记(四)——聚类算法KNN、Kmeans、Dbscan
笔记·算法·机器学习·kmeans·聚类
飞速移动的代码菌10 小时前
【DataWhale】快乐学习大模型 | 202507,Task08笔记
笔记·学习
不在了情绪10 小时前
[ The Missing Semester of Your CS Education ] 学习笔记 Vim篇
笔记·学习·vim
遇见尚硅谷12 小时前
C语言:20250728学习(指针)
c语言·开发语言·数据结构·c++·笔记·学习·算法
这就是佬们吗14 小时前
初识 docker [上]
java·开发语言·笔记·docker·容器