【SpringBoot 从入门到架构师】第13章:Redis缓存整合与实战
·
1. Redis环境搭建、SpringBoot整合Redis
一、Redis环境搭建
Windows环境搭建
方案一:使用官方Windows版本(推荐)
# 1. 下载Redis for Windows
# 从 https://github.com/microsoftarchive/redis/releases 下载
# 选择 Redis-x64-3.2.100.zip
# 2. 解压到指定目录,如 D:\Redis
# 3. 启动Redis服务
# 打开cmd,进入Redis目录
redis-server.exe redis.windows.conf
# 4. 测试连接(另开一个cmd)
redis-cli.exe
ping # 返回 PONG 表示成功
方案二:使用WSL2(Windows Subsystem for Linux)
# 1. 启用WSL2
wsl --install -d Ubuntu
# 2. 在Ubuntu中安装Redis
sudo apt update
sudo apt install redis-server
# 3. 启动Redis
sudo service redis-server start
# 4. 测试
redis-cli ping
Linux环境搭建
# Ubuntu/Debian
sudo apt update
sudo apt install redis-server
# CentOS/RHEL
sudo yum install epel-release
sudo yum install redis
# 启动Redis
sudo systemctl start redis
sudo systemctl enable redis
# 查看状态
sudo systemctl status redis
# 修改配置文件
sudo vi /etc/redis/redis.conf
# 修改:bind 0.0.0.0(允许远程连接)
# 修改:requirepass yourpassword(设置密码)
# 重启服务
sudo systemctl restart redis
Docker方式(推荐)
# 拉取Redis镜像
docker pull redis:latest
# 运行Redis容器
docker run -d \
--name redis \
-p 6379:6379 \
-v /myredis/conf/redis.conf:/usr/local/etc/redis/redis.conf \
-v /myredis/data:/data \
redis redis-server /usr/local/etc/redis/redis.conf \
--appendonly yes \
--requirepass "yourpassword"
# 进入容器
docker exec -it redis redis-cli
二、Spring Boot整合Redis
添加依赖
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 如果需要JSON序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
配置Redis
# application.yml
spring:
redis:
# Redis服务器地址
host: localhost
# Redis服务器端口
port: 6379
# Redis数据库索引(默认为0)
database: 0
# Redis服务器连接密码(默认为空)
password: yourpassword
# 连接超时时间
timeout: 10s
# 连接池配置
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
Redis配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
工具类封装
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// ================================Map=================================
/**
* HashGet
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// ===============================set=================================
/**
* 根据key获取Set中的所有值
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将数据放入set缓存
*/
public boolean sSet(String key, Object... values) {
try {
redisTemplate.opsForSet().add(key, values);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
使用示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedisUtil redisUtil;
// 设置缓存
@PostMapping("/set")
public String set(@RequestParam String key, @RequestParam String value) {
redisTemplate.opsForValue().set(key, value);
return "OK";
}
// 获取缓存
@GetMapping("/get/{key}")
public Object get(@PathVariable String key) {
return redisTemplate.opsForValue().get(key);
}
// 设置带过期时间的缓存
@PostMapping("/setWithExpire")
public String setWithExpire(@RequestParam String key,
@RequestParam String value,
@RequestParam long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
return "OK";
}
// 使用Hash结构
@PostMapping("/hash")
public String hash() {
String key = "user:1001";
redisTemplate.opsForHash().put(key, "name", "张三");
redisTemplate.opsForHash().put(key, "age", "25");
redisTemplate.opsForHash().put(key, "email", "zhangsan@example.com");
return "OK";
}
// 使用List结构
@PostMapping("/list")
public String list() {
String key = "messages";
redisTemplate.opsForList().rightPush(key, "message1");
redisTemplate.opsForList().rightPush(key, "message2");
redisTemplate.opsForList().rightPush(key, "message3");
return "OK";
}
// 使用Set结构
@PostMapping("/set")
public String set() {
String key = "tags";
redisTemplate.opsForSet().add(key, "java", "spring", "redis");
return "OK";
}
}
使用Redis作为缓存
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 缓存结果,key为方法参数
@Cacheable(value = "userCache", key = "#id")
public User getUserById(Long id) {
// 模拟数据库查询
System.out.println("查询数据库,id: " + id);
return new User(id, "用户" + id);
}
// 更新缓存
@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
// 更新数据库
return user;
}
// 删除缓存
@CacheEvict(value = "userCache", key = "#id")
public void deleteUser(Long id) {
// 删除数据库记录
}
// 清空所有缓存
@CacheEvict(value = "userCache", allEntries = true)
public void clearCache() {
System.out.println("清空缓存");
}
}
// 启用缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
};
}
}
分布式锁实现
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String LOCK_PREFIX = "lock:";
private static final long DEFAULT_EXPIRE_TIME = 30; // 秒
private static final long DEFAULT_WAIT_TIME = 10; // 秒
/**
* 获取分布式锁
*/
public String lock(String lockKey) {
return lock(lockKey, DEFAULT_WAIT_TIME, DEFAULT_EXPIRE_TIME);
}
public String lock(String lockKey, long waitTime, long expireTime) {
String lockValue = UUID.randomUUID().toString();
String fullLockKey = LOCK_PREFIX + lockKey;
long end = System.currentTimeMillis() + waitTime * 1000;
while (System.currentTimeMillis() < end) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(fullLockKey, lockValue, expireTime, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(success)) {
return lockValue;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return null;
}
/**
* 释放分布式锁(使用Lua脚本保证原子性)
*/
public boolean unlock(String lockKey, String lockValue) {
String fullLockKey = LOCK_PREFIX + lockKey;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else " +
"return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList(fullLockKey),
lockValue);
return result != null && result > 0;
}
}
三、最佳实践建议
配置建议
spring:
cache:
type: redis
redis:
time-to-live: 60000 # 缓存过期时间(毫秒)
cache-null-values: false # 是否缓存空值
key-prefix: "cache:" # key前缀
use-key-prefix: true
安全建议
- 设置Redis密码
- 绑定特定IP(生产环境)
- 禁用危险命令
- 启用SSL(如果需要)
- 定期备份数据
性能优化
- 使用连接池
- 合理设置过期时间
- 使用Pipeline批量操作
- 避免大Key
- 使用合适的数据结构
监控和运维
# 查看Redis信息
redis-cli info
# 监控命令
redis-cli monitor
# 查看内存使用
redis-cli info memory
# 查看客户端连接
redis-cli client list
四、常见问题解决
连接超时
- 检查防火墙设置
- 确认Redis服务是否运行
- 检查网络配置
序列化问题
- 确保实体类实现Serializable接口
- 使用Jackson2JsonRedisSerializer
内存不足
- 设置maxmemory策略
- 使用LRU淘汰策略
- 定期清理无用数据
这样你就有了完整的Redis环境搭建和Spring Boot整合方案,可以根据实际需求进行调整和扩展。
2. RedisTemplate配置、序列化方式优化
一、RedisTemplate基础配置
基础配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 设置序列化方式
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
om.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
jacksonSerializer.setObjectMapper(om);
// String序列化
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// key采用String序列化
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// value采用Jackson序列化
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
二、序列化方式详解
常见序列化方式对比
|
序列化方式 |
优点 |
缺点 |
适用场景 |
|
Jackson2JsonRedisSerializer |
可读性好,支持复杂对象 |
性能稍差,存储空间较大 |
复杂对象存储,需要可读性 |
|
GenericJackson2JsonRedisSerializer |
自动类型信息,反序列化方便 |
存储空间更大 |
需要类型安全的场景 |
|
JdkSerializationRedisSerializer |
Java原生,支持所有对象 |
不可读,兼容性差 |
简单场景,不关心可读性 |
|
StringRedisSerializer |
性能最好,存储最小 |
只支持字符串 |
Key的序列化,简单值存储 |
|
OxmSerializer |
XML格式,跨语言 |
性能差,存储大 |
XML交互场景 |
优化配置方案
方案一:混合序列化(推荐)
@Configuration
public class OptimizedRedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 配置序列化
configureSerialization(template);
// 配置连接池
template.setEnableTransactionSupport(false);
template.setExposeConnection(true);
return template;
}
private void configureSerialization(RedisTemplate<String, Object> template) {
// Key序列化:String
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value序列化:Jackson(带类型信息)
GenericJackson2JsonRedisSerializer valueSerializer =
new GenericJackson2JsonRedisSerializer(createObjectMapper());
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
// 设置默认序列化器
template.setDefaultSerializer(valueSerializer);
}
private ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 启用压缩(可选)
mapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
return mapper;
}
}
方案二:自定义序列化(高性能)
@Component
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private final Class<T> clazz;
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
}
// 配置使用
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用FastJson序列化
FastJsonRedisSerializer<Object> fastJsonSerializer =
new FastJsonRedisSerializer<>(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(fastJsonSerializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(fastJsonSerializer);
return template;
}
三、高级优化配置
连接池配置
spring:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 20 # 最大连接数
max-idle: 10 # 最大空闲连接
min-idle: 5 # 最小空闲连接
max-wait: 2000ms # 获取连接最大等待时间
# 或者使用jedis
jedis:
pool:
max-active: 20
max-idle: 10
min-idle: 5
自定义RedisTemplate工厂
@Configuration
public class AdvancedRedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("localhost");
config.setPort(6379);
config.setDatabase(0);
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(2))
.shutdownTimeout(Duration.ZERO)
.clientResources(DefaultClientResources.create())
.build();
return new LettuceConnectionFactory(config, clientConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
// 配置序列化
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
RedisSerializer<Object> jsonSerializer = createJsonSerializer();
template.setKeySerializer(stringSerializer);
template.setValueSerializer(jsonSerializer);
template.setHashKeySerializer(stringSerializer);
template.setHashValueSerializer(jsonSerializer);
// 启用事务支持
template.setEnableTransactionSupport(true);
// 设置是否暴露连接
template.setExposeConnection(true);
return template;
}
private RedisSerializer<Object> createJsonSerializer() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
return new GenericJackson2JsonRedisSerializer(mapper);
}
}
四、场景化配置
缓存场景配置
@Configuration
@EnableCaching
public class CacheRedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默认30分钟过期
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(createJsonSerializer()))
.disableCachingNullValues(); // 不缓存null值
// 针对不同缓存设置不同的过期时间
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
cacheConfigs.put("userCache", config.entryTtl(Duration.ofHours(1)));
cacheConfigs.put("productCache", config.entryTtl(Duration.ofMinutes(10)));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigs)
.transactionAware()
.build();
}
}
分布式锁配置
@Component
public class RedisLockService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
public boolean tryLock(String key, String value, long expireSeconds) {
Boolean result = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
// 使用String序列化确保原子性
byte[] keyBytes = STRING_SERIALIZER.serialize(key);
byte[] valueBytes = STRING_SERIALIZER.serialize(value);
return connection.set(
keyBytes,
valueBytes,
Expiration.seconds(expireSeconds),
RedisStringCommands.SetOption.SET_IF_ABSENT
);
});
return Boolean.TRUE.equals(result);
}
}
五、性能优化建议
序列化优化
- Key使用String序列化 :确保Key的可读性和性能
- Value根据场景选择 :
- 简单对象:StringRedisSerializer
- 复杂对象:GenericJackson2JsonRedisSerializer
- 高性能要求:自定义序列化(如FastJson)
配置优化
// 1. 禁用事务支持(除非需要)
template.setEnableTransactionSupport(false);
// 2. 启用连接暴露
template.setExposeConnection(true);
// 3. 配置连接池参数
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
LettucePoolingClientConfiguration poolConfig =
LettucePoolingClientConfiguration.builder()
.poolConfig(new GenericObjectPoolConfig<>())
.build();
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration("localhost", 6379),
poolConfig
);
}
监控配置
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 添加监控拦截器
template.setClientStatistics(true);
// 配置序列化
// ...
return template;
}
六、完整最佳实践配置
@Configuration
@EnableCaching
public class BestPracticeRedisConfig {
/**
* 主RedisTemplate:用于常规缓存
*/
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Key序列化
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// Value序列化(带类型信息)
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
GenericJackson2JsonRedisSerializer jsonSerializer =
new GenericJackson2JsonRedisSerializer(mapper);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.setEnableTransactionSupport(false);
template.afterPropertiesSet();
return template;
}
/**
* 字符串专用RedisTemplate:性能最优
*/
@Bean(name = "stringRedisTemplate")
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
template.setEnableTransactionSupport(false);
return template;
}
/**
* 缓存管理器
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
总结
- Key一定要用String序列化 :保证可读性和性能
- Value根据场景选择 :
- 简单值:StringRedisSerializer
- 复杂对象:GenericJackson2JsonRedisSerializer
- 高性能:自定义序列化
- 配置连接池 :根据业务量调整连接数
- 禁用事务 :除非确实需要
- 分场景配置 :不同业务使用不同的RedisTemplate实例
这样的配置既保证了性能,又提供了良好的可读性和扩展性。
3. 五种数据类型实战使用
准备工作
添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- 或者使用 Lettuce -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.4.RELEASE</version>
</dependency>
配置 Redis 连接
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtil {
private static JedisPool jedisPool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
jedisPool = new JedisPool(config, "localhost", 6379);
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
String(字符串)实战
2.1 基本操作
public class StringDemo {
public void stringOperations() {
try (Jedis jedis = RedisUtil.getJedis()) {
// 设置值
jedis.set("user:1001:name", "张三");
jedis.set("user:1001:age", "25");
// 获取值
String name = jedis.get("user:1001:name");
System.out.println("用户名: " + name);
// 设置过期时间(30秒)
jedis.setex("session:token123", 30, "user_data");
// 自增操作 - 计数器
jedis.set("article:1001:views", "0");
Long views = jedis.incr("article:1001:views");
System.out.println("浏览量: " + views);
// 批量操作
jedis.mset("product:1001:name", "iPhone",
"product:1001:price", "6999",
"product:1001:stock", "100");
List<String> values = jedis.mget("product:1001:name",
"product:1001:price");
}
}
// 分布式锁实现
public boolean tryLock(String lockKey, String requestId, int expireTime) {
try (Jedis jedis = RedisUtil.getJedis()) {
// SET key value NX EX time - 原子操作
String result = jedis.set(lockKey, requestId,
SetParams.setParams().nx().ex(expireTime));
return "OK".equals(result);
}
}
public void releaseLock(String lockKey, String requestId) {
try (Jedis jedis = RedisUtil.getJedis()) {
// 使用Lua脚本确保原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
}
}
}
Hash(哈希)实战
3.1 用户信息存储
public class HashDemo {
public void userProfileDemo() {
try (Jedis jedis = RedisUtil.getJedis()) {
// 存储用户信息
Map<String, String> userMap = new HashMap<>();
userMap.put("username", "zhangsan");
userMap.put("email", "zhangsan@example.com");
userMap.put("phone", "13800138000");
userMap.put("age", "28");
jedis.hset("user:1001", userMap);
// 获取单个字段
String email = jedis.hget("user:1001", "email");
// 获取所有字段
Map<String, String> userInfo = jedis.hgetAll("user:1001");
// 更新部分字段
jedis.hset("user:1001", "age", "29");
// 增加数值
jedis.hincrBy("user:1001", "login_count", 1);
// 判断字段是否存在
boolean hasPhone = jedis.hexists("user:1001", "phone");
// 获取所有字段名
Set<String> fields = jedis.hkeys("user:1001");
// 获取所有值
List<String> values = jedis.hvals("user:1001");
}
}
// 购物车实现
public class ShoppingCartService {
private static final String CART_KEY_PREFIX = "cart:user:";
public void addToCart(String userId, String productId, int quantity) {
try (Jedis jedis = RedisUtil.getJedis()) {
String cartKey = CART_KEY_PREFIX + userId;
jedis.hincrBy(cartKey, productId, quantity);
}
}
public void removeFromCart(String userId, String productId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String cartKey = CART_KEY_PREFIX + userId;
jedis.hdel(cartKey, productId);
}
}
public Map<String, String> getCart(String userId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String cartKey = CART_KEY_PREFIX + userId;
return jedis.hgetAll(cartKey);
}
}
}
}
List(列表)实战
4.1 消息队列和最新列表
public class ListDemo {
// 消息队列实现
public class SimpleMessageQueue {
private static final String QUEUE_KEY = "queue:messages";
public void produce(String message) {
try (Jedis jedis = RedisUtil.getJedis()) {
// 从左边插入
jedis.lpush(QUEUE_KEY, message);
// 限制队列长度,防止内存溢出
jedis.ltrim(QUEUE_KEY, 0, 9999);
}
}
public String consume() {
try (Jedis jedis = RedisUtil.getJedis()) {
// 从右边弹出 - 阻塞版本
List<String> messages = jedis.brpop(30, QUEUE_KEY);
return messages != null ? messages.get(1) : null;
}
}
}
// 最新文章列表
public void latestArticles() {
try (Jedis jedis = RedisUtil.getJedis()) {
String key = "articles:latest";
// 添加新文章
jedis.lpush(key, "article:1001", "article:1002", "article:1003");
// 获取最新的10篇文章
List<String> latest = jedis.lrange(key, 0, 9);
// 维护列表长度(只保留最新的100条)
jedis.ltrim(key, 0, 99);
// 分页获取
int page = 1;
int pageSize = 10;
int start = (page - 1) * pageSize;
int end = start + pageSize - 1;
List<String> pageData = jedis.lrange(key, start, end);
}
}
// 用户操作日志
public void userActivityLog(String userId, String action) {
try (Jedis jedis = RedisUtil.getJedis()) {
String logKey = "user:" + userId + ":activities";
String logEntry = System.currentTimeMillis() + ":" + action;
jedis.lpush(logKey, logEntry);
jedis.ltrim(logKey, 0, 99); // 只保留最近的100条
}
}
}
Set(集合)实战
5.1 标签系统和共同好友
public class SetDemo {
// 标签系统
public void tagSystem() {
try (Jedis jedis = RedisUtil.getJedis()) {
// 给文章添加标签
String articleKey = "article:1001:tags";
jedis.sadd(articleKey, "java", "redis", "database", "缓存");
// 给用户添加兴趣标签
String userKey = "user:1001:interests";
jedis.sadd(userKey, "java", "python", "AI", "大数据");
// 查找共同标签(推荐系统基础)
Set<String> commonTags = jedis.sinter(articleKey, userKey);
// 获取所有标签
Set<String> allTags = jedis.smembers(articleKey);
// 判断是否包含某个标签
boolean hasJava = jedis.sismember(articleKey, "java");
// 随机获取标签
String randomTag = jedis.srandmember(articleKey);
// 移除标签
jedis.srem(articleKey, "database");
}
}
// 共同好友实现
public class SocialNetworkService {
public void addFriend(String userId, String friendId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String userFriendsKey = "user:" + userId + ":friends";
String friendFriendsKey = "user:" + friendId + ":friends";
// 双向添加
jedis.sadd(userFriendsKey, friendId);
jedis.sadd(friendFriendsKey, userId);
}
}
public Set<String> getCommonFriends(String userId1, String userId2) {
try (Jedis jedis = RedisUtil.getJedis()) {
String key1 = "user:" + userId1 + ":friends";
String key2 = "user:" + userId2 + ":friends";
// 求交集
return jedis.sinter(key1, key2);
}
}
public Set<String> getAllFriends(String userId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String key = "user:" + userId + ":friends";
return jedis.smembers(key);
}
}
}
// 抽奖活动
public void lotterySystem() {
try (Jedis jedis = RedisUtil.getJedis()) {
String lotteryKey = "lottery:2024:participants";
// 用户参与抽奖
jedis.sadd(lotteryKey, "user:1001", "user:1002", "user:1003");
// 抽奖 - 随机抽取3个中奖者
Set<String> winners = jedis.srandmember(lotteryKey, 3);
// 或者抽取并移除(不重复中奖)
String winner = jedis.spop(lotteryKey);
}
}
}
ZSet(有序集合)实战
6.1 排行榜和延迟队列
public class ZSetDemo {
// 游戏排行榜
public class GameRanking {
public void updateScore(String playerId, double score) {
try (Jedis jedis = RedisUtil.getJedis()) {
String rankingKey = "game:ranking";
// 更新分数,如果不存在则添加
jedis.zadd(rankingKey, score, playerId);
}
}
public List<String> getTopPlayers(int topN) {
try (Jedis jedis = RedisUtil.getJedis()) {
String rankingKey = "game:ranking";
// 获取前N名(降序)
Set<String> topPlayers = jedis.zrevrange(rankingKey, 0, topN - 1);
return new ArrayList<>(topPlayers);
}
}
public Long getRank(String playerId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String rankingKey = "game:ranking";
// 获取排名(降序排名,从0开始)
return jedis.zrevrank(rankingKey, playerId);
}
}
public Double getScore(String playerId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String rankingKey = "game:ranking";
return jedis.zscore(rankingKey, playerId);
}
}
public List<String> getPlayersByRange(int startRank, int endRank) {
try (Jedis jedis = RedisUtil.getJedis()) {
String rankingKey = "game:ranking";
return new ArrayList<>(jedis.zrevrange(rankingKey, startRank, endRank));
}
}
}
// 热搜榜实现
public class HotSearchService {
private static final String HOT_SEARCH_KEY = "hot:search";
public void search(String keyword) {
try (Jedis jedis = RedisUtil.getJedis()) {
// 每次搜索增加1分
jedis.zincrby(HOT_SEARCH_KEY, 1, keyword);
// 定期清理过期的热搜(比如只保留7天)
// 可以使用时间戳作为score的一部分
}
}
public List<String> getTopKeywords(int limit) {
try (Jedis jedis = RedisUtil.getJedis()) {
Set<String> keywords = jedis.zrevrange(HOT_SEARCH_KEY, 0, limit - 1);
return new ArrayList<>(keywords);
}
}
}
// 延迟队列实现
public class DelayQueue {
private static final String DELAY_QUEUE_KEY = "delay:queue";
public void addTask(String taskId, long delaySeconds) {
try (Jedis jedis = RedisUtil.getJedis()) {
long executeTime = System.currentTimeMillis() / 1000 + delaySeconds;
jedis.zadd(DELAY_QUEUE_KEY, executeTime, taskId);
}
}
public List<String> getReadyTasks() {
try (Jedis jedis = RedisUtil.getJedis()) {
long currentTime = System.currentTimeMillis() / 1000;
// 获取所有到期的任务
Set<String> tasks = jedis.zrangeByScore(DELAY_QUEUE_KEY, 0, currentTime);
// 移除这些任务
if (!tasks.isEmpty()) {
jedis.zremrangeByScore(DELAY_QUEUE_KEY, 0, currentTime);
}
return new ArrayList<>(tasks);
}
}
}
}
综合实战案例
7.1 电商系统缓存设计
public class ECommerceCacheService {
// 商品详情缓存
public void cacheProductInfo(String productId, Map<String, String> productInfo) {
try (Jedis jedis = RedisUtil.getJedis()) {
String key = "product:" + productId + ":info";
jedis.hset(key, productInfo);
jedis.expire(key, 3600); // 1小时过期
}
}
// 商品库存缓存(使用String)
public void cacheProductStock(String productId, int stock) {
try (Jedis jedis = RedisUtil.getJedis()) {
String key = "product:" + productId + ":stock";
jedis.setex(key, 300, String.valueOf(stock)); // 5分钟过期
}
}
// 用户浏览历史(使用List)
public void addToBrowseHistory(String userId, String productId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String key = "user:" + userId + ":browse_history";
jedis.lpush(key, productId);
jedis.ltrim(key, 0, 49); // 只保留最近50条
}
}
// 商品分类(使用Set)
public void addProductToCategory(String categoryId, String productId) {
try (Jedis jedis = RedisUtil.getJedis()) {
String key = "category:" + categoryId + ":products";
jedis.sadd(key, productId);
}
}
// 商品销量排行榜(使用ZSet)
public void updateProductSales(String productId, int sales) {
try (Jedis jedis = RedisUtil.getJedis()) {
String key = "product:sales:ranking";
jedis.zadd(key, sales, productId);
}
}
}
7.2 使用 Spring Boot + RedisTemplate
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson序列化
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
return template;
}
}
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// String 操作
public void setString(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
public Object getString(String key) {
return redisTemplate.opsForValue().get(key);
}
// Hash 操作
public void setHash(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
}
public Map<Object, Object> getHash(String key) {
return redisTemplate.opsForHash().entries(key);
}
// List 操作
public void pushToList(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
public List<Object> getList(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
4. SpringCache注解缓存(@Cacheable、@CachePut、@CacheEvict)
一、@Cacheable - 缓存查询
基本用法
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id);
}
参数详解
- value/cacheNames : 缓存名称(必填),可指定多个
- key : 缓存键,支持 SpEL 表达式
- condition : 条件判断,满足时才缓存
- unless : 条件判断,满足时不缓存
- keyGenerator : 自定义键生成器
- cacheManager : 指定缓存管理器
- cacheResolver : 自定义缓存解析器
常见 SpEL 表达式示例
@Cacheable(value = "users",
key = "#user.id",
condition = "#user.age > 18",
unless = "#result == null")
public User getUser(User user) {
// ...
}
@Cacheable(value = "products", key = "#root.methodName")
public List<Product> getAllProducts() {
// ...
}
二、@CachePut - 更新缓存
特点
- 总是执行方法体
- 用方法返回值更新缓存
- 通常用于新增/修改操作
示例
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
@CachePut(value = "users", key = "#result.id")
public User createUser(User user) {
return userRepository.save(user);
}
三、@CacheEvict - 删除缓存
基本用法
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
重要参数
- allEntries : 是否清空整个缓存区域
- beforeInvocation : 是否在方法执行前清除缓存
示例
// 清空整个 users 缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsersCache() {
// 无需实际逻辑,仅用于触发缓存清除
}
// 方法执行前清除缓存
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUser(Long id) {
// 即使方法抛出异常,缓存也会被清除
}
// 清空整个 users 缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsersCache() {
// 无需实际逻辑,仅用于触发缓存清除
}
// 方法执行前清除缓存
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUser(Long id) {
// 即使方法抛出异常,缓存也会被清除
}
四、组合使用示例
完整 CRUD 示例
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userRepository.findById(id);
}
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void refreshAllUsers() {
// 刷新逻辑
}
}
多缓存操作
@Caching(
evict = {
@CacheEvict(value = "users", key = "#user.id"),
@CacheEvict(value = "userList", allEntries = true)
},
put = {
@CachePut(value = "userDetails", key = "#user.id")
}
)
public User updateUserWithMultipleCaches(User user) {
return userRepository.save(user);
}
五、配置示例
启用缓存
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("users"),
new ConcurrentMapCache("products")
));
return cacheManager;
}
}
Redis 缓存配置
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
六、最佳实践
键设计原则
// 好的键设计
@Cacheable(value = "orders", key = "'user:' + #userId + ':status:' + #status")
public List<Order> getOrdersByUserAndStatus(Long userId, String status) {
// ...
}
条件缓存
@Cacheable(value = "products",
condition = "#pageSize <= 100",
unless = "#result == null or #result.isEmpty()")
public List<Product> getProducts(int page, int pageSize) {
// 只缓存小数据量查询
}
异常处理
@Cacheable(value = "data", unless = "#result == null")
public Data getDataWithFallback(String key) {
try {
return expensiveOperation();
} catch (Exception e) {
log.error("操作失败", e);
return null; // 异常时不缓存
}
}
七、常见问题
自调用失效
@Service
public class UserService {
public User getAndUpdate(Long id) {
// 自调用,@Cacheable 不会生效
User user = getUser(id);
return updateUser(user);
}
@Cacheable("users")
public User getUser(Long id) {
// ...
}
@CachePut("users")
public User updateUser(User user) {
// ...
}
}
解决方案
- 使用 AopContext.currentProxy()
- 将方法拆分到不同类
- 使用 @Autowired 注入自身代理
八、性能监控
@Component
public class CacheMetrics {
@EventListener
public void handleCacheEvent(CacheEvent event) {
log.info("缓存事件: {} - {} - {}",
event.getSource(),
event.getCacheName(),
event.getKey());
}
}
总结
Spring Cache 注解提供了声明式的缓存管理:
- @Cacheable : 优先从缓存读取,适合查询操作
- @CachePut : 更新缓存,适合写操作
- @CacheEvict : 删除缓存,适合删除或更新操作
合理使用这些注解可以显著提升应用性能,但需要注意缓存一致性、内存使用和异常处理等问题。
5. 缓存穿透、缓存击穿、缓存雪崩解决方案
一、缓存问题概述
缓存穿透
定义 :查询不存在的数据,导致请求直接打到数据库
原因 :恶意攻击或业务查询不存在的数据
缓存击穿
定义 :热点key过期瞬间,大量请求直接访问数据库
原因 :热点数据过期,并发访问量大
缓存雪崩
定义 :大量key同时过期或Redis宕机,导致数据库压力激增
原因 :缓存集中过期、Redis服务不可用
二、Spring Boot + Redis 完整解决方案
项目依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version>
</dependency>
<!-- 可选:使用JetCache -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.7.5</version>
</dependency>
</dependencies>
缓存穿透解决方案
方案一:布隆过滤器(推荐)
@Component
public class BloomFilterHelper {
private final RedissonClient redissonClient;
private static final String BLOOM_FILTER_NAME = "userBloomFilter";
public BloomFilterHelper(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
initBloomFilter();
}
private void initBloomFilter() {
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
// 初始化:预期元素数量100万,误判率0.01%
bloomFilter.tryInit(1000000L, 0.001);
// 预热数据到布隆过滤器
loadExistingDataToBloomFilter(bloomFilter);
}
public boolean mightContain(Long id) {
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
return bloomFilter.contains(id);
}
public void add(Long id) {
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
bloomFilter.add(id);
}
}
方案二:缓存空值
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String PRODUCT_KEY_PREFIX = "product:";
private static final long NULL_CACHE_TTL = 300; // 5分钟
public Product getProductById(Long id) {
String key = PRODUCT_KEY_PREFIX + id;
// 1. 先从缓存查询
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
// 空值对象判断
if (product.getId() == -1L) {
return null; // 缓存空值,直接返回null
}
return product;
}
// 2. 布隆过滤器检查(如果使用)
// if (!bloomFilterHelper.mightContain(id)) return null;
// 3. 查询数据库
product = productDao.findById(id);
if (product == null) {
// 缓存空值,防止穿透
Product nullProduct = new Product();
nullProduct.setId(-1L); // 标记为空值
redisTemplate.opsForValue().set(key, nullProduct, NULL_CACHE_TTL, TimeUnit.SECONDS);
return null;
}
// 4. 写入缓存
redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);
return product;
}
}
缓存击穿解决方案
方案一:互斥锁(分布式锁)
@Service
public class HotspotService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String HOT_PRODUCT_KEY = "hot:product:";
private static final String LOCK_KEY_PREFIX = "lock:product:";
/**
* 使用分布式锁解决缓存击穿
*/
public Product getHotProductWithLock(Long productId) {
String cacheKey = HOT_PRODUCT_KEY + productId;
String lockKey = LOCK_KEY_PREFIX + productId;
// 1. 尝试从缓存获取
Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 2. 获取分布式锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待3秒,锁持有10秒
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
// 获取锁失败,短暂休眠后重试或返回旧数据
Thread.sleep(100);
return getHotProductWithLock(productId); // 重试
}
// 3. 双重检查(Double Check)
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 4. 查询数据库
product = productDao.findById(productId);
if (product != null) {
// 5. 写入缓存
redisTemplate.opsForValue().set(
cacheKey,
product,
// 基础过期时间 + 随机值,避免同时过期
30 + new Random().nextInt(30),
TimeUnit.MINUTES
);
}
return product;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁失败", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
方案二:逻辑过期(热点数据永不过期)
@Component
public class HotspotDataManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 逻辑过期方案
*/
public Product getProductWithLogicalExpire(Long productId) {
String cacheKey = "product:logic:" + productId;
// 1. 从缓存获取数据
CacheWrapper<Product> wrapper = (CacheWrapper<Product>) redisTemplate
.opsForValue().get(cacheKey);
// 2. 缓存不存在,直接查库
if (wrapper == null) {
return loadAndCacheProduct(productId);
}
// 3. 判断是否逻辑过期
if (wrapper.isExpired()) {
// 异步更新缓存
CompletableFuture.runAsync(() -> refreshProductCache(productId));
}
return wrapper.getData();
}
/**
* 缓存包装类,包含逻辑过期时间
*/
@Data
@AllArgsConstructor
private static class CacheWrapper<T> implements Serializable {
private T data;
private long expireTime; // 逻辑过期时间戳
public boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}
private Product loadAndCacheProduct(Long productId) {
Product product = productDao.findById(productId);
if (product != null) {
// 设置逻辑过期时间为10分钟后
long expireTime = System.currentTimeMillis() + 10 * 60 * 1000;
CacheWrapper<Product> wrapper = new CacheWrapper<>(product, expireTime);
// 物理缓存永不过期
redisTemplate.opsForValue().set(
"product:logic:" + productId,
wrapper
);
}
return product;
}
private void refreshProductCache(Long productId) {
// 使用分布式锁,防止并发重建
// ... 实现缓存重建逻辑
}
}
缓存雪崩解决方案
方案一:随机过期时间
@Component
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存时添加随机过期时间
*/
public void setWithRandomTTL(String key, Object value, long baseTTL, TimeUnit timeUnit) {
Random random = new Random();
// 基础TTL ± 随机值(20%波动)
long randomOffset = (long) (baseTTL * 0.2 * random.nextDouble());
long finalTTL = baseTTL + (random.nextBoolean() ? randomOffset : -randomOffset);
redisTemplate.opsForValue().set(
key,
value,
finalTTL,
timeUnit
);
}
/**
* 批量设置缓存,使用不同的过期时间
*/
public void batchSetWithStaggeredExpiry(Map<String, Object> dataMap,
long baseTTL,
TimeUnit timeUnit) {
Random random = new Random();
dataMap.forEach((key, value) -> {
long offset = random.nextInt(600); // 随机0-10分钟
long ttl = timeUnit.toSeconds(baseTTL) + offset;
redisTemplate.opsForValue().set(
key,
value,
ttl,
TimeUnit.SECONDS
);
});
}
}
方案二:缓存预热 + 定时更新
@Component
@Slf4j
public class CacheWarmUpService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductDao productDao;
/**
* 应用启动时缓存预热
*/
@PostConstruct
public void warmUpCache() {
log.info("开始缓存预热...");
// 1. 预热热点数据
warmUpHotProducts();
// 2. 预热分类数据
warmUpCategories();
log.info("缓存预热完成");
}
/**
* 定时更新缓存
*/
@Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行
public void scheduledCacheUpdate() {
log.info("定时缓存更新开始...");
updateHotProductsCache();
}
private void warmUpHotProducts() {
List<Product> hotProducts = productDao.findHotProducts(100);
CacheService cacheService = new CacheService();
Map<String, Object> cacheMap = hotProducts.stream()
.collect(Collectors.toMap(
p -> "product:hot:" + p.getId(),
Function.identity()
));
// 使用错峰过期
cacheService.batchSetWithStaggeredExpiry(cacheMap, 30, TimeUnit.MINUTES);
}
}
方案三:多级缓存架构
@Component
public class MultiLevelCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 本地缓存(Caffeine)
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build();
/**
* 多级缓存查询
*/
public Product getProductMultiLevel(Long productId) {
String cacheKey = "product:" + productId;
// 1. 查询本地缓存
Product product = (Product) localCache.getIfPresent(cacheKey);
if (product != null) {
return product;
}
// 2. 查询Redis
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
// 回填本地缓存
localCache.put(cacheKey, product);
return product;
}
// 3. 查询数据库(使用互斥锁防止击穿)
product = getProductWithLock(productId);
if (product != null) {
// 写入多级缓存
localCache.put(cacheKey, product);
redisTemplate.opsForValue().set(
cacheKey,
product,
30 + new Random().nextInt(30),
TimeUnit.MINUTES
);
}
return product;
}
}
Redis高可用配置
spring:
redis:
# Redis集群配置
cluster:
nodes:
- 192.168.1.101:6379
- 192.168.1.102:6379
- 192.168.1.103:6379
max-redirects: 3
# 哨兵模式配置
# sentinel:
# master: mymaster
# nodes:
# - 192.168.1.101:26379
# - 192.168.1.102:26379
# - 192.168.1.103:26379
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
shutdown-timeout: 100ms
降级和熔断策略
@Component
@Slf4j
public class CacheDegradeService {
@Autowired
private CircuitBreakerFactory circuitBreakerFactory;
/**
* 使用Resilience4j熔断器
*/
public Product getProductWithCircuitBreaker(Long productId) {
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("productService");
return circuitBreaker.run(
() -> {
// 正常业务逻辑
return getProductFromCacheOrDB(productId);
},
throwable -> {
// 降级逻辑
log.warn("缓存服务降级,返回默认产品", throwable);
return getDefaultProduct();
}
);
}
/**
* 服务降级:返回默认数据
*/
private Product getDefaultProduct() {
Product defaultProduct = new Product();
defaultProduct.setId(0L);
defaultProduct.setName("默认产品");
defaultProduct.setPrice(BigDecimal.ZERO);
return defaultProduct;
}
/**
* 限流保护
*/
@ServiceLimit(limit = 100, timeout = 1000) // 自定义注解实现限流
public Product getProductWithRateLimit(Long productId) {
return getProductFromCacheOrDB(productId);
}
}
监控和告警
@Component
@Slf4j
public class CacheMonitor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 监控缓存命中率
*/
@Scheduled(fixedRate = 60000) // 每分钟统计
public void monitorCacheHitRate() {
// 获取缓存统计信息
Map<String, String> cacheStats = getCacheStatistics();
double hitRate = calculateHitRate(cacheStats);
if (hitRate < 0.8) { // 命中率低于80%告警
log.warn("缓存命中率过低: {}%", hitRate * 100);
sendAlert("缓存命中率过低警告");
}
}
/**
* 监控Redis健康状态
*/
public boolean checkRedisHealth() {
try {
String result = redisTemplate.getConnectionFactory()
.getConnection()
.ping();
return "PONG".equals(result);
} catch (Exception e) {
log.error("Redis健康检查失败", e);
// 触发降级策略
return false;
}
}
}
三、最佳实践总结
- 缓存穿透 :
- 使用布隆过滤器 + 缓存空值
- 接口层增加参数校验
- 缓存击穿 :
- 热点数据永不过期 + 逻辑过期
- 使用分布式锁重建缓存
- 提前续期热点数据
- 缓存雪崩 :
- 设置随机过期时间
- 使用多级缓存架构
- 保证Redis高可用(集群/哨兵)
- 实施缓存预热策略
- 通用建议 :
- 监控缓存命中率
- 设置合理的过期时间
- 实现降级熔断机制
- 定期进行压力测试
这些方案可以根据实际业务场景组合使用,构建健壮的缓存体系。
更多推荐
所有评论(0)