多维度访问次数限制策略

类型维度
梯度维度
时间段维度

配置文件:

[
    {
        "seconds": 10,
        "times": 5,
        "type": "user_method",
        "errMsg": ""
    },
    {
        "seconds": 30,
        "times": 15,
        "type": "user_method",
        "errMsg": ""
    },
    {
        "times": 100,
        "type": "user",
        "errMsg": ""
    },
    {
        "times": 200,
        "type": "method",
        "errMsg": ""
    },
    {
        "times": 200,
        "type": "range_time",
        "range_time": "18:00-19:00",
        "errMsg": ""
    },
    {
        "times": 200,
        "type": "range_time",
        "range_time": "19:00-20:00",
        "errMsg": ""
    }
]

实现代码:

public RequestLimit tryRequest(final List<RequestLimit> requestLimitList, final String methodName, final Integer userId) {
    Assert.isTrue(StringUtil.isNotBlank(methodName), "tryRequest param methodName is empty!");
    Assert.isTrue(userId != null && userId > 0, "tryRequest param userId is empty!");
    Object ret = runTask(new Callback() {
        @Override
        public RequestLimit onTask(Jedis jedis) {
            boolean flag = true;
            log.info(String.format("[reqLimit_request] \t methodName, userId\t : %s,%s", methodName, userId));
            for (RequestLimit limit : requestLimitList) {

                Integer seconds = limit.getSeconds();
                Integer times = limit.getTimes();
                String type = limit.getType() ;
                if (flag && "user_method".equals(type)) {
                    flag = limitUserMethod(jedis, seconds, times, methodName, userId);
                } else if (flag && "user".equals(type)) {
                    flag = limitUser(jedis, times, userId);
                } else if (flag && "method".equals(type)) {
                    flag = limitMethod(jedis, times, methodName);
                } else if (flag && "range".equals(type)) {
                    flag = limitRangeTime(jedis, limit, userId);
                }
                if (!flag) {
                    return limit;
                }
            }
            log.info(String.format("[reqLimit_pass] \t seconds, methodName, userId :\t %s,%s", methodName, userId));
            return null;
        }
    });

    return ret == null ? null : (RequestLimit) ret;
}

private boolean limitRangeTime(Jedis jedis, RequestLimit limit, Integer userId) {
    long nowTime = System.currentTimeMillis();
    long rangeStartTime = limit.getRangeStartTime();
    long rangeEndTime = limit.getRangeEndTime();
    Integer times = limit.getTimes();
    PreconditionsUtil.checkArguments(times != null, "400", "RequestLimit times is null");

    if (rangeStartTime <= nowTime && nowTime < rangeEndTime) {
        long seconds = rangeEndTime - nowTime;
        log.info(String.format("[reqLimit_error:USER_RANGE_TIME] \t range_time, times, seconds, userId :\t %s,%s,%s,%s",
                limit.getRangeTime(), times, seconds, userId));
        final String key = String.format(USER_RANGE_TIME_REQUEST_LIMIT, rangeEndTime, userId);
        String mistr = jedis.get(key);
        if (mistr == null || Integer.parseInt(mistr) == 0) {
            jedis.incr(key);
            jedis.expire(key, (int) seconds);
        } else if (jedis.incr(key) >= times) {
            log.error(String.format("[reqLimit_error:USER_RANGE_TIME] \t times, seconds, userId :\t %s,%s,%s",
                    jedis.get(key), seconds, userId));
            return false;
        }
    }
    return true;
}

private boolean limitUser(Jedis jedis, Integer times, Integer userId) {
    long dayEndTime = DateUtil.getDayEndTime();
    final String key = String.format(USER_REQUEST_LIMIT, dayEndTime, userId);

    long seconds = dayEndTime - System.currentTimeMillis();
    String mistr = jedis.get(key);
    log.info(String.format("[reqLimit_error:USER] \t times, seconds, userId :\t %s,%s,%s",
             times, seconds, userId));
    if (mistr == null || Integer.parseInt(mistr) == 0) {
        jedis.incr(key);
        jedis.expire(key, (int) seconds);
    } else if (jedis.incr(key) >= times) {
        log.error(String.format("[reqLimit_error:USER] \t times, seconds, userId :\t %s,%s,%s",
                jedis.get(key), seconds, userId));
        return false;
    }
    return true;
}

private boolean limitMethod(Jedis jedis, Integer times, String methodName) {
    long dayEndTime = DateUtil.getDayEndTime();
    final String key = String.format(METHOD_REQUEST_LIMIT, dayEndTime, methodName);

    long seconds = dayEndTime - System.currentTimeMillis();

    log.info(String.format("[reqLimit_error:METHOD] \t times, seconds, methodName :\t %s,%s,%s",
            times, seconds, methodName));
    String mistr = jedis.get(key);
    if (mistr == null || Integer.parseInt(mistr) == 0) {
        jedis.incr(key);
        jedis.expire(key, (int) seconds);
    } else if (jedis.incr(key) >= times) {
        log.error(String.format("[RequireRequestLimit_over:METHOD] \t times, seconds, methodName :\t",
                jedis.get(key), seconds, methodName));
        return false;
    }
    return true;
}

