前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Java-中间件-一小时入门Redis(下)

Java-中间件-一小时入门Redis(下)

原创
作者头像
咸鱼程序员
修改2025-03-11 23:22:34
修改2025-03-11 23:22:34
160
举报
文章被收录于专栏:Java-中间件相关Java-中间件相关

Redis

上篇讲解了 Redis 基本内容,下篇将讲解如何将 Redis 快速的加入项目,并说明几个重要的解决方案内容示例。

极速Redis集成

添加依赖

pom.xml中添加Redis和缓存支持依赖:

代码语言:xml
复制
<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>

配置 Redis 连接

application.yml中配置(根据实际环境修改):

代码语言: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启动类添加注解:

代码语言:java
复制
@SpringBootApplication
@EnableCaching // 启用缓存注解驱动(底层基于AOP实现)
public class ErpApplication {
    public static void main(String[] args) {
        SpringApplication.run(ErpApplication.class, args);
    }
}

基础缓存注解

在 Service 实现层直接使用注解管理缓存

代码语言:java
复制
@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);
    }
}

自定义缓存配置,可选择是否添加

代码语言:java
复制
@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();
    }
}

高频问题解决方案

缓存穿透(请求不存在的数据)

代码语言:java
复制
// 【解决方案】布隆过滤器 + 缓存空值
@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);
}

缓存击穿(热点key突然失效)

代码语言:java
复制
// 【解决方案】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();
    }
}

缓存雪崩(大量key同时过期)

代码语言:yml
复制
# 【解决方案】随机过期时间
spring:
  cache:
    type: redis
    redis:
      time-to-live: 30000 # 基础TTL 30秒
      time-to-live-random: 10000 # 最大随机偏移10秒

相关扩展建议

集群/哨兵配置

  • 集群模式(Cluster)

nodes: 列出所有 Redis 集群的主节点地址(端口为集群通信端口,默认 7000)。

max-redirects: 当客户端收到 MOVED 错误时(表示槽位迁移),最多跟随重定向的次数。

注意:集群模式下,Redis 自动分片存储数据,客户端通过哈希槽定位数据节点。

  • 哨兵模式(Sentinel)

master: 哨兵系统中主节点的名称(需与哨兵配置中的 sentinel monitor 名称一致)。

nodes: 哨兵节点的地址列表(哨兵监听端口默认 26379)。

注意:哨兵模式提供高可用性,自动故障转移,但需至少 3 个哨兵节点组成仲裁。

上述配置同时声明了 cluster 和 sentinel,这在 Spring Boot 中会导致冲突。实际使用时需二选一:

代码语言:yml
复制
# 集群模式(注释掉哨兵配置)
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 开放 Redis 监控端点(默认 /actuator/redis)。
  • 监控内容: 缓存命中率(cacheHits / cacheMisses) 缓存大小(cacheSize) 命令统计(commandStats)
代码语言:yml
复制
management:
  endpoints:
    web:
      exposure:
        include: health,info,redis # 暴露 Redis 监控端点
  endpoint:
    redis:
      enabled: true # 启用 Redis 端点

安全内容

代码语言:yml
复制
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 确保从池中取出的连接可用,避免因网络波动导致的异常。

其他加固项:

代码语言:yml
复制
spring:
  redis:
    password: your_redis_password # 设置 Redis 访问密码
    database: 1 # 使用专用数据库(非默认 0)

冷热数据分离

代码语言:java
复制
@Cacheable(value = "hotProducts", key = "#id", condition = "#id < 1000")
public Product getHotProduct(Long id) { ... }
  • 设计意图: 条件缓存:仅缓存 id < 1000 的商品(假设为热销品),减少缓存冗余。
  • 适用场景: 用户频繁访问的头部数据(如排行榜、爆款商品)。 需要区分数据访问频率的场景。

多级缓存

代码语言:java
复制
@Cacheable(
    value = {"localCache", "redisCache"}, 
    key = "#id", 
    cacheResolver = "multiLevelCacheResolver"
)
public Product getProduct(Long id) {
    // 优先从本地缓存读取,未命中则查询 Redis
    return productRepository.findById(id).orElse(null);
}

实现要点:

自定义 CacheResolver:

代码语言:java
复制
@Bean
public CacheResolver multiLevelCacheResolver(CacheManager cacheManager) {
    return new MultiLevelCacheResolver(cacheManager) {{
        setCaches(Arrays.asList(localCache, redisCache));
    }};
}
  • 优势: 本地缓存(如 Caffeine)降低远程 Redis 访问延迟。 Redis 作为共享缓存兜底,保证数据一致性。

缓存异步更新

代码语言:java
复制
// 【解决方案】@Async注解
@Async
@CachePut(value = "products", key = "#product.id")
public CompletableFuture<Product> asyncUpdateProduct(Product product) {
    // 异步保存到数据库
    return CompletableFuture.completedFuture(productRepository.save(product));
}
  • 适用场景: 高并发写入:避免同步更新缓存阻塞主线程。 最终一致性:允许短暂的数据不一致(如允许 Redis 缓存旧值,后续异步更新)。 注意:需确保异步任务完成后,缓存与数据库最终一致。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Redis
    • 极速Redis集成
      • 添加依赖
      • 配置 Redis 连接
      • 基础缓存注解
    • 高频问题解决方案
      • 缓存穿透(请求不存在的数据)
      • 缓存击穿(热点key突然失效)
      • 缓存雪崩(大量key同时过期)
    • 相关扩展建议
      • 集群/哨兵配置
      • 监控与告警
      • 安全内容
      • 冷热数据分离
      • 多级缓存
      • 缓存异步更新
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档