惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
博客园 - 三生石上(FineUI控件)
WordPress大学
WordPress大学
博客园 - 【当耐特】
月光博客
月光博客
Vercel News
Vercel News
D
Docker
I
InfoQ
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
GbyAI
GbyAI
有赞技术团队
有赞技术团队
雷峰网
雷峰网
博客园 - 聂微东
小众软件
小众软件
Y
Y Combinator Blog
腾讯CDC
L
LangChain Blog
The GitHub Blog
The GitHub Blog
宝玉的分享
宝玉的分享
Stack Overflow Blog
Stack Overflow Blog
大猫的无限游戏
大猫的无限游戏
T
The Blog of Author Tim Ferriss

祈雨的笔记

安全多方计算MPC spark原理解析 kueue执行源码分析 spark on k8s执行源码分析 spark-operator源码解析 系统压测遇到的缓存击穿问题 我的世界PC与安卓联机 蚂蚁金服流量投放平台的AIG改造 G1大对象致Old区占用率高 日志打印导致接口响应率下跌分析 Groovy加载类导致OOM分析 ERROR日志打印导致CPU满载 记OceanBase死锁超时 应用发版期间服务响应超时 Ark Serverless初探 系统优化复盘一二三 The user specified as a definer does not exist Kong网关初探 API网关选型调研 CPU火焰图常用工具 配置中心选型调研 root操作Nginx导致用户组错误 基于Proxifier使用代理 FastJSON字段智能匹配踩坑 Nacos初探 记一次Nginx服务器CPU满荷载故障 基于券系统分库分表的思考 limit不参与SQL成本计算致索引失效 Linux常用性能监控命令 golang低版本http2偶现400
springboot(20)Redis缓存@Cacheable对存在的数据返回null
祈雨的笔记 · 2018-06-08 · via 祈雨的笔记

问题描述

最近我们用Spring Cache + redis来做缓存,使用的是1.8.10.RELEASE版本的spring-data-redis。在高并发下数据库存在数据但是@Cacheable 注解返回的内容是null。查看了一下源代码,在使用注解获取缓存的时候,RedisCache的get方法会先去判断key是否存在,然后再去获取值。这了就有一个漏铜,当线程1判断了key是存在的,紧接着这个时候这个key过期了,这时线程1再去获取值的时候返回的是null。

org.springframework.data.redis.cache.RedisCache.get(RedisCacheKey)方法源码:

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
public RedisCacheElement get(final RedisCacheKey cacheKey) {

Assert.notNull(cacheKey, "CacheKey must not be null!");

Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {

@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});

if (!exists.booleanValue()) {
return null;
}

return new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey)));
}

protected Object lookup(Object key) {

RedisCacheKey cacheKey = key instanceof RedisCacheKey ? (RedisCacheKey) key : getRedisCacheKey(key);

byte[] bytes = (byte[]) redisOperations.execute(new AbstractRedisCacheCallback<byte[]>(
new BinaryRedisCacheElement(new RedisCacheElement(cacheKey, null), cacheValueAccessor), cacheMetadata) {

@Override
public byte[] doInRedis(BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
return connection.get(element.getKeyBytes());
}
});

return bytes == null ? null : cacheValueAccessor.deserializeIfNecessary(bytes);
}

解决方案

更换版本:spring-data-redis在1.8.11的版本中修复了该问题

1
2
3
4
5
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.11.RELEASE</version>
</dependency>

1.8.11的源代码:

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
public RedisCacheElement get(final RedisCacheKey cacheKey) {

Assert.notNull(cacheKey, "CacheKey must not be null!");

Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {

@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});

if (!exists) {
return null;
}

byte[] bytes = doLookup(cacheKey);


if (bytes == null) {
return null;
}

return new RedisCacheElement(cacheKey, fromStoreValue(deserialize(bytes)));
}

private byte[] doLookup(Object key) {

RedisCacheKey cacheKey = key instanceof RedisCacheKey ? (RedisCacheKey) key : getRedisCacheKey(key);

return (byte[]) redisOperations.execute(new AbstractRedisCacheCallback<byte[]>(
new BinaryRedisCacheElement(new RedisCacheElement(cacheKey, null), cacheValueAccessor), cacheMetadata) {

@Override
public byte[] doInRedis(BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
return connection.get(element.getKeyBytes());
}
});
}

配置Redis管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class RedisConfig {






@Bean
public CacheManager cacheManager(RedisTemplate<String,?> redisTemplate) {
RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate);

cacheManager.setUsePrefix(true);
cacheManager.setDefaultExpiration(60);
return cacheManager;
}

}