为什么Redis密钥没有过期? [英] Why Redis keys are not expiring?

查看:102
本文介绍了为什么Redis密钥没有过期?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经检查了以下问题,但它们并没有帮助我解决问题.我正在使用spring-data-redis库在我的Spring REST应用程序中将Redis用作速率限制的键值存储.我进行了巨大的测试.在此,我使用以下代码存储密钥,并设置过期时间.大多数情况下,密钥会按预期过期.但是有些时候密钥还没有过期!

I have checked these questions but they did not help me to fix my issue. I am using Redis as a key value store for Rate Limiting in my Spring REST application using spring-data-redis library. I test with huge load. In that I use the following code to store a key and I am setting the expire time as well. Most of the time the key expires as expected. But some times the key is not expiring!

代码段

RedisAtomicInteger counter = counter = new RedisAtomicInteger("mykey");
counter.expire(1, TimeUnit.MINUTES);

我使用redis-cli工具检查了密钥的可用性

I checked the availability of the keys using redis-cli tool

键*

ttl键名

ttl keyname

redis.conf具有默认值.

redis.conf having default values.

有什么建议吗?

完整代码:

该功能在一个方面

public synchronized Object checkLimit(ProceedingJoinPoint joinPoint) throws Exception, Throwable {

        boolean isKeyAvailable = false;
        List<String> keysList = new ArrayList<>();

        Object[] obj = joinPoint.getArgs();
        String randomKey = (String) obj[1];
        int randomLimit = (Integer) obj[2];

        // for RedisTemplate it is already loaded as 

        // @Autowired
        // private RedisTemplate template; 

        // in this class
        Set<String> redisKeys = template.keys(randomKey+"_"randomLimit+"*");
        Iterator<String> it = redisKeys.iterator();
        while (it.hasNext()) {
               String data = it.next();
               keysList.add(data);
        }

        if (keysList.size() > 0) {
            isKeyAvailable = keysList.get(0).contains(randomKey + "_" + randomLimit);
        }

        RedisAtomicInteger counter = null;
        // if the key is not there
        if (!isKeyAvailable) {
              long expiryTimeStamp = 0;
              int timePeriodInMintes = 1;
              expiryTimeStamp = new Date(System.currentTimeMillis() + timePeriodInMintes * 60 * 1000).getTime();
              counter = new RedisAtomicInteger(randomKey+ "_"+ randomLimit + "_" + expiryTimeStamp,template.getConnectionFactory());
              counter.incrementAndGet();
              counter.expire(timePeriodInMintes, TimeUnit.MINUTES);
              break;

        } else {

              String[] keys = keysList.get(0).split("_");
              String rLimit = keys[1];

              counter = new RedisAtomicInteger(keysList.get(0), template.getConnectionFactory());
              int count = counter.get();
              // If count exceeds throw error
              if (count != 0 && count >= Integer.parseInt(rLimit)) {
                    throw new Exception("Error");
               }  
               else {
                    counter.incrementAndGet();
              }
      }
        return joinPoint.proceed();
    }

这些行运行时

RedisAtomicInteger计数器=计数器=新的RedisAtomicInteger("mykey"); counter.expire(1,TimeUnit.MINUTES);

RedisAtomicInteger counter = counter = new RedisAtomicInteger("mykey"); counter.expire(1, TimeUnit.MINUTES);

我可以看到

75672562.380127 [0 10.0.3.133:65462] "KEYS" "mykey_1000*"
75672562.384267 [0 10.0.3.133:65462] "GET" "mykey_1000_1475672621787"
75672562.388856 [0 10.0.3.133:65462] "SET" "mykey_1000_1475672621787" "0"
75672562.391867 [0 10.0.3.133:65462] "INCRBY" "mykey_1000_1475672621787" "1"
75672562.395922 [0 10.0.3.133:65462] "PEXPIRE" "mykey_1000_1475672621787" "60000"
...
75672562.691723 [0 10.0.3.133:65462] "KEYS" "mykey_1000*"
75672562.695562 [0 10.0.3.133:65462] "GET" "mykey_1000_1475672621787"
75672562.695855 [0 10.0.3.133:65462] "GET" "mykey_1000_1475672621787"
75672562.696139 [0 10.0.3.133:65462] "INCRBY" "mykey_1000_1475672621787" "1" 

在Redis日志中,当我在

in Redis log, when I "MONITOR" it in

推荐答案

现在,有了更新的代码,我相信除了您要报告的内容之外,您的方法从根本上还是有缺陷的.

Now with the updated code I believe your methodology is fundamentally flawed aside from what you're reporting.

