Java 中 Redis 过期策略深度解析(含拓展-redis内存淘汰策略列举)

🤟致敬读者

  • 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉

📘博主相关



📃文章前言

  • 🔷文章均为学习工作中整理的笔记。
  • 🔶如有错误请指正,共同学习进步。

Java 中 Redis 过期策略深度解析

在这里插入图片描述

在 Java 应用中,Redis 的过期策略是缓存管理的核心机制,直接关系到内存使用效率和系统性能。下面从原理到实践全面解析:


一、Redis 过期策略核心原理回顾
  1. 双重删除策略

    • 惰性删除:访问时检查过期时间,若过期则立即删除
    • 定期删除:Redis 每秒执行 10 次(可配置)的过期扫描
      # redis.conf 配置
      hz 10  # 每秒扫描频率
      
  2. 内存淘汰机制

    内存达到 maxmemory
    淘汰策略
    volatile-lru
    volatile-ttl
    volatile-random
    allkeys-lru
    noeviction

二、Java 中的过期操作 API
1. Jedis 客户端操作
// 设置键值对并指定过期时间(秒)
jedis.setex("user:session:1001", 1800, "session_data"); 

// 单独设置过期时间
jedis.expire("cache:product:2023", 3600);  // 秒
jedis.pexpire("temp:data", 5000L);         // 毫秒

// 获取剩余时间
long ttl = jedis.ttl("user:session:1001"); // 秒
long pttl = jedis.pttl("cache:product:2023"); // 毫秒
2. Spring Data Redis 操作
// 注解方式设置缓存过期
@Cacheable(value = "users", key = "#userId", 
           cacheManager = "customCacheManager")
public User getUser(String userId) {
    // ...
}

// 配置自定义 CacheManager
@Bean
public RedisCacheManager customCacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(30)) // 全局默认30分钟
        .serializeValuesWith(SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(User.class)));
    
    return RedisCacheManager.builder(factory)
           .cacheDefaults(config)
           .withCacheConfiguration("users", 
                RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(2))) // 特定缓存2小时
           .build();
}

三、Java 中的策略实践要点
1. 过期时间设置策略
  • 动态 TTL:避免缓存雪崩

    // 基础过期时间 + 随机偏移量
    int baseExpire = 3600; // 1小时
    int randomOffset = new Random().nextInt(600); // 0-10分钟随机
    jedis.setex("hot_product", baseExpire + randomOffset, productData);
    
  • 分级过期

    数据类型建议 TTL说明
    用户会话30-60分钟高安全性要求
    商品详情2-4小时中等更新频率
    全局配置永久(不设)极少变更
2. 大Key过期优化

Redis 6.0+ 异步删除配置:

// 启动Redis时配置异步删除
new RedisServer("redis-server", 
    "--lazyfree-lazy-expire yes",
    "--lazyfree-lazy-eviction yes");
    
// Redisson 处理大Hash
RMapCache<String, Product> map = redisson.getMapCache("products");
map.expire(2, TimeUnit.HOURS); // 整个Map过期
3. 缓存穿透/击穿防护
// 双重检查锁解决缓存击穿
public Product getProduct(String id) {
    String key = "product:" + id;
    String data = jedis.get(key);
    
    if ("".equals(data)) return null; // 空值缓存
    
    if (data == null) {
        synchronized (this) {
            data = jedis.get(key);
            if (data == null) {
                Product product = db.getProduct(id);
                if (product == null) {
                    jedis.setex(key, 300, ""); // 空值缓存5分钟
                    return null;
                }
                jedis.setex(key, 3600, serialize(product));
                return product;
            }
        }
    }
    return deserialize(data);
}

四、生产环境最佳实践
1. 过期键监控
// 获取Redis统计信息
String stats = jedis.info("stats");
Pattern pattern = Pattern.compile("expired_keys:(\\d+)");
Matcher matcher = pattern.matcher(stats);
if (matcher.find()) {
    long expiredKeys = Long.parseLong(matcher.group(1));
    metrics.record("redis.expired_keys", expiredKeys);
}

// Spring Boot Actuator 监控
@Bean
public MeterRegistryCustomizer<MeterRegistry> redisMetrics() {
    return registry -> {
        registry.gauge("redis.expired_keys", 
            Tags.of("host", redisHost),
            () -> jedis.info("stats").contains("expired_keys:") ? 
                   Long.parseLong(jedis.info("stats").split("expired_keys:")[1].split("\r")[0]) : 0
        );
    };
}
2. 动态调整策略
// 根据负载动态调整过期时间
int getDynamicTTL() {
    double load = getSystemLoad();
    if (load > 0.8) return 600;   // 高负载时缩短TTL
    if (load < 0.3) return 3600;  // 低负载时延长TTL
    return 1800;                  // 默认30分钟
}

jedis.setex("cache:data", getDynamicTTL(), data);
3. 集群环境注意事项
  • 主从延迟:主节点删除后从节点可能短暂存在过期数据
    // 强制读主节点解决脏读
    if (consistencyRequired) {
        jedis.readonly(); // 关闭只读模式(默认从主节点读)
    }
    
  • 跨数据中心:使用 Redisson 的 RRemoteService
    RRemoteService remoteService = redisson.getRemoteService();
    remoteService.register(ProductService.class, productServiceImpl, 
        RemoteInvocationOptions.defaults().timeout(3, TimeUnit.SECONDS));
    

