简单限流
/**
* 判断用户行为是否达到阈值
*
* @param userId 用户id
* @param action 用户行为
* @param maxCount 次数阈值
* @param period 限制时间窗口大小
* @return
*/
private boolean is_action_allowed(String userId, String action, long maxCount, int period) {
String key = userId + ":" + action;
// nowTs ~ targetTs 规定时间窗口
long nowTs = System.currentTimeMillis();
long edgeTs = nowTs - period * 1000;
// 设置新值 zadd key value score
stringRedisTemplate.opsForZSet().add(key, String.valueOf(nowTs), nowTs);
// 去除时间窗口外的记录 zremrangebyscore(key, min, max) 删除 score 为 [min, max] 的数据
// 时间窗口就是 [beforeTs, nowTs]
stringRedisTemplate.opsForZSet().removeRangeByScore(key, 0, edgeTs);
// 获取时间窗口内的数量 zcard key
Long count = stringRedisTemplate.opsForZSet().zCard(key);
// 设置过期时间,便于后期剔除冷用户
stringRedisTemplate.expire(key, period, TimeUnit.SECONDS);
if (Objects.isNull(count)) {
return true;
} else {
// 如果时间窗口内的数量小于阈值, 说明可以操作
return count <= maxCount;
}
}
@Test
public void testLimit() {
String userId = "2020";
String action = "write";
boolean ret;
for (int i=0; i<16; i++) {
ret = is_action_allowed(userId, action, 20, 10);
if (!ret) {
System.out.println("限流! 当前次数: " + i);
}
}
}