Redis中的某一热点数据缓存过期了,此时有大量请求访问怎么办?
1、提前设置热点数据永不过期
2、分布式中用redis分布式锁(锁可以在多个 JVM 实例之间协调)、单体中用synchronized(锁只在同一个 JVM 内有效)
编写服务类
import com.redisson.api.RLock;
import com.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class CacheService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
private static final String HOT_DATA_KEY = "hotData";
private static final String LOCK_KEY = "hotDataLock";
public String getHotData() {
// 尝试从 Redis 中获取热点数据
String hotData = redisTemplate.opsForValue().get(HOT_DATA_KEY);
if (hotData == null) {
// 获取分布式锁
RLock lock = redissonClient.getLock(LOCK_KEY);
try {
// 尝试加锁,最多等待100ms,锁的过期时间为30秒
if (lock.tryLock(100, 30, TimeUnit.SECONDS)) {
try {
// 再次检查缓存是否过期(双重检查)
hotData = redisTemplate.opsForValue().get(HOT_DATA_KEY);
if (hotData == null) {
// 缓存确实过期,从数据库加载数据
hotData = loadHotDataFromDatabase();
// 将数据存入 Redis,设置过期时间为10分钟
redisTemplate.opsForValue().set(HOT_DATA_KEY, hotData, 10, TimeUnit.MINUTES);
}
} finally {
// 释放锁
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return hotData;
}
private String loadHotDataFromDatabase() {
// 模拟从数据库加载数据
return "Hot Data from Database";
}
}
模拟多个请求
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Component
public class CacheTestRunner implements CommandLineRunner {
@Autowired
private CacheService cacheService;
@Override
public void run(String... args) throws Exception {
// 模拟 10 个请求同时访问热点数据
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
String hotData = cacheService.getHotData();
System.out.println("Thread " + Thread.currentThread().getId() + " got hot data: " + hotData);
});
}
executorService.shutdown();
}
}
Thread 12 got hot data: Hot Data from Database
Thread 13 got hot data: Hot Data from Database
Thread 14 got hot data: Hot Data from Database
...
所有线程最终都会获取到相同的数据,但只有第一个线程会去加载数据,避免了缓存击穿问题。
单体应用
在单体应用中,所有请求都运行在同一个 JVM 实例中,因此可以使用 synchronized
来同步线程。
java复制
@Service
public class CacheService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String HOT_DATA_KEY = "hotData";
private static final Object lock = new Object(); // 用于同步的锁对象
public String getHotData() {
// 尝试从 Redis 中获取热点数据
String hotData = redisTemplate.opsForValue().get(HOT_DATA_KEY);
if (hotData == null) {
synchronized (lock) { // 使用 synchronized 同步
// 再次检查缓存是否过期(双重检查)
hotData = redisTemplate.opsForValue().get(HOT_DATA_KEY);
if (hotData == null) {
// 缓存确实过期,从数据库加载数据
hotData = loadHotDataFromDatabase();
// 将数据存入 Redis,设置过期时间为10分钟
redisTemplate.opsForValue().set(HOT_DATA_KEY, hotData, 10, TimeUnit.MINUTES);
}
}
}
return hotData;
}
private String loadHotDataFromDatabase() {
// 模拟从数据库加载数据
return "Hot Data from Database";
}
}