目录
一、简介
本文今天主要是介绍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