【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>
相关推荐
有个人神神叨叨5 分钟前
Agent 记忆学习笔记-1.1
笔记·学习
步十人9 分钟前
epoll——I/O多路复用技术
linux·数据库·redis
三块可乐两块冰10 分钟前
rag笔记4
笔记
问心无愧051314 分钟前
ctf show web入门58
前端·笔记
俏皮小混子44 分钟前
山东大学软件学院项目实训-创新实训-计科智伴(五)——个人博客(从接口对接到边界问题修复的完整记录)
笔记·学习·状态模式·山东大学
Java 码思客44 分钟前
【Redis分布式缓存实战】第4章 单机Redis部署、配置与基础优化
redis·分布式·缓存
Oll Correct1 小时前
计算机二级WPS Office第十四套WPS演示
笔记·计算机二级wps
sukioe1 小时前
Redis 入门:为什么出现、核心原理与安装配置
数据库·redis·缓存
哇嘎呀1 小时前
OSPF笔记
网络·笔记
Upsy-Daisy1 小时前
IOTA 学习笔记(三):IOTA 的技术演进路线
笔记·学习