【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 {@Getterprivate static Map<String, Product> localCache = new ConcurrentHashMap<>();}
/*** 控制器**/
@RestController
@RequestMapping("/api/product/")
public class ProductController {@Autowiredprivate 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; // 商品IDprivate 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 {@Autowiredprivate ProductDao productDao;@Autowiredprivate Redisson redisson;@Autowiredprivate 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;}//热点缓存重建,加分布式锁,第一个请求写缓存成功后,做二次读取缓存操作,其余请求都可以走RedisRLock 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<String> 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);}@Beanpublic Redisson redisson() {// 此为单机模式Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);return (Redisson) Redisson.create(config);}//通知其它实例更新本地缓存@Beanpublic 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;}}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.12</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example.mutilcache</groupId><artifactId>redis-multi-cache</artifactId><version>0.0.1-SNAPSHOT</version><name>redis-multi-cache</name><description>redis-multi-cache</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></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></project>