Redis 中key自动过期和内存淘汰策略

一、简介

  本文今天主要是介绍Redis中key自动过期,以及redis对应的Java实现该怎么用。因为篇幅问题,我这里写了一个测试类,引入 RedisTemplate对象,后面例子里就不一一引入了。大家理解就行,如果大家还不知道如何通过Spring Boot 整合redis则可以查看我之前的文章:SpringBoot整合redis(redis支持单节点和集群)

package com.alian.datastruct;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RedisAutoExpiredTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
}

二、常用方法

2.1、EXPIRE、PEXPIRE

  • EXPIRE 设置秒级精度的生存时间
  • PEXPIRE 设置毫秒级精度的生存时间

语法

EXPIRE key seconds
PEXPIRE key milliseconds

命令操作

127.0.0.1:6379> set name alian    #设置一个key,演示秒级过期
OK
127.0.0.1:6379> expire name 5     #设置过期时间为5秒
(integer) 1
127.0.0.1:6379> get name     #5秒内访问,键存在
"alian"
127.0.0.1:6379> get name     #5秒之后访问,键不存在了
(nil)
127.0.0.1:6379>
127.0.0.1:6379> set number 99    #设置一个key,演示毫秒级过期
OK
127.0.0.1:6379> pexpire number 7500   #设置过期时间为7500毫秒
(integer) 1
127.0.0.1:6379> get number    #7500毫秒内访问,键存在
"99"
127.0.0.1:6379> get number    #7500毫秒之后访问,键不存在了
(nil)

  需要注意的是,如果 key 还在生存期内,再次执行 EXPIRE key seconds或者 PEXPIRE key milliseconds ,那么它的生存时间将重置为后面设置的生存时间,而不是在原有的时间上加或者减。

