Java操作redis设置第二天凌晨过期的解决方案是什么

Java操作redis设置第二天凌晨过期场景

在做查询数据的时候,遇到了需要设置数据在redis中第二天过期的问题,但是redis又没有对应的API,就只好自己来解决了

思路

计算出第二天凌晨与当前时间的时间差,将该时间差设置为redis的过期时间,就可以达到我们想要的效果

代码/**
* 计算第二天凌晨与当前时间的时间差秒数
* @param
* @return java.lang.Long
* @author shy
* @date 2021/3/12 18:10
*/
public static Long getNowToNextDaySeconds() {
Calendar cal = Calendar.getInstance();

cal.add(Calendar.DAY_OF_YEAR, 1);

cal.set(Calendar.HOUR_OF_DAY, 0);

cal.set(Calendar.SECOND, 0);

cal.set(Calendar.MINUTE, 0);

cal.set(Calendar.MILLISECOND, 0);

return (cal.getTimeInMillis() - System.currentTimeMillis()) / 1000;

}

拿到了时间差,剩下的基本上就没什么问题了。

Java操作Redis设置第二天凌晨过期的解决方案

附上Redis工具类:

/**
* 操作redis
* @author shy
* @date 2020/12/10 10:01
*/
@Service
public class RedisService {

@Autowired
private StringRedisTemplate stringRedisTemplate;


@Autowired
private RedisTemplate<
String, Object>
redisTemplate;


/**
* 判断String类型key是否存在
*
* @param key
* @return
* @author shy
* @date 2018年11月13日 下午1:40:37
*/
public boolean hasStringKey(String key) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
return stringRedisTemplate.opsForValue().getOperations().hasKey(key);

}

/**
* 判断String类型key是否存在
*
* @param key
* @return
* @author shy
* @date 2018年11月13日 下午1:43:51
*/
public boolean nonStringKey(String key) {
return !hasStringKey(key);

}
/**
* 设置String类型key,String类型value,过期时间timeout,TimeUnit
*
* @param key
* @param value
* @param timeout
* @param timeUnit
* @author shy
* @date 2018年12月10日13:53:38
*/
public void setStringKey(String key, String value, Long timeout, TimeUnit timeUnit) {
if (StringUtils.isBlank(key) || Objects.isNull(timeout)) {
throw new EmptyParameterException();

}
stringRedisTemplate.opsForValue().set(key, value, timeout, timeUnit);

}

public void setStringKey(String key, String value) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
stringRedisTemplate.opsForValue().set(key, value);

}
/**
* 获取String类型value
*
* @param key
* @return
* @author shy
* @date 2018年11月12日 下午7:09:31
*/
public String getStringValue(String key) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
return stringRedisTemplate.opsForValue().get(key);

}

/**
* 获取Key的过期时间
*
* @param key
* @return
* @author shy
* @date 2019年4月25日17:28:36
*/
public Long getExpire(String key) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
return stringRedisTemplate.getExpire(key);

}

/**
* 设置Key的过期时间
*
* @param key
* @return
* @author shy
* @date 2019年4月25日17:28:36
*/
public Boolean setExpire(String key,Long timeout, TimeUnit timeUnit) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
return stringRedisTemplate.expire(key, timeout, timeUnit);

}

/**
* value自增+n
* @param key
* @return
* @author shy
* @date 2019年4月8日15:54:30
*/
public Long setIncrementValue(String key) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
return stringRedisTemplate.opsForValue().increment(key, 1L);

}
/**
* 设置String类型key,Object类型value,过期时间timeout
*
* @param key
* @param value
* @param timeout
* @author shy
* @date 2018年12月10日13:54:07
*/
public void setObjectKey(String key, Object value, Long timeout,TimeUnit time) {
if (StringUtils.isBlank(key) || Objects.isNull(timeout)) {
throw new EmptyParameterException();

}
redisTemplate.opsForValue().set(key, value, timeout, time);

}

public void setObjectKey(String key, Object value) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
redisTemplate.opsForValue().set(key, value);

}

/**
* 获取Object类型value
*
* @param key
* @param clazz
* @return
* @author shy
* @date 2019年11月6日10:01:30
*/
@SuppressWarnings("
unchecked"
)
public <
T>
T getObjectValue(String key, Class<
T>
clazz) {
if (StringUtils.isBlank(key)) {
return null;

}
return (T) redisTemplate.opsForValue().get(key);

}

/**
* 移除单个String类型key
*
* @param key
* @author shy
* @date 2018年11月13日 上午10:42:01
*/
public void removeSingleStringKey(String key) {
if (StringUtils.isBlank(key)) {
throw new EmptyParameterException();

}
stringRedisTemplate.opsForValue().getOperations().delete(key);

}

/**
* 移除Collection<
String>
类型keys
*
* @param keys
* @author shy
* @date 2018年11月13日 下午3:15:16
*/
public void removeMultiStringKey(Collection<
String>
keys) {
if (CollectionUtils.isNotEmpty(keys)) {
stringRedisTemplate.opsForValue().getOperations().delete(keys);

}
}

