Redis 配置秘籍:过期键删除与内存淘汰的最佳实践

358 阅读9分钟

Redis 的键空间是什么?

Redis 的键空间(key space)是指 Redis 数据库中存储的所有键值对的集合。可以理解为 Redis 数据库的一张大表,其中每一行是一个键值对。键空间内的键支持多种数据类型(如字符串、哈希、列表、集合、有序集合等),并且可以为键设置过期时间,来实现自动删除。

image.png


如何设置 Redis 的过期时间?

Redis 提供了多种方式来设置键的过期时间:

  1. EXPIRE 命令
    用于设置键的过期时间(以秒为单位)。
    示例:EXPIRE key 10(10秒后过期)。

  2. PEXPIRE 命令
    设置键的过期时间(以毫秒为单位)。
    示例:PEXPIRE key 10000(10秒后过期)。

  3. EXPIREAT 和 PEXPIREAT 命令
    通过指定 Unix 时间戳来设置过期时间(秒或毫秒)。
    示例:EXPIREAT key 1700000000(在特定时间点过期)。

  4. SETEX 命令
    设置键值同时指定过期时间。
    示例:SETEX key 10 value(10秒后过期)。

  5. 通过 Redis 客户端库
    使用某些编程语言的 Redis 客户端(如 Python 的 redis-py),设置过期时间通常直接通过库函数实现。

image.png

在 Redis 中,键的过期时间存储在一个专门的数据结构中,与键值分开管理。

image.png


1. 过期时间的存储结构

  • Redis 使用一个额外的哈希表(hash table),专门存储键的过期时间。
  • 键(Key):指向 Redis 主键,与数据存储的键一致。
  • 值(Value):记录该键的过期时间(以毫秒或秒为单位的 Unix 时间戳)。

例如:

键名过期时间
key11700845200000(毫秒)
key21700845300000(毫秒)

注意

  1. 如果某个键没有设置过期时间,则不会在过期时间哈希表中记录。
  2. Redis 内部通过两个哈希表分别管理数据和过期时间。

微信截图_20250122094754.png


2. 过期时间的单位

Redis 默认以毫秒为单位存储过期时间,但通过命令设置时可以选择秒级或毫秒级。

  • 秒级过期时间:使用 EXPIREEXPIREAT 命令设置。
  • 毫秒级过期时间:使用 PEXPIREPEXPIREAT 命令设置。

Redis 内部会将所有过期时间统一转换为毫秒级 Unix 时间戳存储。


3. 如何检查键的过期时间?

  • TTL(Time-To-Live)命令:查看键剩余的过期时间。

    TTL key1
    

    返回值:

    • 0:键的剩余存活时间(单位:秒)。

    • -1:键没有设置过期时间。
    • -2:键不存在或已过期。
  • PTTL 命令:查看剩余时间(以毫秒为单位)。

    PTTL key1
    

4. 过期键的删除与触发

Redis 的过期时间存储仅影响键何时被删除,具体删除依赖于以下策略:

  1. 惰性删除:当键被访问时,Redis 检查它的过期时间,过期即删除。
  2. 定期删除:Redis 定时扫描一部分键,删除过期键。
  3. 内存淘汰:当内存不足时,结合过期时间和淘汰策略清理数据。

5. 示例:查看过期时间

通过 Jedis 库操作 Redis 键的过期时间:

import redis.clients.jedis.Jedis;

public class RedisExpireTimeDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        // 设置键并指定过期时间(秒级)
        jedis.setex("key1", 60, "value1");

        // 查看过期时间
        long ttl = jedis.ttl("key1");
        System.out.println("key1 的剩余过期时间(秒): " + ttl);

        // 设置键并指定毫秒级过期时间
        jedis.pexpire("key2", 30000); // 30 秒后过期
        long pttl = jedis.pttl("key2");
        System.out.println("key2 的剩余过期时间(毫秒): " + pttl);

        jedis.close();
    }
}

Redis 的过期键删除策略是什么?

过期键是设置了过期时间的键,Redis 会在过期后将其从键空间中删除。删除策略共有 三种