private boolean limitUserMethod(Jedis jedis, Integer seconds, Integer times, String methodName, Integer userId) {
    final String key = String.format(METHOD_USER_REQUEST_LIMIT, seconds, methodName, userId);

    String mistr = jedis.get(key);

    log.info(String.format("[reqLimit_error:METHOD] \t times, seconds, methodName, userId :\t %s,%s,%s,%s",
            times, seconds, methodName, userId));
    if (mistr == null || Integer.parseInt(mistr) == 0) {
        jedis.incr(key);
        jedis.expire(key, seconds);
    } else if (jedis.incr(key) >= times) {
        log.error(String.format("[RequireRequestLimit_over:USER_METHOD] \t times, seconds, methodName, userId :\t",
                jedis.get(key), seconds, methodName, userId));
        return false;
    }
    return true;
}
<think>好的,用户想在Redis中实现每日访问次数限制。我需要结合之前的引用内容来给出解决方案。首先,我得回忆一下引用的资料,比如引用3提到每天凌晨清除点击次数,引用4是关于时间窗口内的限制,而引用1和2是关于登录和接口调用的限制。 用户的需求是限制每天的访问次数,所以应该使用按天计数的策略。Redis的过期时间或者定时任务清除数据是关键。引用3中使用的是定时任务每天凌晨清除Hash中的数据,这可能是一个方法。另外,可以使用INCR命令来递增计数器,并设置过期时间为当天剩余时间,但这样每个用户可能需要单独设置过期时间,可能比较复杂。 另一个方法是使用Hash结构,每个用户对应一个字段,每天递增该字段的值。然后配合定时任务在每天零点清除整个Hash,如引用3中的做法。这样可以统一管理,避免每个键单独设置过期时间的问题。 还需要考虑并发情况下的原子操作,比如使用INCR和EXPIRE的组合,或者使用Lua脚本来保证原子性。同时,引用4中的例子是固定时间窗口(10分钟),而这里需要的是自然日的窗口,所以设置过期时间到当天结束可能更合适,但需要计算剩余秒数。 可能还需要考虑分布式环境下的一致性,Redis本身是单线程的,所以命令执行是原子性的,可以放心使用。此外,性能方面,使用Hash结构或者单独的键存储计数器都是可行的,但Hash结构可能更节省内存,尤其是用户量大的时候。 总结步骤应该是:用户每次访问时,用Redis的INCR命令增加计数,如果超过限制则拒绝。同时,设置过期时间或者定时任务来重置计数器。具体实现可能涉及键的设计,比如按用户ID和日期组合成键,或者使用Hash结构按日期存储所有用户的计数。定时任务清除当天的数据,如引用3中的做法,可以确保每天从零开始。 另外,需要处理跨天的边界情况,比如用户在23:59访问,下一个访问在00:01,这时候计数器应该分别属于两天。因此,键或Hash字段的设计必须包含日期信息,或者使用自然日切换的定时任务来清除数据。 最后,代码示例部分,可能需要展示如何使用RedisTemplate在Java中实现,比如使用opsForHash().increment()方法,并设置定时任务。同时,要处理首次访问时的初始化,以及判断是否超过限制。</think>### Redis实现每日访问次数限制的步骤 #### 1. 数据结构选择 使用Redis的**Hash结构**存储每日访问次数: - **Key**: `daily_access:日期`(例如:`daily_access:20230805`) - **Field**: 用户唯一标识(如用户ID) - **Value**: 当日累计访问次数 #### 2. 核心操作 ```python # 用户每次访问时执行以下操作 user_id = "user123" today = datetime.now().strftime("%Y%m%d") key = f"daily_access:{today}" # 原子性递增并返回当前值 current_count = redis.hincrby(key, user_id, 1) # 如果是首次访问,设置Key过期时间(第二天0点自动过期) if current_count == 1: expire_time = 86400 - (time.time() % 86400) redis.expire(key, int(expire_time)) ``` #### 3. 访问控制逻辑 ```python MAX_DAILY_ACCESS = 100 # 每日最大访问次数 if current_count > MAX_DAILY_ACCESS: return "超出当日访问限制" else: # 允许访问 ``` #### 4. 定时清除机制(可选) 通过定时任务每天凌晨清除数据,如引用[^3]中所示: ```java @Scheduled(cron = "0 0 0 * * *") public void clearDailyAccess() { // 删除所有日期相关的Key(按业务需求调整) redis.delete("daily_access:*"); } ``` ### 方案优势 1. **原子操作**:`hincrby`和`expire`命令保证操作的原子性[^2] 2. **自动过期**:通过计算当日剩余秒数设置精确的Key过期时间 3. **高效存储**:Hash结构节省内存空间(相比为每个用户单独设置Key) 4. **扩展性**:可轻松扩展为多维度限制(如接口级、功能级) ### 注意事项 - 分布式系统需统一服务器时区 - 高并发场景建议结合Lua脚本保证操作原子性 - 历史数据可通过持久化Hash结构实现审计功能
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值