Redis更新数据的时候如何不重置过期时间

最近在研究使用 自定义注解 + 拦截器 + Redis 实现限流 的功能时,需要用Redis记录一段时间内某个接口被请求的次数。发现一个问题:我使用的是RedisTemplate,当我在redis中插入一个myKey值,并且设置了对应过期时间. 当过期时间还没到的时候重新 更新 myKey值会导致 过期时间被刷新,不信邪的我直接在Redis-cli 中又试了一下,发现过期时间依然被刷新了。

下面是我的测试代码:

@Test
    public void redisTest04() throws InterruptedException {
        stringIntegerRedisTemplate.opsForValue().set("testData", 12, 20, TimeUnit.SECONDS);
        log.info("testData value : [ {} ] 过期时间 --001 [ {} ]", stringIntegerRedisTemplate.opsForValue().get("testData"),
                stringIntegerRedisTemplate.getExpire("testData"));

        Thread.sleep(5000);
        stringIntegerRedisTemplate.opsForValue().set("testData", 13);
        log.info("testData value : [ {} ] 过期时间 --002 [ {} ]", stringIntegerRedisTemplate.opsForValue().get("testData"),
                stringIntegerRedisTemplate.getExpire("testData"));
    }

测试结果如下:

 结果,在set的时候,这条数据变成了永久有效了,因为 redisTemplate.opsForValue().set()这个方法,如果不传过期时间的话,它就默认是永不过期的。

针对这个问题: 我查看了下redis的官方文档, 他们是这么解释的:

The timeout will only be cleared by commands that delete or overwrite the contents of the key, including DEL, SET, GETSET and all the *STORE commands. This means that all the operations that conceptually alter the value stored at the key without replacing it with a new one will leave the timeout untouched. For instance, incrementing the value of a key with INCR, pushing a new value into a list with LPUSH, or altering the field value of a hash with HSET are all operations that will leave the timeout untouched.

解释:

如果用DEL, SET, GETSET会将key对应存储的值替换成新的,命令也会清除掉超时时间;如果list结构中添加一个数据或者改变hset数据的一个字段是不会清除超时时间的;如果想要通过set去覆盖值那就必须重新设置expire。

这么看来,过期时间被刷新是无法避免的了,那么我的功能怎么实现呢?只能想办法了呀。

方案一

  • 既然每次重新set的时候,就算我不设置过期时间它也会自动刷新,那么如果我在set之前拿到它的过期时间,在set新值的同时将获取到的过期时间也set进去,这样虽然有些误差,但是如果不是对时间要求特别严格,也是一种可取的办法。看我写的一个test:
    @Resource
        private RedisTemplate<String, Integer> stringIntegerRedisTemplate;
    
        @Test
        public void redisTest01() throws InterruptedException {
            stringIntegerRedisTemplate.opsForValue().set("testData", 12, 20, TimeUnit.SECONDS);
            log.info("testData value : [ {} ] 过期时间 --001 [ {} ]", stringIntegerRedisTemplate.opsForValue().get("testData"),
                    stringIntegerRedisTemplate.getExpire("testData"));
    
            Thread.sleep(5000);
            stringIntegerRedisTemplate.opsForValue()
                    .set("testData", 13, stringIntegerRedisTemplate.getExpire("testData"), TimeUnit.SECONDS);
            log.info("testData value : [ {} ] 过期时间 --002 [ {} ]", stringIntegerRedisTemplate.opsForValue().get("testData"),
                    stringIntegerRedisTemplate.getExpire("testData"));
        }

    输出结果:

可见这种做法是行的通的。

但是,这其中肯定是存在误差的,如果时间要求严格的话,这样是不行的。那么我们就得另想办法了。

方案二

  • 好在Redis存在一些方法解决我们的问题。我们使用的RedisTemplate有个 expireAt(K key, final Date date) 的方法,可以设置键到那个时间过期。那么只要我们将键的过期时间取出来,加上此时的时间,就能得到它会在哪个时间过期,在reset值后,用expireAt方法设置过期时间就OK了,看下面的demo:
@Test
    public void test004() throws InterruptedException {
        redisTemplate.opsForValue().set("time", 12, 30, TimeUnit.SECONDS);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtil.DATE_FORMAT_SECOND);


        Long time = redisTemplate.getExpire("time");
        System.out.println(simpleDateFormat.format(new Date(Instant.now().toEpochMilli())) + ",过期时间001: " + time);

        Date date = new Date(Instant.now().toEpochMilli() + time * 1000);
        System.out.println("到 " + simpleDateFormat.format(date) + "过期");
        Thread.sleep(5000);

        redisTemplate.expireAt("time", date);
        System.out.println(simpleDateFormat.format(new Date(Instant.now().toEpochMilli())) + ",过期时间002: " + redisTemplate.getExpire("time"));
    }
输出结果如下:
2020-01-09 17:09:22,过期时间001: 30
到 2020-01-09 17:09:52过期
2020-01-09 17:09:27,过期时间002: 25

这样就没有误差了!

 

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值