【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 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);
     }

    }


    4.0.0

    org.springframework.boot
    spring-boot-starter-parent
    3.3.12


    com.example.mutilcache
    redis-multi-cache
    0.0.1-SNAPSHOT
    redis-multi-cache
    redis-multi-cache














    <java.version>17</java.version>



    org.springframework.boot
    spring-boot-starter-web

    复制代码
         <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>
相关推荐
LinXunFeng2 天前
Obsidian - 使用 Share Note 分享笔记并自部署
前端·笔记·github
用户3169353811834 天前
Java连接Redis
redis
小小工匠6 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
闪闪发亮的小星星6 天前
高斯光以及高斯光公式解释
笔记
ofoxcoding6 天前
在AI API聚合平台配置DeepSeek V3.2提示词缓存实战:快速接入与成本优化指南
人工智能·spring·缓存·ai
cqbzcsq6 天前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
阿米亚波6 天前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
自传.6 天前
尚硅谷 Vibe Coding|第三章(1) Claude Code深度使用与进阶技巧 学习笔记
笔记·学习·尚硅谷·vibecoding
NeilYuen6 天前
gRPC结合FAISS构建AI助手语义缓存模块(一):设计
人工智能·缓存·faiss
.千余6 天前
【C++】模板进阶全解:非类型参数|全特化|偏特化|分离编译完全指南
开发语言·c++·笔记·学习·其他