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

🤟致敬读者

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

📘博主相关


文章目录

      • [Java 中 Redis 过期策略深度解析](#Java 中 Redis 过期策略深度解析)
        • [一、Redis 过期策略核心原理回顾](#一、Redis 过期策略核心原理回顾)
        • [二、Java 中的过期操作 API](#二、Java 中的过期操作 API)
          • [1. Jedis 客户端操作](#1. Jedis 客户端操作)
          • [2. Spring Data Redis 操作](#2. Spring Data Redis 操作)
        • [三、Java 中的策略实践要点](#三、Java 中的策略实践要点)
          • [1. 过期时间设置策略](#1. 过期时间设置策略)
          • [2. 大Key过期优化](#2. 大Key过期优化)
          • [3. 缓存穿透/击穿防护](#3. 缓存穿透/击穿防护)
        • 四、生产环境最佳实践
          • [1. 过期键监控](#1. 过期键监控)
          • [2. 动态调整策略](#2. 动态调整策略)
          • [3. 集群环境注意事项](#3. 集群环境注意事项)
        • 五、常见问题排查
          • [1. 内存未释放问题](#1. 内存未释放问题)
          • [2. 过期键未删除问题](#2. 过期键未删除问题)
        • 六、高级特性应用
          • [1. Redisson 过期监听](#1. Redisson 过期监听)
          • [2. RedisJSON 过期扩展](#2. RedisJSON 过期扩展)
      • [总结:Java 开发者必备技能](#总结:Java 开发者必备技能)
    • 拓展(Redis内存淘汰策略列举)

📃文章前言

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

Java 中 Redis 过期策略深度解析

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


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

    • 惰性删除:访问时检查过期时间,若过期则立即删除

    • 定期删除 :Redis 每秒执行 10 次(可配置)的过期扫描

      bash 复制代码
      # redis.conf 配置
      hz 10  # 每秒扫描频率
  2. 内存淘汰机制

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


二、Java 中的过期操作 API
1. Jedis 客户端操作
java 复制代码
// 设置键值对并指定过期时间(秒)
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 操作
java 复制代码
// 注解方式设置缓存过期
@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:避免缓存雪崩

    java 复制代码
    // 基础过期时间 + 随机偏移量
    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+ 异步删除配置:

java 复制代码
// 启动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. 缓存穿透/击穿防护
java 复制代码
// 双重检查锁解决缓存击穿
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. 过期键监控
java 复制代码
// 获取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. 动态调整策略
java 复制代码
// 根据负载动态调整过期时间
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. 集群环境注意事项
  • 主从延迟 :主节点删除后从节点可能短暂存在过期数据

    java 复制代码
    // 强制读主节点解决脏读
    if (consistencyRequired) {
        jedis.readonly(); // 关闭只读模式(默认从主节点读)
    }
  • 跨数据中心 :使用 Redisson 的 RRemoteService

    java 复制代码
    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. 过期键未删除问题

原因

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

解决方案

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

六、高级特性应用
1. Redisson 过期监听
java 复制代码
// 监听特定键过期事件
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 过期扩展
java 复制代码
// 使用 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/sec 0 任何驱逐发生
    mem_fragmentation_ratio 1.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


📜文末寄语

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

相关推荐
whltaoin10 分钟前
Java面试专项一-准备篇
java·面试
至善迎风14 分钟前
一键更新依赖全指南:Flutter、Node.js、Kotlin、Java、Go、Python 等主流语言全覆盖
java·flutter·node.js
TCChzp14 分钟前
Kafka入门-集群基础环境搭建(JDK/Hadoop 部署 + 虚拟机配置 + SSH 免密+Kafka安装启动)
java·hadoop·kafka
量子炒饭大师17 分钟前
项目实战——C语言扫雷游戏
c语言·开发语言·游戏
饮长安千年月17 分钟前
JavaSec-专题-反序列化
java·开发语言·web安全·网络安全·系统安全·可信计算技术·安全架构
链上Sniper27 分钟前
智能合约安全漏洞解析:从 Reentrancy 到 Integer Overflow
开发语言·网络·架构·区块链·php·智能合约
Rocky40131 分钟前
JavaEE->多线程:定时器
java·开发语言·多线程·定时器
SimonKing39 分钟前
揭秘自定义注解,背后的面向切面编程(AOP)的艺术
java·后端·架构
纪元A梦40 分钟前
分布式拜占庭容错算法——实现工作量证明(PoW)算法详解
java·分布式·算法