001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.components.limiter.redis;
017
018import io.jboot.exception.JbootIllegalConfigException;
019import io.jboot.support.redis.JbootRedis;
020import io.jboot.support.redis.JbootRedisManager;
021
022/**
023 * 通过lua脚本来进行限次
024 */
025public class RedisRateLimitUtil {
026
027    private static final String RATE_LIMIT_SCRIPT = "local c" +
028            "\nc = redis.call('get',KEYS[1])" +
029            // 调用量已经超过最大值,直接返回
030            "\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
031            "\nreturn tonumber(c);" +
032            "\nend" +
033            // 自增
034            "\nc = redis.call('incr',KEYS[1])" +
035            "\nif tonumber(c) == 1 then" +
036            // 从第一次调用开始限流,设置对应键值的过期
037            "\nredis.call('expire',KEYS[1],ARGV[2])" +
038            "\nend" +
039            "\nreturn c;";
040
041    private static JbootRedis redis;
042
043    /**
044     * 限制时长默认为1秒
045     */
046    public static boolean tryAcquire(String resource, int rate) {
047        return tryAcquire(resource, rate, 1);
048    }
049
050    /**
051     * 尝试是否能正常执行
052     *
053     * @param resource      资源名
054     * @param rate          限制次数
055     * @param periodSeconds 限制时长,单位为秒
056     * @return true 可以执行
057     * false 限次,禁止
058     */
059    public static boolean tryAcquire(String resource, int rate, int periodSeconds) {
060        if (redis == null) {
061            redis = JbootRedisManager.me().getRedis();
062            if (redis == null) {
063                throw new JbootIllegalConfigException("Redis config not well, can not use LimitScope.CLUSTER in @EnableLimit() ");
064            }
065        }
066        Long count = (Long) redis.eval(RATE_LIMIT_SCRIPT, 1, resource, String.valueOf(rate), String.valueOf(periodSeconds));
067        return count <= rate;
068    }
069}