/**
* redis key 模糊查询
* @author shy
* @date 2021年1月4日 上午11:21:45
* @param key
* @return
*/
public Set<
String>
queryStringKeys(String key) {
return redisTemplate.keys(key + "
*"
);

}
} redis过期策略功能介绍

我们在使用redis时,一般会设置一个过期时间,当然也有不设置过期时间的,也就是永久不过期。

当我们设置了过期时间,redis是如何判断是否过期,以及根据什么策略来进行删除的。

设置过期时间

我们set key的时候,可以给一个expire time,就是过期时间,指定这个key比如说只能存活一个小时,假设你设置一批key存活一小时,那么接下来一小时后,redis是如何对这批key进行删除的?

答案是:定期删除+惰性删除。

所谓定期删除是指redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。每隔100ms遍历所有设置过期时间的key会导致性能大幅下降,请注意。Redis实际上会在每100毫秒内随机选择一些key进行检查和删除。

但是问题是定期删除可能会导致很多过期key到了时间并没有被删除,所以要惰性删除,就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了就会删除。

通过上述两种手段结合起来,保证过期的key一定会被干掉。到了过期时间并不会自动删除所有过期的key,因此过期后内存占用并不会降低。

但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后没有及时去查也就没走惰性删除,就会导致大量过期key堆积在内存里耗费redis内存,这种情况如何处理?

答案是:走内存淘汰机制。

内存淘汰

如果redis的内存占用过多的时候,此时会进行一些淘汰,有如下一些策略:

  • noeviction:当内存不足以容纳新写入数据时,新写入数据会报错,这个实际场景一般不会使用。

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最少使用的key(这个是最常用的)

  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般用的比较少。

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。

  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

内存淘汰会触发淘汰条件删除某些key,这也是造成key没有设置过期时间而丢失的原因。



Redis是一种高性能的键值对数据库,被广泛应用于Web应用的缓存、消息队列、热点数据存储等场景中。在使用Redis的过程中,我们通常会用到过期时间(expire)来控制键值对的有效期,这在缓存数据和临时数据处理中尤其重要。然而,在不同的业务场景中,过期时间的设置可能会有不同的要求。本文将介绍如何使用Java操作Redis设置第二天凌晨过期的解决方案。
第一节:使用RedisTemplate设置过期时间
RedisTemplate是Spring Data Redis提供的一种最常用的操作Redis数据的方式。我们可以通过RedisTemplate的opsForValue()方法来操作Redis中的键值对,该方法支持设置过期时间。例如:
```
ValueOperations ops = redisTemplate.opsForValue();
ops.set(key, value, expireTime, timeUnit);
```
其中,key表示键,value表示值,expireTime表示过期时间,timeUnit表示时间单位。实际上,expireTime并不需要以秒为单位,我们也可以使用Calendar类来计算出第二天凌晨的时间,然后将该时间与当前时间相减得到相差的秒数,再传入ops.set()方法中即可。例如:
```
Calendar tomorrow = Calendar.getInstance();
tomorrow.add(Calendar.DAY_OF_MONTH, 1);
tomorrow.set(Calendar.HOUR_OF_DAY, 0);
tomorrow.set(Calendar.MINUTE, 0);
tomorrow.set(Calendar.SECOND, 0);
long expireSeconds = (tomorrow.getTimeInMillis() - System.currentTimeMillis()) / 1000;
ops.set(key, value, expireSeconds, TimeUnit.SECONDS);
```
这样就可以设置第二天凌晨过期的时间了。
第二节:使用注解设置过期时间
除了使用RedisTemplate设置过期时间外,我们还可以使用Spring Data Redis提供的注解@RedisHash来设置过期时间。例如:
```
@RedisHash(value = \"test\", timeToLive = 86400, setTimeToLive = true)
public class TestEntity implements Serializable {
private String id;
private String name;
// ...
}
```
其中,value表示Redis中Hash类型的名称,timeToLive表示过期时间,setTimeToLive表示是否启用过期时间。如果setTimeToLive为true,则在插入数据时会自动设置过期时间。例如:
```
TestEntity entity = new TestEntity();
entity.setId(\"1\");
entity.setName(\"test\");
testRepository.save(entity);
```
这样就可以自动设置过期时间了。
第三节:使用Lua脚本设置过期时间
最后,我们还可以使用Lua脚本来设置过期时间。由于Lua脚本在Redis中是原子性的,因此使用Lua脚本可以避免设置过期时间与插入数据之间的竞态条件。例如:
```
String luaScript = \"redis.call('set', KEYS[1], ARGV[1]); redis.call('expireat', KEYS[1], ARGV[2]); return 1;\";
DefaultRedisScript script = new DefaultRedisScript();
script.setScriptText(luaScript);
script.setResultType(Boolean.class);
List keys = new ArrayList<>();
keys.add(key);
List args = new ArrayList<>();
args.add(value);
args.add(expireTimestamp);
Boolean result = redisTemplate.execute(script, keys, args);
```
其中,expireTimestamp表示过期时间的时间戳。这里使用了Redis的expireat命令,该命令可以根据时间戳来设置过期时间。
综上所述,我们可以使用RedisTemplate、注解或Lua脚本来设置第二天凌晨过期的时间。根据实际情况选择适合的方式来进行操作即可。
返回顶部