2

これらの質問を確認ここに画像の説明を入力しましたが、問題を解決するのに役立ちませんでした。spring-data-redis ライブラリを使用して、Spring REST アプリケーションでレート制限のキー値ストアとして Redis を使用しています。大きな負荷でテストします。その中で、次のコードを使用してキーを保存し、有効期限も設定しています。ほとんどの場合、鍵は期待どおりに期限切れになります。ただし、キーの有効期限が切れていない場合もあります。

コードスニペット

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

redis-cli ツールを使用してキーの可用性を確認しました

キー *

ttl キーネーム

デフォルト値を持つ redis.conf。

助言がありますか ?

編集1:

完全なコード:

機能はアスペクトにあります

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);

私は見えます

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ログで、「監視」すると

4

1 に答える 1

2

編集:更新されたコードでは、あなたが報告していることは別として、あなたの方法論には根本的な欠陥があると思います。

実装した方法では、本番環境で実行する必要がありますKEYS-これは悪いことです。スケールアウトすると、サーバー上で不要なシステム ブロッキング負荷が増大します。それに関するドキュメントのすべてのビットが言うように、本番環境では使用しないでください。keysキー名に有効期限をエンコードしても、何のメリットもないことに注意してください。キー名のその部分を作成タイムスタンプにしたり、乱数にしたりしても、何も変わりません。確かに、そのビットを削除しても、何も変わりません。

代わりに、時間に依存しないキー名を使用するのが、より健全なルートです。有効期限ハンドルを使用すると、その機能が自動的に実行されます。レート制限されたものを「セッション」と呼びましょう。タイムスタンプのないキー名は「セッションID」です。60 秒の有効期限を設定することで、61 秒の時点で使用できなくなります。そのため、現在の時刻や有効期限を知る必要なく、結果を安全にインクリメントして制限と比較できます。必要なのは、静的キー名とそれに設定された適切な有効期限だけです。

存在しないキーの場合INCR、Redis は「1」を返します。これは、キーを作成し、単一のステップ/呼び出しでインクリメントしたことを意味します。したがって、基本的にロジックは次のようになります。

  1. 「セッション」ID を作成する
  2. ID を使用してカウンターをインクリメントする
  3. 結果を制限と比較する
    1. カウント == 1 の場合、有効期限を 60 秒に設定します
    2. ID カウント > 制限、拒否

ステップ 3.1 が重要です。カウント 1 は、これが Redis の新しいキーであり、有効期限を設定したいことを意味します。それ以外の場合は、有効期限が既に設定されている必要があります。3.2 で設定すると、カウンターが 60 秒以上保持されるため、プロセスが中断されます。

これにより、有効期限に基づく動的なキー名を持つ必要がないkeysため、レート制限されたオブジェクトの既存の「セッション」があるかどうかを調べるために使用する必要がなくなります。また、コードをよりシンプルで予測可能にし、Redis へのラウンド トリップを減らします。つまり、Redis の負荷が低くなり、パフォーマンスが向上します。あなたが使用しているクライアントライブラリでそれを行う方法については、私はそれに慣れていないので言えません. しかし、基本的なシーケンスはかなり基本的で単純なので、それに変換できるはずです。

ただし、表示されていないのは、有効期限が切れていないという主張を裏付けるものです。あなたが行ったことは、Redis が実際に有効期限を設定するように指示されていることを示すことだけです。あなたの主張を裏付けるには、キーの有効期限が切れていないことを示す必要があります。つまり、有効期限後にキーを取得したこと、および有効期限後に再作成されてカウンターが「リセット」されなかったことを示す必要があります。有効期限が切れていることを確認する方法の 1 つは、 keyspace notificationsを使用することです。これにより、Redis がキーの有効期限が切れていることを確認できます。

このプロセスが少し失敗するのは、レート制限のために複数のウィンドウを実行する場合、またはウィンドウがはるかに大きい場合 (つまり、10 分) の場合です。この場合、並べ替えられたセットは、リクエストのフロントローディングを防ぐためのより適切なオプションである可能性があります。 - 必要に応じて。しかし、あなたの例が書かれているように、上記はうまく機能します。

于 2016-10-05T13:09:04.443 に答える