【Redis】笔记|第10节|京东HotKey实现多级缓存架构 - 实践
缓存架构
京东HotKey架构
代码结构
代码详情
功能点:(如代码有错误,欢迎讨论纠正)
- 多级缓存,先查HotKey缓存,再查Redis,最后才查数据库
- 热点数据重建逻辑使用分布式锁,二次查询
- 更新缓存采用读写锁提升性能
- 利用HotKey系统推送热点Key到应用服务端(HotKey客户端角色)
- 适用读多写少的场景
/** * 常量 **/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 **/@Componentpublic 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层 **/@Repositorypublic 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; } }
/** * 商品实体 **/@Datapublic 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; // 更新时间}
/** * 商品服务 **/@Servicepublic 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; } }
@SpringBootApplicationpublic 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 17 org.springframework.boot spring-boot-starter-web com.jd.platform.hotkey hotkey-client 0.0.4-SNAPSHOT org.projectlombok lombok true org.redisson redisson 3.48.0 com.alibaba fastjson 1.2.83 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-actuator org.apache.maven.plugins maven-compiler-plugin org.projectlombok lombok org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok