【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 **/@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
posted @ 2025-07-22 11:34  yfceshi  阅读(22)  评论(0)    收藏  举报