20.缓存问题与解决方案详解教程

365不给提款流水数据异常 📅 2026-01-21 01:32:41 👤 admin 👁️ 1942 ❤️ 378
20.缓存问题与解决方案详解教程

文章目录

[1. 缓存基础概念](#1. 缓存基础概念)

[1.1 什么是缓存](#1.1 什么是缓存)

[1.2 缓存的作用](#1.2 缓存的作用)

[1.3 常见的缓存类型](#1.3 常见的缓存类型)

[1.4 缓存架构示例](#1.4 缓存架构示例)

[2. 缓存雪崩 (Cache Avalanche)](#2. 缓存雪崩 (Cache Avalanche))

[2.1 什么是缓存雪崩](#2.1 什么是缓存雪崩)

[2.2 缓存雪崩的原因](#2.2 缓存雪崩的原因)

[2.3 缓存雪崩的危害](#2.3 缓存雪崩的危害)

[2.4 缓存雪崩的解决方案](#2.4 缓存雪崩的解决方案)

方案1:设置随机过期时间

方案2:缓存集群和主从复制

方案3:熔断降级机制

方案4:本地缓存兜底

[2.5 缓存雪崩预防最佳实践](#2.5 缓存雪崩预防最佳实践)

[3. 缓存穿透 (Cache Penetration)](#3. 缓存穿透 (Cache Penetration))

[3.1 什么是缓存穿透](#3.1 什么是缓存穿透)

[3.2 缓存穿透的场景示例](#3.2 缓存穿透的场景示例)

[3.3 缓存穿透的危害](#3.3 缓存穿透的危害)

[3.4 缓存穿透的解决方案](#3.4 缓存穿透的解决方案)

方案1:缓存空值

方案2:布隆过滤器

方案3:参数校验

方案4:接口限流

[3.5 Redis布隆过滤器实现](#3.5 Redis布隆过滤器实现)

[4. 缓存预热 (Cache Warming)](#4. 缓存预热 (Cache Warming))

[4.1 什么是缓存预热](#4.1 什么是缓存预热)

[4.2 缓存预热的时机](#4.2 缓存预热的时机)

[4.3 缓存预热的策略](#4.3 缓存预热的策略)

策略1:启动时预热

策略2:分批预热

策略3:定时预热

策略4:智能预热

[4.4 预热监控和管理](#4.4 预热监控和管理)

[5. 缓存更新 (Cache Update)](#5. 缓存更新 (Cache Update))

[5.1 什么是缓存更新](#5.1 什么是缓存更新)

[5.2 缓存更新的策略](#5.2 缓存更新的策略)

[策略1:Cache Aside(旁路缓存)](#策略1:Cache Aside(旁路缓存))

[策略2:Write Through(写透缓存)](#策略2:Write Through(写透缓存))

[策略3:Write Behind(异步写回)](#策略3:Write Behind(异步写回))

[5.3 缓存一致性问题解决方案](#5.3 缓存一致性问题解决方案)

方案1:延时双删

方案2:基于消息队列的异步更新

方案3:分布式锁保证一致性

[5.4 缓存更新最佳实践](#5.4 缓存更新最佳实践)

实践1:批量更新优化

[6. 缓存降级 (Cache Degradation)](#6. 缓存降级 (Cache Degradation))

[6.1 什么是缓存降级](#6.1 什么是缓存降级)

[6.2 缓存降级的场景](#6.2 缓存降级的场景)

[6.3 缓存降级策略](#6.3 缓存降级策略)

策略1:本地缓存降级

策略2:多级缓存降级

策略3:静态数据降级

策略4:限流降级

[6.4 降级监控和告警](#6.4 降级监控和告警)

监控组件

[6.5 降级策略配置化](#6.5 降级策略配置化)

[7. 缓存最佳实践总结](#7. 缓存最佳实践总结)

[7.1 设计原则](#7.1 设计原则)

[7.2 性能优化](#7.2 性能优化)

[7.3 运维建议](#7.3 运维建议)

1. 缓存基础概念

1.1 什么是缓存

缓存是一种高速存储技术,用于临时存储频繁访问的数据,以提高系统性能和响应速度。在软件架构中,缓存通常位于应用程序和数据库之间,作为数据的快速访问层。

1.2 缓存的作用

提高响应速度:从内存中读取数据比从磁盘快几个数量级

减少数据库压力:减少对数据库的直接访问

提升用户体验:快速响应用户请求

节约成本:减少服务器资源消耗

1.3 常见的缓存类型

本地缓存:如HashMap、Guava Cache

分布式缓存:如Redis、Memcached

数据库缓存:如MySQL查询缓存

CDN缓存:内容分发网络缓存

1.4 缓存架构示例

java

复制代码

// 典型的缓存使用模式

public class UserService {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

public User getUserById(Long userId) {

String key = "user:" + userId;

// 1. 先查缓存

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user; // 缓存命中

}

// 2. 缓存未命中,查数据库

user = userRepository.findById(userId);

if (user != null) {

// 3. 将数据写入缓存

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

}

return user;

}

}

2. 缓存雪崩 (Cache Avalanche)

2.1 什么是缓存雪崩

缓存雪崩是指在同一时间,大量的缓存key同时失效,导致大量请求直接打到数据库上,造成数据库瞬间压力过大甚至宕机的现象。

2.2 缓存雪崩的原因

缓存服务器宕机:Redis服务器突然宕机

大量key同时过期:设置了相同的过期时间

缓存预热不充分:系统重启后缓存为空

2.3 缓存雪崩的危害

数据库瞬间压力暴增

系统响应时间急剧增加

可能导致数据库连接池耗尽

严重时可能导致整个系统崩溃

2.4 缓存雪崩的解决方案

方案1:设置随机过期时间

java

复制代码

@Service

public class ProductService {

private RedisTemplate redisTemplate;

private ProductRepository productRepository;

public Product getProductById(Long productId) {

String key = "product:" + productId;

Product product = (Product) redisTemplate.opsForValue().get(key);

if (product == null) {

product = productRepository.findById(productId);

if (product != null) {

// 设置随机过期时间:30分钟 + 0-10分钟的随机时间

int randomMinutes = new Random().nextInt(10);

Duration expireTime = Duration.ofMinutes(30 + randomMinutes);

redisTemplate.opsForValue().set(key, product, expireTime);

}

}

return product;

}

}

方案2:缓存集群和主从复制

yaml

复制代码

# Redis集群配置示例

spring:

redis:

cluster:

nodes:

- 192.168.1.100:7001

- 192.168.1.100:7002

- 192.168.1.100:7003

- 192.168.1.101:7001

- 192.168.1.101:7002

- 192.168.1.101:7003

max-redirects: 3

timeout: 3000ms

lettuce:

pool:

max-active: 16

max-idle: 8

min-idle: 0

方案3:熔断降级机制

java

复制代码

@Component

public class ProductServiceWithCircuitBreaker {

private RedisTemplate redisTemplate;

private ProductRepository productRepository;

private CircuitBreaker circuitBreaker;

public ProductServiceWithCircuitBreaker() {

// 配置熔断器

this.circuitBreaker = CircuitBreaker.ofDefaults("productService");

circuitBreaker.getEventPublisher().onStateTransition(event ->

System.out.println("CircuitBreaker state transition: " + event));

}

public Product getProductById(Long productId) {

return circuitBreaker.executeSupplier(() -> {

String key = "product:" + productId;

Product product = (Product) redisTemplate.opsForValue().get(key);

if (product == null) {

product = productRepository.findById(productId);

if (product != null) {

redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(30));

}

}

return product;

});

}

}

方案4:本地缓存兜底

java

复制代码

@Service

public class ProductServiceWithLocalCache {

private RedisTemplate redisTemplate;

private ProductRepository productRepository;

private Cache localCache;

public ProductServiceWithLocalCache() {

// 创建本地缓存

this.localCache = Caffeine.newBuilder()

.maximumSize(1000)

.expireAfterWrite(5, TimeUnit.MINUTES)

.build();

}

public Product getProductById(Long productId) {

String key = "product:" + productId;

try {

// 1. 先查Redis缓存

Product product = (Product) redisTemplate.opsForValue().get(key);

if (product != null) {

// 同时更新本地缓存

localCache.put(key, product);

return product;

}

} catch (Exception e) {

// Redis异常时,查询本地缓存

Product localProduct = localCache.getIfPresent(key);

if (localProduct != null) {

return localProduct;

}

}

// 2. 查询数据库

Product product = productRepository.findById(productId);

if (product != null) {

try {

redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(30));

} catch (Exception e) {

// Redis写入失败,只更新本地缓存

localCache.put(key, product);

}

}

return product;

}

}

2.5 缓存雪崩预防最佳实践

java

复制代码

@Configuration

public class CacheConfiguration {

@Bean

public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory) {

RedisTemplate template = new RedisTemplate<>();

template.setConnectionFactory(connectionFactory);

// 设置序列化方式

template.setKeySerializer(new StringRedisSerializer());

template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

return template;

}

@Bean

public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()

.entryTtl(Duration.ofMinutes(30)) // 默认30分钟过期

.disableCachingNullValues();

return RedisCacheManager.builder(connectionFactory)

.cacheDefaults(config)

.build();

}

}

3. 缓存穿透 (Cache Penetration)

3.1 什么是缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时才查询数据库,而且不存在的数据不会写入缓存,导致这个不存在的数据每次请求都要查询数据库,给数据库造成压力。

3.2 缓存穿透的场景示例

java

复制代码

// 问题代码示例

public User getUserById(Long userId) {

String key = "user:" + userId;

// 1. 查缓存

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user;

}

// 2. 查数据库

user = userRepository.findById(userId);

if (user != null) {

// 3. 只有数据存在才缓存

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

}

// 如果user为null,不缓存,下次还会查数据库

return user;

}

3.3 缓存穿透的危害

大量无效请求穿透到数据库

数据库查询压力增大

系统整体性能下降

可能被恶意攻击利用

3.4 缓存穿透的解决方案

方案1:缓存空值

java

复制代码

@Service

public class UserServiceWithNullCache {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

public User getUserById(Long userId) {

String key = "user:" + userId;

// 1. 查缓存

Object cached = redisTemplate.opsForValue().get(key);

if (cached != null) {

// 如果是特殊标记,说明数据不存在

if ("NULL".equals(cached)) {

return null;

}

return (User) cached;

}

// 2. 查数据库

User user = userRepository.findById(userId);

if (user != null) {

// 3. 缓存有效数据

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

} else {

// 4. 缓存空值,但设置较短的过期时间

redisTemplate.opsForValue().set(key, "NULL", Duration.ofMinutes(5));

}

return user;

}

}

方案2:布隆过滤器

java

复制代码

@Service

public class UserServiceWithBloomFilter {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private BloomFilter bloomFilter;

@PostConstruct

public void initBloomFilter() {

// 创建布隆过滤器,预计100万个元素,误判率0.01%

bloomFilter = BloomFilter.create(Funnels.longFunnel(), 1000000, 0.0001);

// 将所有用户ID加入布隆过滤器

List userIds = userRepository.findAllUserIds();

userIds.forEach(bloomFilter::put);

}

public User getUserById(Long userId) {

// 1. 先用布隆过滤器判断

if (!bloomFilter.mightContain(userId)) {

// 布隆过滤器说不存在,一定不存在

return null;

}

String key = "user:" + userId;

// 2. 查缓存

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user;

}

// 3. 查数据库

user = userRepository.findById(userId);

if (user != null) {

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

}

return user;

}

}

方案3:参数校验

java

复制代码

@RestController

public class UserController {

private UserService userService;

@GetMapping("/user/{userId}")

public ResponseEntity getUser(@PathVariable Long userId) {

// 1. 参数校验

if (userId == null || userId <= 0) {

return ResponseEntity.badRequest().build();

}

// 2. 业务范围校验

if (userId > 999999999L) { // 假设用户ID不会超过这个值

return ResponseEntity.notFound().build();

}

User user = userService.getUserById(userId);

return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();

}

}

方案4:接口限流

java

复制代码

@Component

public class RateLimitInterceptor implements HandlerInterceptor {

private RateLimiter rateLimiter = RateLimiter.create(100); // 每秒100个请求

@Override

public boolean preHandle(HttpServletRequest request,

HttpServletResponse response,

Object handler) throws Exception {

if (!rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {

response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());

response.getWriter().write("请求过于频繁,请稍后再试");

return false;

}

return true;

}

}

3.5 Redis布隆过滤器实现

java

复制代码

@Component

public class RedisBloomFilter {

private RedisTemplate redisTemplate;

private static final String BF_KEY_PREFIX = "bf:";

/**

* 添加元素到布隆过滤器

*/

public void add(String filterName, String value) {

String key = BF_KEY_PREFIX + filterName;

int[] hashes = getHashes(value);

for (int hash : hashes) {

redisTemplate.opsForValue().setBit(key, Math.abs(hash), true);

}

// 设置过期时间

redisTemplate.expire(key, Duration.ofDays(7));

}

/**

* 判断元素是否可能存在

*/

public boolean mightContain(String filterName, String value) {

String key = BF_KEY_PREFIX + filterName;

int[] hashes = getHashes(value);

for (int hash : hashes) {

if (!redisTemplate.opsForValue().getBit(key, Math.abs(hash))) {

return false;

}

}

return true;

}

/**

* 生成多个哈希值

*/

private int[] getHashes(String value) {

int[] hashes = new int[3]; // 使用3个哈希函数

int hash1 = value.hashCode();

int hash2 = hash1 >>> 16;

for (int i = 0; i < 3; i++) {

hashes[i] = hash1 + i * hash2;

}

return hashes;

}

}

4. 缓存预热 (Cache Warming)

4.1 什么是缓存预热

缓存预热是指在系统启动或者在业务高峰期之前,提前将热点数据加载到缓存中,避免在业务高峰期时因为缓存未命中而导致大量请求打到数据库上。

4.2 缓存预热的时机

系统启动时:应用启动完成后立即预热

定时预热:在业务低峰期定时刷新缓存

手动预热:通过管理接口手动触发预热

4.3 缓存预热的策略

策略1:启动时预热

java

复制代码

@Component

public class CacheWarmUpService {

private RedisTemplate redisTemplate;

private ProductRepository productRepository;

private UserRepository userRepository;

@EventListener(ApplicationReadyEvent.class)

public void warmUpCache() {

System.out.println("开始缓存预热...");

// 预热热门商品

warmUpHotProducts();

// 预热活跃用户

warmUpActiveUsers();

// 预热系统配置

warmUpSystemConfig();

System.out.println("缓存预热完成!");

}

private void warmUpHotProducts() {

List hotProducts = productRepository.findHotProducts(100);

for (Product product : hotProducts) {

String key = "product:" + product.getId();

redisTemplate.opsForValue().set(key, product, Duration.ofHours(2));

}

System.out.println("热门商品预热完成,共预热 " + hotProducts.size() + " 个商品");

}

private void warmUpActiveUsers() {

List activeUsers = userRepository.findActiveUsers(1000);

for (User user : activeUsers) {

String key = "user:" + user.getId();

redisTemplate.opsForValue().set(key, user, Duration.ofHours(1));

}

System.out.println("活跃用户预热完成,共预热 " + activeUsers.size() + " 个用户");

}

private void warmUpSystemConfig() {

Map configs = getSystemConfigs();

configs.forEach((key, value) -> {

redisTemplate.opsForValue().set("config:" + key, value, Duration.ofDays(1));

});

System.out.println("系统配置预热完成");

}

private Map getSystemConfigs() {

// 模拟获取系统配置

Map configs = new HashMap<>();

configs.put("max_order_amount", 50000);

configs.put("free_shipping_threshold", 100);

configs.put("vip_discount_rate", 0.9);

return configs;

}

}

策略2:分批预热

java

复制代码

@Service

public class BatchCacheWarmUpService {

private RedisTemplate redisTemplate;

private ProductRepository productRepository;

private TaskExecutor taskExecutor;

public void warmUpProductsInBatches() {

int totalCount = productRepository.countAllProducts();

int batchSize = 100;

int totalBatches = (totalCount + batchSize - 1) / batchSize;

System.out.println("开始分批预热商品缓存,总数:" + totalCount + ",批次数:" + totalBatches);

for (int i = 0; i < totalBatches; i++) {

final int batchIndex = i;

taskExecutor.execute(() -> {

List products = productRepository.findProductsByPage(batchIndex * batchSize, batchSize);

for (Product product : products) {

String key = "product:" + product.getId();

redisTemplate.opsForValue().set(key, product, Duration.ofHours(2));

}

System.out.println("第 " + (batchIndex + 1) + " 批预热完成,预热了 " + products.size() + " 个商品");

});

// 控制预热速度,避免对系统造成压力

try {

Thread.sleep(100);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}

}

策略3:定时预热

java

复制代码

@Component

public class ScheduledCacheWarmUp {

private RedisTemplate redisTemplate;

private ProductRepository productRepository;

/**

* 每天凌晨2点预热热门商品缓存

*/

@Scheduled(cron = "0 0 2 * * ?")

public void scheduledWarmUp() {

System.out.println("开始定时缓存预热...");

// 获取热门商品

List hotProducts = productRepository.findHotProductsByLastWeek(200);

for (Product product : hotProducts) {

String key = "product:" + product.getId();

// 设置不同的过期时间,避免同时失效

int randomHours = 12 + new Random().nextInt(12); // 12-24小时

redisTemplate.opsForValue().set(key, product, Duration.ofHours(randomHours));

}

System.out.println("定时预热完成,预热了 " + hotProducts.size() + " 个热门商品");

}

/**

* 每小时更新一次实时排行榜

*/

@Scheduled(fixedRate = 3600000) // 1小时

public void updateRankingCache() {

// 更新销量排行榜

List topSelling = productRepository.findTopSellingProducts(50);

redisTemplate.opsForList().leftPushAll("ranking:top_selling", topSelling.toArray());

redisTemplate.expire("ranking:top_selling", Duration.ofHours(2));

// 更新热门搜索榜

List hotKeywords = getHotSearchKeywords();

redisTemplate.opsForList().leftPushAll("ranking:hot_keywords", hotKeywords.toArray());

redisTemplate.expire("ranking:hot_keywords", Duration.ofHours(1));

}

private List getHotSearchKeywords() {

// 模拟获取热门搜索关键词

return Arrays.asList("手机", "电脑", "耳机", "键盘", "鼠标");

}

}

策略4:智能预热

java

复制代码

@Service

public class IntelligentCacheWarmUp {

private RedisTemplate redisTemplate;

private ProductRepository productRepository;

private AnalyticsService analyticsService;

/**

* 基于用户行为数据的智能预热

*/

public void intelligentWarmUp() {

// 1. 分析用户访问模式

Map productAccessCount = analyticsService.getProductAccessCount(Duration.ofDays(7));

// 2. 按访问量排序

List> sortedProducts = productAccessCount.entrySet()

.stream()

.sorted(Map.Entry.comparingByValue().reversed())

.limit(500)

.collect(Collectors.toList());

// 3. 分级预热

for (int i = 0; i < sortedProducts.size(); i++) {

Long productId = sortedProducts.get(i).getKey();

Integer accessCount = sortedProducts.get(i).getValue();

Product product = productRepository.findById(productId);

if (product != null) {

String key = "product:" + productId;

Duration expireTime = calculateExpireTime(i, accessCount);

redisTemplate.opsForValue().set(key, product, expireTime);

}

}

}

/**

* 根据商品热度计算过期时间

*/

private Duration calculateExpireTime(int rank, int accessCount) {

if (rank < 50) {

return Duration.ofHours(24); // 最热门的商品缓存24小时

} else if (rank < 200) {

return Duration.ofHours(12); // 次热门商品缓存12小时

} else {

return Duration.ofHours(6); // 一般热门商品缓存6小时

}

}

}

4.4 预热监控和管理

java

复制代码

@RestController

@RequestMapping("/cache/warmup")

public class CacheWarmUpController {

private BatchCacheWarmUpService batchWarmUpService;

private IntelligentCacheWarmUp intelligentWarmUp;

private RedisTemplate redisTemplate;

/**

* 手动触发预热

*/

@PostMapping("/manual")

public ResponseEntity manualWarmUp(@RequestParam String type) {

try {

switch (type) {

case "product":

batchWarmUpService.warmUpProductsInBatches();

break;

case "intelligent":

intelligentWarmUp.intelligentWarmUp();

break;

default:

return ResponseEntity.badRequest().body("不支持的预热类型");

}

return ResponseEntity.ok("预热任务已启动");

} catch (Exception e) {

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

.body("预热失败:" + e.getMessage());

}

}

/**

* 查看缓存状态

*/

@GetMapping("/status")

public ResponseEntity> getCacheStatus() {

Map status = new HashMap<>();

// 统计不同类型缓存的数量

Set productKeys = redisTemplate.keys("product:*");

Set userKeys = redisTemplate.keys("user:*");

Set configKeys = redisTemplate.keys("config:*");

status.put("productCacheCount", productKeys != null ? productKeys.size() : 0);

status.put("userCacheCount", userKeys != null ? userKeys.size() : 0);

status.put("configCacheCount", configKeys != null ? configKeys.size() : 0);

status.put("timestamp", System.currentTimeMillis());

return ResponseEntity.ok(status);

}

/**

* 清空指定类型的缓存

*/

@DeleteMapping("/clear")

public ResponseEntity clearCache(@RequestParam String type) {

try {

Set keys = redisTemplate.keys(type + ":*");

if (keys != null && !keys.isEmpty()) {

redisTemplate.delete(keys);

return ResponseEntity.ok("已清空 " + keys.size() + " 个 " + type + " 类型的缓存");

} else {

return ResponseEntity.ok("没有找到 " + type + " 类型的缓存");

}

} catch (Exception e) {

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

.body("清空缓存失败:" + e.getMessage());

}

}

}

5. 缓存更新 (Cache Update)

5.1 什么是缓存更新

缓存更新是指当数据发生变化时,需要同步更新缓存中的数据,确保缓存数据与数据库数据的一致性。这是分布式系统中的一个重要问题,需要选择合适的策略来处理。

5.2 缓存更新的策略

策略1:Cache Aside(旁路缓存)

这是最常用的缓存模式,应用程序直接与缓存和数据库交互。

java

复制代码

@Service

public class UserServiceCacheAside {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

/**

* 读取数据

*/

public User getUserById(Long userId) {

String key = "user:" + userId;

// 1. 先查缓存

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user;

}

// 2. 缓存未命中,查数据库

user = userRepository.findById(userId);

if (user != null) {

// 3. 写入缓存

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

}

return user;

}

/**

* 更新数据

*/

@Transactional

public void updateUser(User user) {

// 1. 先更新数据库

userRepository.save(user);

// 2. 删除缓存

String key = "user:" + user.getId();

redisTemplate.delete(key);

// 或者更新缓存

// redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

}

/**

* 删除数据

*/

@Transactional

public void deleteUser(Long userId) {

// 1. 删除数据库数据

userRepository.deleteById(userId);

// 2. 删除缓存

String key = "user:" + userId;

redisTemplate.delete(key);

}

}

策略2:Write Through(写透缓存)

数据同时写入缓存和数据库。

java

复制代码

@Service

public class UserServiceWriteThrough {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

public User getUserById(Long userId) {

String key = "user:" + userId;

// 先查缓存

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user;

}

// 缓存未命中,从数据库加载并写入缓存

user = loadUserFromDatabase(userId);

return user;

}

@Transactional

public void updateUser(User user) {

try {

// 1. 同时更新数据库和缓存

userRepository.save(user);

String key = "user:" + user.getId();

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

} catch (Exception e) {

// 如果任一操作失败,需要回滚

throw new RuntimeException("更新失败", e);

}

}

private User loadUserFromDatabase(Long userId) {

User user = userRepository.findById(userId);

if (user != null) {

String key = "user:" + userId;

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

}

return user;

}

}

策略3:Write Behind(异步写回)

数据先写入缓存,然后异步写入数据库。

java

复制代码

@Service

public class UserServiceWriteBehind {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private TaskExecutor taskExecutor;

private BlockingQueue writeQueue = new LinkedBlockingQueue<>();

@PostConstruct

public void startAsyncWriter() {

// 启动异步写入线程

taskExecutor.execute(this::processWriteQueue);

}

public User getUserById(Long userId) {

String key = "user:" + userId;

// 先查缓存

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user;

}

// 从数据库加载

user = userRepository.findById(userId);

if (user != null) {

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

}

return user;

}

public void updateUser(User user) {

// 1. 立即更新缓存

String key = "user:" + user.getId();

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

// 2. 异步更新数据库

try {

writeQueue.offer(user, 1, TimeUnit.SECONDS);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

private void processWriteQueue() {

while (true) {

try {

User user = writeQueue.take();

userRepository.save(user);

System.out.println("异步写入数据库完成:" + user.getId());

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

break;

} catch (Exception e) {

System.err.println("异步写入失败:" + e.getMessage());

}

}

}

}

5.3 缓存一致性问题解决方案

方案1:延时双删

java

复制代码

@Service

public class UserServiceDelayedDoubleDelete {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private TaskExecutor taskExecutor;

@Transactional

public void updateUser(User user) {

String key = "user:" + user.getId();

// 1. 先删除缓存

redisTemplate.delete(key);

// 2. 更新数据库

userRepository.save(user);

// 3. 延时再删除一次缓存

taskExecutor.execute(() -> {

try {

Thread.sleep(1000); // 延时1秒

redisTemplate.delete(key);

System.out.println("延时删除缓存完成:" + key);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

});

}

}

方案2:基于消息队列的异步更新

java

复制代码

@Service

public class UserServiceWithMQ {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private RabbitTemplate rabbitTemplate;

@Transactional

public void updateUser(User user) {

// 1. 更新数据库

userRepository.save(user);

// 2. 发送缓存更新消息

CacheUpdateMessage message = new CacheUpdateMessage();

message.setType("USER_UPDATE");

message.setKey("user:" + user.getId());

message.setUserId(user.getId());

rabbitTemplate.convertAndSend("cache.update.exchange", "cache.update", message);

}

@RabbitListener(queues = "cache.update.queue")

public void handleCacheUpdate(CacheUpdateMessage message) {

try {

if ("USER_UPDATE".equals(message.getType())) {

// 删除缓存,下次访问时重新加载

redisTemplate.delete(message.getKey());

System.out.println("处理缓存更新消息:" + message.getKey());

}

} catch (Exception e) {

System.err.println("处理缓存更新失败:" + e.getMessage());

}

}

public static class CacheUpdateMessage {

private String type;

private String key;

private Long userId;

// getter和setter方法...

}

}

方案3:分布式锁保证一致性

java

复制代码

@Service

public class UserServiceWithDistributedLock {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private RedissonClient redissonClient;

@Transactional

public void updateUser(User user) {

String lockKey = "lock:user:" + user.getId();

RLock lock = redissonClient.getLock(lockKey);

try {

// 获取分布式锁

if (lock.tryLock(10, TimeUnit.SECONDS)) {

// 1. 更新数据库

userRepository.save(user);

// 2. 删除缓存

String cacheKey = "user:" + user.getId();

redisTemplate.delete(cacheKey);

} else {

throw new RuntimeException("获取锁失败");

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

} finally {

if (lock.isHeldByCurrentThread()) {

lock.unlock();

}

}

}

}

5.4 缓存更新最佳实践

实践1:批量更新优化

java

复制代码

@Service

public class BatchUpdateService {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

/**

* 批量更新用户数据

*/

@Transactional

public void batchUpdateUsers(List users) {

if (users == null || users.isEmpty()) {

return;

}

// 1. 批量更新数据库

userRepository.saveAll(users);

// 2. 批量删除缓存

List cacheKeys = users.stream()

.map(user -> "user:" + user.getId())

.collect(Collectors.toList());

redisTemplate.delete(cacheKeys);

System.out.println("批量更新完成,影响用户数:" + users.size());

}

/**

* 批量预热缓存

*/

public void batchWarmUpUsers(List userIds) {

List users = userRepository.findAllById(userIds);

// 使用Pipeline批量写入Redis

redisTemplate.executePipelined((RedisCallback) connection -> {

for (User user : users) {

String key = "user:" + user.getId();

byte[] keyBytes = key.getBytes();

byte[] valueBytes = serializeUser(user);

connection.setEx(keyBytes, 1800, valueBytes); // 30分钟过期

}

return null;

});

System.out.println("批量预热完成,预热用户数:" + users.size());

}

private byte[] serializeUser(User user) {

// 序列化用户对象

try {

ObjectMapper mapper = new ObjectMapper();

return mapper.writeValueAsBytes(user);

} catch (Exception e) {

throw new RuntimeException("序列化失败", e);

}

}

}

6. 缓存降级 (Cache Degradation)

6.1 什么是缓存降级

缓存降级是指当缓存系统出现故障或性能问题时,系统自动切换到备用方案,确保系统的可用性。这是一种保障系统稳定性的重要机制。

6.2 缓存降级的场景

缓存服务器宕机:Redis服务器不可用

缓存响应超时:网络延迟或服务器负载过高

缓存连接池耗尽:并发请求过多

缓存数据异常:数据损坏或格式错误

6.3 缓存降级策略

策略1:本地缓存降级

java

复制代码

@Service

public class UserServiceWithLocalFallback {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private Cache localCache;

private CircuitBreaker circuitBreaker;

public UserServiceWithLocalFallback() {

// 初始化本地缓存

this.localCache = Caffeine.newBuilder()

.maximumSize(1000)

.expireAfterWrite(10, TimeUnit.MINUTES)

.build();

// 初始化熔断器

this.circuitBreaker = CircuitBreaker.ofDefaults("redis");

}

public User getUserById(Long userId) {

String key = "user:" + userId;

// 1. 尝试从Redis获取数据

User user = circuitBreaker.executeSupplier(() -> {

return (User) redisTemplate.opsForValue().get(key);

});

if (user != null) {

// 更新本地缓存

localCache.put(key, user);

return user;

}

// 2. Redis失败,尝试本地缓存

user = localCache.getIfPresent(key);

if (user != null) {

System.out.println("使用本地缓存降级:" + key);

return user;

}

// 3. 本地缓存也没有,查询数据库

user = userRepository.findById(userId);

if (user != null) {

// 同时更新本地缓存和Redis(如果可用)

localCache.put(key, user);

try {

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

} catch (Exception e) {

System.out.println("Redis写入失败,仅使用本地缓存");

}

}

return user;

}

}

策略2:多级缓存降级

java

复制代码

@Service

public class MultiLevelCacheService {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private Cache l1Cache; // 一级缓存(本地)

private Cache l2Cache; // 二级缓存(本地备份)

public MultiLevelCacheService() {

// L1缓存:小容量,短过期时间

this.l1Cache = Caffeine.newBuilder()

.maximumSize(500)

.expireAfterWrite(5, TimeUnit.MINUTES)

.build();

// L2缓存:大容量,长过期时间

this.l2Cache = Caffeine.newBuilder()

.maximumSize(2000)

.expireAfterWrite(30, TimeUnit.MINUTES)

.build();

}

public User getUserById(Long userId) {

String key = "user:" + userId;

// 1. 查询L1缓存

User user = l1Cache.getIfPresent(key);

if (user != null) {

return user;

}

// 2. 查询Redis

try {

user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

l1Cache.put(key, user);

l2Cache.put(key, user);

return user;

}

} catch (Exception e) {

System.out.println("Redis查询失败,尝试L2缓存");

}

// 3. 查询L2缓存

user = l2Cache.getIfPresent(key);

if (user != null) {

l1Cache.put(key, user);

System.out.println("使用L2缓存降级:" + key);

return user;

}

// 4. 查询数据库

user = userRepository.findById(userId);

if (user != null) {

l1Cache.put(key, user);

l2Cache.put(key, user);

// 尝试写入Redis

try {

redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));

} catch (Exception e) {

System.out.println("Redis写入失败,仅使用本地缓存");

}

}

return user;

}

}

策略3:静态数据降级

java

复制代码

@Service

public class UserServiceWithStaticFallback {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private Map staticUserCache;

@PostConstruct

public void initStaticCache() {

// 初始化静态数据缓存(VIP用户、管理员等重要用户)

List vipUsers = userRepository.findVipUsers();

staticUserCache = vipUsers.stream()

.collect(Collectors.toMap(User::getId, Function.identity()));

System.out.println("静态缓存初始化完成,VIP用户数:" + vipUsers.size());

}

public User getUserById(Long userId) {

String key = "user:" + userId;

try {

// 1. 尝试Redis

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user;

}

} catch (Exception e) {

System.out.println("Redis不可用,尝试降级方案");

}

// 2. 检查静态缓存(重要用户)

User staticUser = staticUserCache.get(userId);

if (staticUser != null) {

System.out.println("使用静态缓存降级:" + userId);

return staticUser;

}

// 3. 查询数据库

return userRepository.findById(userId);

}

/**

* 定期更新静态缓存

*/

@Scheduled(fixedRate = 3600000) // 每小时更新

public void refreshStaticCache() {

try {

List vipUsers = userRepository.findVipUsers();

Map newCache = vipUsers.stream()

.collect(Collectors.toMap(User::getId, Function.identity()));

this.staticUserCache = newCache;

System.out.println("静态缓存刷新完成");

} catch (Exception e) {

System.err.println("静态缓存刷新失败:" + e.getMessage());

}

}

}

策略4:限流降级

java

复制代码

@Service

public class UserServiceWithRateLimit {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private RateLimiter rateLimiter;

private AtomicBoolean degraded = new AtomicBoolean(false);

public UserServiceWithRateLimit() {

// 限流器:每秒允许100个Redis请求

this.rateLimiter = RateLimiter.create(100);

}

public User getUserById(Long userId) {

String key = "user:" + userId;

// 检查是否需要降级

if (!degraded.get() && rateLimiter.tryAcquire()) {

try {

User user = (User) redisTemplate.opsForValue().get(key);

if (user != null) {

return user;

}

} catch (Exception e) {

System.out.println("Redis异常,启用降级模式");

degraded.set(true);

// 10秒后尝试恢复

scheduleRecovery();

}

}

// 降级:直接查询数据库

if (degraded.get()) {

System.out.println("降级模式:直接查询数据库");

} else {

System.out.println("限流降级:跳过缓存查询");

}

return userRepository.findById(userId);

}

private void scheduleRecovery() {

CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS)

.execute(() -> {

try {

// 测试Redis连接

redisTemplate.opsForValue().get("test:connection");

degraded.set(false);

System.out.println("Redis恢复正常,退出降级模式");

} catch (Exception e) {

System.out.println("Redis仍不可用,继续降级模式");

}

});

}

}

6.4 降级监控和告警

监控组件

java

复制代码

@Component

public class CacheMonitor {

private RedisTemplate redisTemplate;

private MeterRegistry meterRegistry;

private Counter cacheHitCounter;

private Counter cacheMissCounter;

private Counter degradationCounter;

public CacheMonitor(MeterRegistry meterRegistry) {

this.meterRegistry = meterRegistry;

this.cacheHitCounter = Counter.builder("cache.hit")

.description("Cache hit count")

.register(meterRegistry);

this.cacheMissCounter = Counter.builder("cache.miss")

.description("Cache miss count")

.register(meterRegistry);

this.degradationCounter = Counter.builder("cache.degradation")

.description("Cache degradation count")

.register(meterRegistry);

}

public void recordCacheHit() {

cacheHitCounter.increment();

}

public void recordCacheMiss() {

cacheMissCounter.increment();

}

public void recordDegradation(String reason) {

degradationCounter.increment(Tags.of("reason", reason));

}

/**

* 健康检查

*/

@Scheduled(fixedRate = 30000) // 每30秒检查

public void healthCheck() {

try {

redisTemplate.opsForValue().get("health:check");

System.out.println("Redis健康检查:正常");

} catch (Exception e) {

System.err.println("Redis健康检查:异常 - " + e.getMessage());

recordDegradation("health_check_failed");

}

}

/**

* 获取缓存统计信息

*/

public Map getCacheStats() {

Map stats = new HashMap<>();

stats.put("hitCount", cacheHitCounter.count());

stats.put("missCount", cacheMissCounter.count());

stats.put("degradationCount", degradationCounter.count());

double hitRate = 0.0;

double totalRequests = cacheHitCounter.count() + cacheMissCounter.count();

if (totalRequests > 0) {

hitRate = cacheHitCounter.count() / totalRequests;

}

stats.put("hitRate", hitRate);

return stats;

}

}

6.5 降级策略配置化

java

复制代码

@ConfigurationProperties(prefix = "cache.degradation")

@Component

public class CacheDegradationConfig {

private boolean enabled = true;

private int timeoutMs = 1000;

private int maxRetries = 3;

private boolean useLocalCache = true;

private boolean useStaticCache = true;

private int recoveryDelaySeconds = 10;

// getter和setter方法...

}

@Service

public class ConfigurableCacheService {

private RedisTemplate redisTemplate;

private UserRepository userRepository;

private CacheDegradationConfig config;

private Cache localCache;

public User getUserById(Long userId) {

String key = "user:" + userId;

if (config.isEnabled()) {

try {

// 设置超时时间

User user = getFromRedisWithTimeout(key, config.getTimeoutMs());

if (user != null) {

return user;

}

} catch (Exception e) {

return handleDegradation(userId, key, e);

}

}

// 直接查询数据库

return userRepository.findById(userId);

}

private User getFromRedisWithTimeout(String key, int timeoutMs) {

// 实现带超时的Redis查询

CompletableFuture future = CompletableFuture.supplyAsync(() -> {

return (User) redisTemplate.opsForValue().get(key);

});

try {

return future.get(timeoutMs, TimeUnit.MILLISECONDS);

} catch (TimeoutException e) {

throw new RuntimeException("Redis查询超时", e);

} catch (Exception e) {

throw new RuntimeException("Redis查询失败", e);

}

}

private User handleDegradation(Long userId, String key, Exception e) {

System.out.println("Redis异常,启用降级策略:" + e.getMessage());

// 尝试本地缓存

if (config.isUseLocalCache()) {

User user = localCache.getIfPresent(key);

if (user != null) {

System.out.println("使用本地缓存降级");

return user;

}

}

// 查询数据库

return userRepository.findById(userId);

}

}

7. 缓存最佳实践总结

7.1 设计原则

缓存穿透防护:使用布隆过滤器和空值缓存

缓存雪崩防护:设置随机过期时间和多级缓存

数据一致性:选择合适的缓存更新策略

降级保护:设计多层降级方案

监控告警:实时监控缓存状态

7.2 性能优化

使用批量操作减少网络开销

合理设置连接池大小

选择合适的序列化方式

控制缓存key的大小和数量

7.3 运维建议

定期备份重要缓存数据

监控缓存命中率和响应时间

设置合理的内存使用限制

建立缓存故障处理流程

通过学习这个详细的缓存教程,你应该能够:

理解各种缓存问题的原因和影响

掌握多种解决方案的实现方法

根据业务场景选择合适的缓存策略

设计可靠的缓存降级机制

建立完善的缓存监控体系

记住,缓存是一把双刃剑,正确使用能大幅提升系统性能,但处理不当也可能带来数据一致性问题。在实际应用中,需要根据具体的业务场景和技术要求,选择最适合的缓存策略。

相关推荐

威露士是哪个国家的
365bet娱乐场

威露士是哪个国家的

📅 08-28 👁️ 8633
台版iphone6/iphone6 plus价格是多少?
bte365娱乐场

台版iphone6/iphone6 plus价格是多少?

📅 08-20 👁️ 2757
Win10系统如何查看电脑使用时间
365bet娱乐场

Win10系统如何查看电脑使用时间

📅 10-21 👁️ 1566