【Redis】笔记|第8节|大厂高并发缓存架构实战与优化 - 指南
缓存架构
代码结构
代码详情
功能点:
- 多级缓存,先查本地缓存,再查Redis,最后才查数据库
- 热点数据重建逻辑使用分布式锁,二次查询
- 更新缓存采用读写锁提升性能
- 采用Redis的发布订阅机制通知所有实例更新本地缓存
- 适用读多写少的场景
/** * 常量 **/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:"; public static final String NOTIFY_LOCAL_CACHE_UPDATE = "notify_local_cache_update";}
/** * 本地缓存 **/public class LocalCacheHolder { @Getter private static Map localCache = new ConcurrentHashMap<>(); }
/** * 控制器 **/@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; @Autowired private RTopic localCacheUpdateTopic; //写锁 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())); LocalCacheHolder.getLocalCache().put(Constants.PRODUCT_CACHE + productResult.getId(), product); localCacheUpdateTopic.publish(JSON.toJSONString(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())); LocalCacheHolder.getLocalCache().put(Constants.PRODUCT_CACHE + productResult.getId(), product); localCacheUpdateTopic.publish(JSON.toJSONString(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())); LocalCacheHolder.getLocalCache().put(productCacheKey, product); localCacheUpdateTopic.publish(JSON.toJSONString(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) { Product product = LocalCacheHolder.getLocalCache().get(productCacheKey); if (product != null) { return product; } 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); } //通知其它实例更新本地缓存 @Bean public RTopic localCacheUpdateTopic(Redisson redisson) { RTopic topic = redisson.getTopic(Constants.NOTIFY_LOCAL_CACHE_UPDATE); topic.addListener(String.class, (channel, message) -> { System.out.println("收到消息:" + channel + ":" + message); Product product = JSON.parseObject(message, Product.class); LocalCacheHolder.getLocalCache().put(Constants.LOCK_PRODUCT_UPDATE_PREFIX + product.getId(), product); }); return topic; } }
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 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