实现它的方式需要在生产环境中运行KEYS-这很糟糕.向外扩展时,将导致服务器上不断增加的不必要的系统阻塞负载.就像文档中的每句话都说的那样,在生产环境中不要使用keys.请注意,在密钥名称中编码到期时间不会给您带来任何好处.如果您将键名的那一部分设置为创建时间戳记,甚至是随机数,都不会改变.确实,如果您删除了那一点,什么都不会改变.

The way you've implemented it you require running KEYS in production - this is bad. As you scale out you will be causing a growing, and unnecessary, system blocking load on the server. As every bit of documentation on it says, do not use keys in production. Note that encoding the expiration time in the key name gives you no benefit. If you made that part of the key name a the creation timestamp, or even a random number nothing would change. Indeed, if you removed that bit, nothing would change.

更合理的方法是使用与时间无关的键名.使用到期可以为您处理该功能.让我们将您的限速商品称为会话".在没有时间戳记的情况下,您的键名是会话ID".通过在其上设置60s的到期时间,它将不再在61s标记处可用.因此,您可以安全地递增结果并将其与您的限制进行比较,而无需知道当前时间或到期时间.您只需要一个静态键名并在其上设置适当的到期时间即可.

A more sane route would instead be to use a keyname which is not time-dependent. The use of expiration handles that function for you. Let us call your rate-limited thing a "session". Your key name sans the timestamp is the "session ID". By setting an expiration of 60s on it, it will no longer be available at the 61s mark. So you can safely increment and compare the result to your limit without needing to know the current time or expiry time. All you need is a static key name and an appropriate expiration set on it.

如果您INCR一个不存在的密钥,则Redis将返回"1",表示它已创建密钥并以单步/调用的方式递增.所以基本上逻辑是这样的:

If you INCR a non-existing key, Redis will return "1" meaning it created the key and incremented it in a single step/call. so basically the logic goes like this:

  1. 创建会话" ID
  2. 使用ID的递增计数器
  3. 比较结果以限制
  1. create "session" ID
  2. increment counter using ID
  3. compare result to limit
  1. 如果count == 1,则将到期时间设置为60s
  2. id count>限制,拒绝

步骤3.1很重要.计数为1表示这是Redis中的新密钥,并且您要在其上设置到期时间.其他任何内容都意味着应该已经设置了到期时间.如果在3.2中进行设置,则会中断该过程,因为它将使计数器保留60秒钟以上.

Step 3.1 is important. A count of 1 means this is a new key in Redis, and you want to set your expiration on it. Anything else means the expiration should already have been set. If you set it in 3.2 you will break the process because it will preserve the counter for more than 60s.

使用此功能,您不需要基于到期时间的动态键名,因此不需要使用keys来确定速率限制对象是否存在现有的会话".这也使您的代码更加简单和可预测,并减少了与Redis的往返行程-这意味着它将降低Redis的负载并提高性能.至于使用您正在使用的客户端库的方式,我不能说,因为我不太熟悉它.但是基本序列应该是可以翻译的,因为它相当基本和简单.

With this you don't need to have dynamic key names based on expiration time, and thus don't need to use keys to find out if there is an existing "session" for the rate-limited object. It also makes your code much simpler and predictable, as well as reduce round trips to Redis - meaning it will be lower load on Redis and perform better. As to how to do that w/the client library you're using I can't say because I'm not that familiar with it. But the basic sequence should be translatable to it as it is fairly basic and simple.

但是,您尚未显示的任何东西都可以证明未发生过期.您所要做的只是表明Redis确实已被告知并设置了到期时间.为了支持您的主张,您需要证明密钥没有过期.这意味着您需要在到期时间后显示对密钥的检索,并且该计数器没有在到期后通过重新创建来重置".您可以看到到期日的一种方法是使用密钥空间通知.这样,您将能够看到Redis说密钥已过期.

What you haven't shown, however, is anything to support the assertion that the expiration isn't happening. All you've done is show that Redis is indeed being told to and setting an expiration. In order to support your claim you need to show that the key does not expire. Which means you need to show retrieval of the key after the expiration time, and that the counter was not "reset" by being recreated after the expiration. One way you can see the expiration is happening is to use keyspace notifications. With that you will be able to see Redis saying a key was expired.

如果您使用多个窗口进行速率限制,或者您有一个更大的窗口(例如10分钟),则此过程可能会失败,在这种情况下,排序集可能是更明智的选择,以防止出现前端加载请求-如果需要的话.但是,正如您编写的示例一样,上面的方法也可以正常工作.

Where this process will fail a bit is if you do multiple windows for rate-limiting, or if you have a much larger window (ie. 10 minutes) in which case sorted sets might be a more sane option to prevent front-loading of requests - if desired. But as your example is written, the above will work just fine.

这篇关于为什么Redis密钥没有过期?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