1. 惰性删除(Lazy Deletion)

  • 机制:仅当键被访问时,Redis 检查其是否过期,如果过期则立即删除。
  • 优点:开销小,只有键被访问时才会检查。
  • 缺点:可能导致大量过期键占用内存,特别是如果过期键很少被访问。

2. 定期删除(Periodic Deletion)

  • 机制:Redis 会定期扫描一部分键,检查是否过期,并删除其中的过期键。
  • 优点:自动清理过期键,减少内存占用。
  • 缺点:如果扫描频率过低或扫描的键数量不足,可能导致过期键堆积;如果扫描过于频繁,则可能影响性能。

3. 内存淘汰(Memory Eviction)

  • 机制:当 Redis 内存到达限制时,触发淘汰策略,优先清理过期键。
  • 优点:在内存紧张时减少占用。
  • 缺点:需要触发内存淘汰,且无法实时清理所有过期键。

Redis 的内存淘汰策略是什么?

当 Redis 的内存使用达到限制(由 maxmemory 配置指定)时,为了腾出空间存储新数据,会触发内存淘汰策略。Redis 提供了 六种内存淘汰策略

1. noeviction(不淘汰)

  • 机制:拒绝写入操作,直接返回错误。
  • 优点:数据完全保留,适合读多写少场景。
  • 缺点:可能影响写操作的正常进行。

2. allkeys-lru

  • 机制:从所有键中淘汰最近最少使用的键。
  • 优点:优先保留热点数据。
  • 缺点:需要额外维护 LRU 数据结构,增加内存开销。

3. volatile-lru

  • 机制:从设置了过期时间的键中淘汰最近最少使用的键。
  • 优点:结合过期时间管理,减少无用数据。
  • 缺点:如果大部分键没有过期时间,会导致淘汰效果不明显。

4. allkeys-random

  • 机制:随机淘汰键(所有键中随机选择)。
  • 优点:实现简单,适用于无显著热点的场景。
  • 缺点:可能导致热点数据被淘汰。

5. volatile-random

  • 机制:从设置了过期时间的键中随机淘汰。
  • 优点:只淘汰可过期的键,减少重要数据被误删的概率。
  • 缺点:淘汰效果不够精准。

6. volatile-ttl

  • 机制:从设置了过期时间的键中优先淘汰即将过期的键。
  • 优点:更智能的淘汰策略,减少即将无效数据的占用。
  • 缺点:需要频繁维护过期时间排序,开销较高。

image.png


如何设置Redis 的过期键删除策略和内存淘汰策略?

Redis 的过期键删除策略如何设置?

Redis 的过期键删除策略不能直接通过客户端设置,而是由 Redis 服务端自动处理。用户可以通过调整 Redis 配置文件来优化:

  1. 配置文件设置(redis.conf)

    • 定期删除相关参数:
      hz 10  # Redis 每秒的主循环频率,影响定期删除检查频率,默认是10。
      
    • 惰性删除和定期删除均为 Redis 默认的机制,无需手动启用。
  2. Java 示例:设置键的过期时间
    使用 Java 的 Jedis 库:

    import redis.clients.jedis.Jedis;
    
    public class RedisExpirationDemo {
        public static void main(String[] args) {
            // 连接 Redis
            Jedis jedis = new Jedis("localhost", 6379);
    
            // 设置一个键,并指定 10 秒后过期
            jedis.setex("key1", 10, "value1");
    
            // 设置一个键,并使用 EXPIRE 命令指定过期时间
            jedis.set("key2", "value2");
            jedis.expire("key2", 20);
    
            System.out.println("key1: " + jedis.get("key1"));
            System.out.println("key2: " + jedis.get("key2"));
    
            // 关闭连接
            jedis.close();
        }
    }
    

Redis 的内存淘汰策略

通过设置 Redis 的 maxmemorymaxmemory-policy 配置。

在 Redis 配置文件 redis.conf 中:

# 配置过期键删除策略
# noeviction:不淘汰
# allkeys-lru:所有键中最近最少使用的键
# volatile-lru:仅过期键中最近最少使用的键
# allkeys-random:所有键中随机淘汰
# volatile-random:仅过期键中随机淘汰
# volatile-ttl:仅过期键中优先淘汰即将过期的键
maxmemory-policy volatile-lru