五、常见问题排查
1. 内存未释放问题

现象INFO memory 显示内存未减少
排查步骤

  1. 检查 maxmemory-policy 配置
  2. 监控 evicted_keysexpired_keys 计数器
  3. 使用 redis-cli --bigkeys 分析大Key
  4. 检查是否启用异步删除(Redis 6.0+)
2. 过期键未删除问题

原因

  • 键长期未被访问(惰性删除未触发)
  • 定期删除扫描未命中(概率性遗漏)
  • 主从同步延迟

解决方案

// 主动触发过期扫描(生产慎用)
jedis.configSet("hz", 100);  // 临时提高扫描频率
Thread.sleep(5000);          // 等待5秒
jedis.configSet("hz", 10);   // 恢复默认

六、高级特性应用
1. Redisson 过期监听
// 监听特定键过期事件
RMapCache<String, String> map = redisson.getMapCache("sessions");
map.addListener(new ExpiredListener<String, String>() {
    @Override
    public void onExpired(EntryEvent<String, String> event) {
        log.info("Session expired: {}", event.getKey());
        // 触发清理动作
    }
});
2. RedisJSON 过期扩展
// 使用 RedisJSON 模块设置字段级过期
JSONObject product = new JSONObject();
product.put("id", 1001);
product.put("name", "Laptop");
product.put("price", 999.99);

// 设置整体过期
jedis.jsonSetWithEscape("product:1001", product, 3600);

// 设置字段级过期(需要RedisJSON 2.6+)
jedis.sendCommand(
    Command.JSON_SET, 
    "product:1001", 
    ".price", 
    "\"899.99\"", 
    "EX", 
    "600" // 价格字段10分钟后过期
);

总结:Java 开发者必备技能

  1. 策略选择

    • 会话数据 → volatile-ttl
    • 高频访问数据 → volatile-lru
    • 全局数据 → allkeys-lru
  2. 性能口诀

    “小Key高频用惰删,大Key过期启异步;
    动态TTL防雪崩,双删机制保一致”

  3. 监控指标

    指标健康阈值报警条件
    expired_keys/sec<1000持续>5000
    evicted_keys/sec0任何驱逐发生
    mem_fragmentation_ratio1.0-1.5>1.8 或 <0.9

掌握这些知识,你将在 Java 项目中构建高效可靠的 Redis 缓存系统,轻松应对高并发场景下的数据过期挑战。



拓展(Redis内存淘汰策略列举)

Redis 提供了几种内存淘汰策略来处理当可用内存不足时如何自动删除键以释放空间的问题。以下是 Redis 中常见的几种内存淘汰策略:

noeviction 默认的

这是默认的策略。当内存使用达到上限并且客户端尝试执行会导致更多内存使用的命令(比如添加新数据)时,Redis 会返回错误。

实现方式:Redis 直接拒绝执行可能导致内存增加的命令。

例子:假设 Redis 已经达到内存上限,此时执行SET命令添加新的键值对,Redis 会返回错误并拒绝该操作。

volatile-lru

从设置了过期时间的键值对中,移除最近最少使用的键值对。

实现方式:Redis 会维护一个记录设置了过期时间的键的访问时间的队列,当需要淘汰数据时,从队列尾部移除元素。

例子:有多个设置了过期时间的键key1、key2和key3,其中key1最近访问最少,当内存不足时,key1会被淘汰。

volatile-ttl

移除即将过期的键值对,也就是剩余生存时间(TTL)最短的键值对。

实现方式:Redis 会遍历设置了过期时间的键,比较它们的 TTL,选择 TTL 最小的进行淘汰。

例子:键keyA的 TTL 为 10 秒,键keyB的 TTL 为 5 秒,当内存不足时,keyB会被优先淘汰。

volatile-random

在设置了过期时间的键值对中,随机移除某个键值对。

实现方式:通过随机算法从设置了过期时间的键集合中选择一个进行淘汰。

例子:在一组设置了过期时间的键中,随机选取一个如keyX进行淘汰。

allkeys-lru

从所有键值对中,移除最近最少使用的键值对。

实现方式:Redis 维护一个所有键的访问时间队列,淘汰时从队列尾部移除。

例子:包括设置了过期时间和未设置过期时间的多个键,如keyC最近访问最少,当内存不足时,keyC被淘汰。

allkeys-random

从所有键值对中,随机移除某个键值对。

实现方式:通过随机算法从所有键集合中选择一个进行淘汰。

例子:在所有键中,随机选择如keyY进行淘汰。

该拓展部分参考文章:https://cloud.tencent.com/developer/news/1677151


📜文末寄语

  • 🟠关注我,获取更多内容。
  • 🟡技术动态、实战教程、问题解决方案等内容持续更新中。
  • 🟢《全栈知识库》技术交流和分享社区,集结全栈各领域开发者,期待你的加入。
  • 🔵​加入开发者的《专属社群》,分享交流,技术之路不再孤独,一起变强。
  • 🟣点击下方名片获取更多内容🍭🍭🍭👇

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值