上篇讲解了 Redis 基本内容,下篇将讲解如何将 Redis 快速的加入项目,并说明几个重要的解决方案内容示例。
在pom.xml
中添加Redis和缓存支持依赖:
<dependencies>
<!-- Redis 数据访问核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.1.5</version> <!-- 必须与 Spring Boot 版本严格匹配 -->
</dependency>
<!-- 缓存抽象层支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- JSON 序列化工具 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
在application.yml
中配置(根据实际环境修改):
spring:
redis:
host: redis-cluster-01.example.com # 生产环境建议配置哨兵/集群模式
port: 6379
password: ${REDIS_PASSWORD:your_password} # 支持配置中心动态获取
database: 0 # 默认数据库(生产环境建议固定专用DB)
timeout: 5000ms # 连接超时时间
lettuce:
pool:
max-active: 100 # 最大并发连接数(需匹配 Redis maxclients)
max-idle: 20 # 最大空闲连接(防止连接泄漏)
min-idle: 5 # 最小空闲连接(保持长连接)
shutdown-timeout: 100ms # 关闭连接池超时时间
###启用缓存配置
在Spring Boot启动类添加注解:
@SpringBootApplication
@EnableCaching // 启用缓存注解驱动(底层基于AOP实现)
public class ErpApplication {
public static void main(String[] args) {
SpringApplication.run(ErpApplication.class, args);
}
}
在 Service 实现层直接使用注解管理缓存
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
// 【核心逻辑】
// 1. 查询时优先从Redis获取数据
// 2. 若缓存未命中则执行数据库查询
// 3. 将查询结果自动序列化存储到Redis
@Cacheable(
value = "products", // 缓存名称(需与CacheManager配置一致)
key = "#id", // 缓存键生成规则(SpEL表达式)
unless = "#result == null", // 不缓存空值(防止缓存污染)
cacheManager = "customCacheManager" // 指定自定义缓存管理器
)
public Product getProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Product not found"));
}
// 【核心逻辑】
// 1. 先执行数据库更新操作
// 2. 将新数据同步写入Redis覆盖旧值
// 3. 适用于需要实时更新缓存的场景
@CachePut(
value = "products",
key = "#product.id",
condition = "#product.price > 0" // 条件更新(避免无效数据写入)
)
public Product updateProduct(Product product) {
return productRepository.save(product);
}
// 【核心逻辑】
// 1. 删除数据库记录前先清除缓存
// 2. 防止出现缓存与数据库不一致问题
@CacheEvict(
value = "products",
key = "#id",
allEntries = false // 仅清除指定Key(false为默认值)
)
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
}
自定义缓存配置,可选择是否添加
@Configuration
@EnableConfigurationProperties(CacheProperties.class)
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer()); // Key使用字符串序列化
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // Value使用JSON序列化
template.setHashKeySerializer(new StringRedisSerializer()); // Hash Key序列化
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); // Hash Value序列化
return template;
}
// 【核心配置】
// 1. 统一缓存管理配置
// 2. 设置默认过期时间(30分钟)
// 3. 禁用空值缓存(防止缓存穿透)
@Bean
public CacheManager customCacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.expireAfterWrite(30, TimeUnit.MINUTES) // 默认过期时间
.disableCachingNullValues(); // 不缓存空值
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(Map.of(
"hotProducts", RedisCacheConfiguration.defaultCacheConfig().expireAfterWrite(5, TimeUnit.MINUTES)
))
.build();
}
}
// 【解决方案】布隆过滤器 + 缓存空值
@Bean
public BloomFilter<String> bloomFilter() {
BloomFilter<String> filter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 1000000, 0.01);
// 初始化全量数据到布隆过滤器
List<Product> allProducts = productRepository.findAll();
allProducts.forEach(p -> filter.put(p.getId().toString()));
return filter;
}
// 服务层校验
public Product getProductById(Long id) {
if (!bloomFilter.mightContain(id.toString())) {
throw new InvalidRequestException("Invalid product ID");
}
return getProductFromCacheOrDB(id);
}
// 【解决方案】Redisson分布式锁
@Bean
public RedissonClient redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
return Redisson.create(config);
}
@Cacheable(value = "hotProducts", key = "#id", sync = true)
public Product getHotProduct(Long id) {
RLock lock = redisson.getLock("product_lock:" + id);
try {
lock.lock(10, TimeUnit.SECONDS);
return productRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Product not found"));
} finally {
lock.unlock();
}
}
# 【解决方案】随机过期时间
spring:
cache:
type: redis
redis:
time-to-live: 30000 # 基础TTL 30秒
time-to-live-random: 10000 # 最大随机偏移10秒
nodes: 列出所有 Redis 集群的主节点地址(端口为集群通信端口,默认 7000)。
max-redirects: 当客户端收到 MOVED 错误时(表示槽位迁移),最多跟随重定向的次数。
注意:集群模式下,Redis 自动分片存储数据,客户端通过哈希槽定位数据节点。
master: 哨兵系统中主节点的名称(需与哨兵配置中的 sentinel monitor 名称一致)。
nodes: 哨兵节点的地址列表(哨兵监听端口默认 26379)。
注意:哨兵模式提供高可用性,自动故障转移,但需至少 3 个哨兵节点组成仲裁。
上述配置同时声明了 cluster 和 sentinel,这在 Spring Boot 中会导致冲突。实际使用时需二选一:
# 集群模式(注释掉哨兵配置)
spring:
redis:
cluster:
nodes: 192.168.1.1:7000,192.168.1.2:7001
max-redirects: 3
# 哨兵模式(注释掉集群配置)
spring:
redis:
sentinel:
master: mymaster
nodes: 192.168.1.4:26379,192.168.1.5:26380
management:
endpoints:
web:
exposure:
include: health,info,redis # 暴露 Redis 监控端点
endpoint:
redis:
enabled: true # 启用 Redis 端点
spring:
redis:
ssl:
enabled: true # 启用 SSL 加密
trust-store: classpath:redis.truststore # 信任的 CA 证书
trust-store-password: changeit # 证书密码
timeout: 10000ms # 连接超时时间
jedis:
pool:
test-on-borrow: true # 借用连接时健康检查
SSL 加密:生产环境必须启用,防止数据泄露。
生成证书:使用 openssl 创建 Redis 服务器证书和 CA 证书。
配置 Java 信任库:将 CA 证书导入 redis.truststore。
连接池健康检查:test-on-borrow 确保从池中取出的连接可用,避免因网络波动导致的异常。
其他加固项:
spring:
redis:
password: your_redis_password # 设置 Redis 访问密码
database: 1 # 使用专用数据库(非默认 0)
@Cacheable(value = "hotProducts", key = "#id", condition = "#id < 1000")
public Product getHotProduct(Long id) { ... }
@Cacheable(
value = {"localCache", "redisCache"},
key = "#id",
cacheResolver = "multiLevelCacheResolver"
)
public Product getProduct(Long id) {
// 优先从本地缓存读取,未命中则查询 Redis
return productRepository.findById(id).orElse(null);
}
实现要点:
自定义 CacheResolver:
@Bean
public CacheResolver multiLevelCacheResolver(CacheManager cacheManager) {
return new MultiLevelCacheResolver(cacheManager) {{
setCaches(Arrays.asList(localCache, redisCache));
}};
}
// 【解决方案】@Async注解
@Async
@CachePut(value = "products", key = "#product.id")
public CompletableFuture<Product> asyncUpdateProduct(Product product) {
// 异步保存到数据库
return CompletableFuture.completedFuture(productRepository.save(product));
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。