Java操作

    @Test
    public void expireAndPExpire() {
        // 给键name设置值为alian
        redisTemplate.opsForValue().set("name", "alian");
        // 设置键name生存时间为5秒
        redisTemplate.expire("name", 5, TimeUnit.SECONDS);
        // 给键number设置值为99
        redisTemplate.opsForValue().set("number", "99");
        // 设置键number生存时间为7500毫秒
        redisTemplate.expire("number", 7500, TimeUnit.MILLISECONDS);
        log.info("name = {}", redisTemplate.opsForValue().get("name"));
        log.info("number = {}", redisTemplate.opsForValue().get("number"));
        try {
            log.info("开始休眠8秒");
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("name = {}", redisTemplate.opsForValue().get("name"));
        log.info("number = {}", redisTemplate.opsForValue().get("number"));
    }
name = alian
number = 99
开始休眠8秒
name = null
number = null

2.2、SET命令的EX选项和PX选项

  • SET命令的EX选项 设置值的时候,同时设置生存时间(秒级精度)
  • SET命令的PX选项 设置值的时候,同时设置生存时间(毫秒级精度)

语法

set key value EX seconds
set key value PX milliseconds

命令操作

我们上面小节的命令

set key value 
EXPIRE key seconds

可以变成一条命令,同时还能保证原子性。

set key value EX seconds

具体的操作如下:

127.0.0.1:6379> set name alian EX 5
OK
127.0.0.1:6379> get name
"alian"
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>
127.0.0.1:6379> set number 99 PX 7500
OK
127.0.0.1:6379> get number
"99"
127.0.0.1:6379> get number
(nil)

Java操作

    @Test
    public void expireAndPExpire() {
        // 给键name设置秒级精度,生存时间为5秒
        redisTemplate.opsForValue().set("name", "alian", 5, TimeUnit.SECONDS);
        // 给键number设置毫秒级精度,生存时间为7500毫秒
        redisTemplate.opsForValue().set("number", "99", 7500, TimeUnit.MILLISECONDS);
        log.info("name = {}", redisTemplate.opsForValue().get("name"));
        log.info("number = {}", redisTemplate.opsForValue().get("number"));
        try {
            log.info("开始休眠8秒");
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("name = {}", redisTemplate.opsForValue().get("name"));
        log.info("number = {}", redisTemplate.opsForValue().get("number"));
    }
name = alian
number = 99
开始休眠8秒
name = null
number = null

2.3、EXPIREAT、PEXPIREAT

  • EXPIREAT 设置秒级过期时间
  • PEXPIREAT 设置毫秒级过期时间

语法

EXPIREAT key seconds_timestamp
PEXPIREAT key milliseconds_timestamp

命令操作

127.0.0.1:6379> set name alian
OK
127.0.0.1:6379> expireat name 1672307999   #秒级
(integer) 1
127.0.0.1:6379> get name
"alian"
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> pexpireat name 1672307999000     #毫秒级
(integer) 1
127.0.0.1:6379> get name
"alian"
127.0.0.1:6379> get name
(nil)

Java操作

    @Test
    public void expireAt() {
        // 给键name设置值为alian
        redisTemplate.opsForValue().set("name", "alian");
        // 2022-12-29 17:59:59
        LocalDateTime date = LocalDateTime.of(2022, 12, 29, 17, 59, 59);
        // 转为date,通过Java8时间,你可以随意加减时间
        Date expireTime = Date.from(date.atZone(ZoneId.systemDefault()).toInstant());
        // 设置键name在2022-12-29 17:59:59过期
        redisTemplate.expireAt("name", expireTime);
        log.info("name = {}", redisTemplate.opsForValue().get("name"));
        try {
            // 当前时间和指定过期时间的差值
            long millis = ChronoUnit.MILLIS.between(LocalDateTime.now(), date) + 2000;
            log.info("开始休眠{}毫秒秒", millis);
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("name = {}", redisTemplate.opsForValue().get("name"));
    }
name = alian
开始休眠308174毫秒秒
name = null

2.4、TTL、PTTL

  • TTL 获取键的生存时间(秒级)
  • PTTL 获取键的生存时间(毫秒级)

语法

TTL key
PTTL key
  • 返回值0:可能生存时间小于0,要使用更高精度获取了
  • 返回值-1:没有设置生存时间或者过期时间
  • 返回值-2:键不存在了

命令操作

127.0.0.1:6379> set age 28
OK
127.0.0.1:6379> ttl age
(integer) -1        #未设置过期时间
127.0.0.1:6379> set name alian EX 30
OK
127.0.0.1:6379> ttl name
(integer) 26
127.0.0.1:6379> ttl name
(integer) 23
127.0.0.1:6379> ttl name
(integer) 22
127.0.0.1:6379> set number 99 PX 10000
OK
127.0.0.1:6379> pttl number
(integer) 5794
127.0.0.1:6379> pttl number
(integer) 1451
127.0.0.1:6379> pttl number
(integer) -2       #键不存在

Java操作

    @Test
    public void ttlAndPTtl() {
        redisTemplate.opsForValue().set("age", "28");
        Long ageExpire = redisTemplate.getExpire("age", TimeUnit.SECONDS);
        log.info("age过期时间是:{}", ageExpire);
        // 给键name设置值为alian,同时设置生存时间为5秒
        redisTemplate.opsForValue().set("name", "alian", 5, TimeUnit.SECONDS);
        // 给键number设置值为99,同时设置生存时间为7500毫秒
        redisTemplate.opsForValue().set("number", "99", 7500, TimeUnit.MILLISECONDS);

        // 获取name过期时间
        Long nameExpire = redisTemplate.getExpire("name", TimeUnit.SECONDS);
        log.info("name过期时间是:{}", nameExpire);
        // 获取number过期时间
        Long numberExpire = redisTemplate.getExpire("number", TimeUnit.MILLISECONDS);
        log.info("number过期时间是:{}", numberExpire);
    }
age过期时间是:-1
name过期时间是:4
number过期时间是:7499

  在使用 TTL 命令时,有时候会遇到命令返回0的情况,出现这种情况的原因在于,命令只能返回秒级精度的生存时间,当给定的key生存时间不足1s时,就会返回0了,所以需要使用更高精度的 PTTL 去获取了。

三、部分数据结构过期说明

  Redis中的数据结构 List Hash Set SortedSet GEO Stream ,它们可能包含很多键值对,这些数据结构不支持对数据结构中某个键值对进行过期设置的,只能对这个数据结构的 Key 进行过期设置(不是数据结构里面的键值对中的键)比如:

命令方式

EXPIRE key seconds
PEXPIRE key milliseconds

Java方式

redisTemplate.expire(K key, final long timeout, final TimeUnit unit)

如果你想从这些数据结构中弄掉这些键值对,你只能通过对应的删除方法去移除了。比如

  • List :使用 lpop rpop rpoplpush blpop
  • Hash :使用 hdel
  • Set :使用 srem
  • SortedSet :使用 zrem
  • GEO :使用 zrem
  • Stream :使用 xdel

  对于key的删除一定要谨慎,有时候遇到了 big key ,同步的删除就会导致阻塞,从而影响到整个redis处理,导致其他服务读取超时,如果后面有时间,专门对 big key 进行讲解。

四、Redis内存淘汰策略

4.1、LRU和LFU

  • LRU:全称 Least Recently Used,意思为淘汰掉最近最少使用的键值对
  • LFU:全称 Least Frequently Used,意思为淘汰掉使用频率最低的键值对

4.2、八种淘汰策略

策略说明
allkeys-lru在所有键值对中,通过LRU算法淘汰最近最少使用的键值对
allkeys-lfu在所有键值对中,通过LFU算法移除使用频率最低的键值对(Redis 4.0版本后
allkeys-random在所有键值对中,随机移除键值对
volatile-lru在设置了过期时间的键值对中,通过LRU算法淘汰最近最少使用的键值对
volatile-lfu在设置了过期时间的键值对中,通过LFU算法移除使用频率最低的键值对(Redis 4.0版本后
volatile-random在设置了过期时间的键值对中,随机移除键值对
volatile-ttl在设置了过期时间的键值对中,移除将要过期的键值对
noeviction默认值,禁止淘汰数据,内存不足时报错

4.3、最大内存配置

  通过redis的配置文件(*.conf),可以搜到如下配置:

maxmemory <bytes>

  一般推荐Redis设置内存为最大物理内存的四分之三,需要注意的是这里的单位是字节,假设你内存是2G

#1G=1024M
#2G=2048M
#1M=1024kb
#1kb=1024b
#maxmemory 1024*2*0.75*1024*1024
maxmemory 1610612736

  也就是当内存里的数据占用达到最大限制maxmemory时,就会触发上面介绍的内存淘汰策略。

4.4、策略配置

  那怎么设置的呢?同样在配置文件中(*.conf)找到:

# The default is:
#
# maxmemory-policy noeviction

  这里看到Redis 默认的策略就是noeviction,如果想改成allkeys-lru则:

maxmemory-policy allkeys-lru

  Redis 提供了一个配置参数 maxmemory-samples,这个参数就是 Redis 选出的数据个数(默认情况下,Redis将检查五个键并选择一个最近使用较少的)。默认值为5会产生足够好的效果;10非常接近真正的LRU算法的,但需要更多的CPU;3的速度更快,但不是很准确。配置如下:

maxmemory-samples 5
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值