业务场景:

热点动态中的某一条说说因为过期且当时大量用户请求查看动态详情,出现数据库查询压力骤增的情况,从而导致某一热点动态的缓存击穿的情况。

想法:

1、 互斥锁:在热点数据的缓存失效时,可以尝试使用分布式锁来避免多个请求同时访问数据库。在获取锁的情况下,只有一个请求去加载数据并更新缓存,其他请求等待,从而减轻数据库的压力。

  1. 获取互斥锁: 当缓存失效时,多个请求会尝试获取一个互斥锁。如果只有一个请求能够成功获取锁,那么它将负责加载数据并更新缓存。
  2. 获取锁成功: 如果某个请求成功获取了互斥锁,它可以开始加载数据。在加载数据时,需要判断缓存是否已经被其他请求加载(可能在当前请求获取锁的过程中被其他请求加载了),避免重复加载数据。
  3. 更新缓存: 获取锁成功的请求负责加载数据并更新缓存。在更新缓存之后,释放互斥锁,让其他等待锁的请求有机会获取锁并使用更新后的缓存。
  4. 获取锁失败: 如果请求获取锁失败,意味着其他请求已经在加载数据和更新缓存了。这些失败的请求可以等待一段时间后再次尝试,或者直接返回默认值,以避免多次同时访问数据库。

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class CacheWithMutexLock<T> {
private final RedisTemplate<String, T> redisTemplate;
private static final String LOCK_PREFIX = "lock_";

public CacheWithMutexLock(RedisTemplate<String, T> redisTemplate) {
this.redisTemplate = redisTemplate;
}

public T getOrLoadFromCache(String key, Supplier<T> dataLoader, T defaultValue, long lockTimeout) {
String lockKey = LOCK_PREFIX + key;

try {
// 尝试获取互斥锁
boolean gotLock = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", lockTimeout, TimeUnit.MILLISECONDS);
if (gotLock) {
try {
// 检查缓存是否已经加载
T cachedValue = redisTemplate.opsForValue().get(key);
if (cachedValue != null) {
return cachedValue;
}

// 加载数据并更新缓存
T loadedValue = dataLoader.get();
redisTemplate.opsForValue().set(key, loadedValue);
return loadedValue;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
} else {
// 获取锁失败,返回默认值
return defaultValue;
}
} catch (Exception e) {
e.printStackTrace();
return defaultValue;
}
}
}

getOrLoadFromCache 方法尝试获取互斥锁,如果获取成功,它会检查缓存是否已经加载数据,如果没有则加载数据并更新缓存。如果获取锁失败,就直接返回默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
RedisTemplate<String, String> redisTemplate = ...; // 初始化 RedisTemplate
CacheWithMutexLock<String> cache = new CacheWithMutexLock<>(redisTemplate);

String key = "my_key";
String result = cache.getOrLoadFromCache(key,
() -> {
// 从数据库或其他数据源加载数据的逻辑
return "Loaded data";
},
"Default value",
5000 // 互斥锁的超时时间,单位为毫秒
);

RedisTemplate 的一个简化实现。使用互斥锁来协调多个请求的数据加载和缓存更新,可以避免缓存击穿问题。