或者在运行时通过命令设置:

127.0.0.1:6379> CONFIG SET maxmemory-policy volatile-lru

示例:

# 设置 Redis 内存上限
maxmemory 256mb

# 设置内存淘汰策略
maxmemory-policy allkeys-lru

同样,也可以通过运行时命令设置:

127.0.0.1:6379> CONFIG SET maxmemory 268435456  # 设置为256MB
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru

实际项目中的应用场景

  1. 电商秒杀系统
    • 场景:限时优惠的商品数据需要设置过期时间,避免占用内存。
    • 策略
      • 过期键删除:定期删除 + 惰性删除
      • 淘汰策略:volatile-ttl,优先清理即将过期的键。
  • 电商秒杀场景需要设置过期时间,确保商品活动结束后,数据不再占用 Redis。
import redis.clients.jedis.Jedis;

public class RedisSeckillSystem {
    public static void main(String[] args) {
        // 连接 Redis
        Jedis jedis = new Jedis("localhost", 6379);
        
        // 设置过期时间的商品库存
        String productKey = "product:1001:stock";
        jedis.setex(productKey, 3600, "100");  // 设置商品库存,过期时间为1小时
        
        // 模拟秒杀逻辑
        int remainingStock = Integer.parseInt(jedis.get(productKey));
        if (remainingStock > 0) {
            jedis.decr(productKey);  // 减少库存
            System.out.println("秒杀成功!剩余库存:" + jedis.get(productKey));
        } else {
            System.out.println("秒杀结束,商品已售罄!");
        }
        
        // 关闭连接
        jedis.close();
    }
}

  1. 实时数据缓存
    • 场景:热点数据需要经常更新,避免无关数据占用内存。
    • 策略
      • 淘汰策略:allkeys-lru,优先保留最常访问的热点数据。
  • 热点数据缓存,使用 LRU 策略优先保留最近访问的数据。
import redis.clients.jedis.Jedis;

public class RedisCache {
    public static void main(String[] args) {
        // 连接 Redis
        Jedis jedis = new Jedis("localhost", 6379);
        
        // 配置 Redis 内存上限和淘汰策略
        jedis.configSet("maxmemory", "256mb");
        jedis.configSet("maxmemory-policy", "allkeys-lru");
        
        // 模拟写入热点数据
        for (int i = 1; i <= 1000; i++) {
            String key = "user:" + i;
            jedis.set(key, "data" + i);
        }
        
        // 模拟访问热点数据
        jedis.get("user:1");
        jedis.get("user:2");
        jedis.get("user:3");
        
        System.out.println("热点数据缓存模拟完成!");
        
        // 关闭连接
        jedis.close();
    }
}

  1. 用户会话管理
    • 场景:用户登录状态需要定期过期。
    • 策略
      • 过期键删除:惰性删除,仅在会话被访问时清理。
      • 淘汰策略:volatile-lru,优先淘汰过期会话数据。
  • 用户会话数据需要设置过期时间,采用 volatile-lru 策略管理内存。
import redis.clients.jedis.Jedis;

public class RedisSessionManagement {
    public static void main(String[] args) {
        // 连接 Redis
        Jedis jedis = new Jedis("localhost", 6379);
        
        // 设置内存淘汰策略
        jedis.configSet("maxmemory", "128mb");
        jedis.configSet("maxmemory-policy", "volatile-lru");
        
        // 模拟用户登录会话数据
        String sessionKey = "session:user:123";
        jedis.setex(sessionKey, 1800, "user_data");  // 设置会话数据过期时间为30分钟
        
        // 检查会话是否有效
        String sessionData = jedis.get(sessionKey);
        if (sessionData != null) {
            System.out.println("用户会话有效,数据:" + sessionData);
        } else {
            System.out.println("用户会话已过期!");
        }
        
        // 关闭连接
        jedis.close();
    }
}

通过合理组合过期键删除策略和内存淘汰策略,可以根据项目需求优化性能和资源利用率。