@Autowired
private StringRedisTemplate redisTemplate;
// 固定时间窗口算法
@GetMapping("/start")
public Map<String, Object> start(@RequestParam Map<String, Object> paramMap) {
//根据前端传递的qps上线
int times = 100;
if (paramMap.containsKey("times")) {
times = Integer.parseInt(paramMap.get("times").toString());
}
String redisKey = "redisQps";
RedisAtomicInteger redisAtomicInteger = new RedisAtomicInteger(redisKey, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
int no = redisAtomicInteger.getAndIncrement();
//设置时间固定时间窗口长度 1S
if (no == 0) {
redisAtomicInteger.expire(1, TimeUnit.SECONDS);
}
//判断是否超限 time=2 表示qps=3
log.info("no值->{}",no);
if (no > times) {
throw new RuntimeException("qps refuse request");
}
log.info("times值->{}",times);
//返回成功告知
Map<String, Object> map = new HashMap<>();
map.put("success", "success");
return map;
}
滑动时间窗口算法是一种限流算法,其原理是记录一段时间内的请求数量,并根据时间窗口的滑动来判断是否接受新的请求。该算法通过维护一个滑动窗口来记录时间窗口内的请求数量,并根据窗口的大小和限流阈值来决定是否接受新的请求。
滑动时间窗口算法的实现方式相对复杂一些,但相对于固定时间窗口算法更加灵活和精确。在滑动时间窗口算法中,时间窗口是不断滑动的,每个窗口的大小和起点可以根据实际需求进行配置。同时,该算法也支持动态调整限流阈值和流量控制粒度,能够更好地应对流量波动和突发请求的情况。
滑动时间窗口算法的核心思想是通过维护一个时间窗口内的请求计数器,并根据时间窗口的滑动来判断是否接受新的请求。具体来说,当一个新的请求到达时,算法会根据当前时间点判断该请求属于哪个时间窗口,并更新对应窗口的计数器。如果计数器已经达到了限流阈值,则拒绝该请求;否则,接受该请求。随着时间的推移,时间窗口会不断滑动,并更新计数器的值。
滑动时间窗口算法相对于固定时间窗口算法更加灵活和精确,能够更好地应对流量波动和突发请求的情况。同时,该算法也支持动态调整限流阈值和流量控制粒度,能够更好地满足实际需求。在实际应用中,需要根据具体情况选择适合的限流算法来控制流量。
滑动时间窗口算法的优点主要包括以下几点:
然而,滑动时间窗口算法也存在一些缺点:
// 滑动时间窗口算法
@GetMapping("/startList")
public Map<String, Object> startList(@RequestParam Map<String, Object> paramMap) {
String redisKey = "qpsZset";
Integer times = 100;
if (paramMap.containsKey("times")) {
times = Integer.valueOf(paramMap.get("times").toString());
}
long currentTimeMillis = System.currentTimeMillis();
long interMills = 10000L;
Long count = redisTemplate.opsForZSet().count(redisKey, currentTimeMillis - interMills, currentTimeMillis);
// 检查QPS(Queries Per Second)是否超过限制
/**
* 使用System.currentTimeMillis()获取当前时间戳。
* 设置一个时间间隔interMills为1000毫秒(即1秒)。
* 使用redisTemplate.opsForZSet().count(redisKey, currentTimeMillis - interMills, currentTimeMillis)查询过去1秒内Redis ZSet中指定键(redisKey)的数量。这个数量会被赋值给count变量。
* 如果count的值大于times,则抛出一个运行时异常,表示QPS超过限制
*/
if (count > times) {
throw new RuntimeException("qps refuse request");
}
redisTemplate.opsForZSet().add(redisKey, UUID.randomUUID().toString(), currentTimeMillis);
Map<String, Object> map = new HashMap<>();
map.put("success", "success");
return map;
}
漏桶算法是一种常用于流量控制和限流的算法,其核心思想是将突发流量整形以便为网络提供一个稳定的流量。
漏桶算法可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。
漏桶算法的实现相对简单,不需要复杂的数据结构或计算,易于理解和部署。其优点包括平滑处理流入系统的请求,以恒定的速率将请求处理释放,从而避免了突发请求对系统的冲击;控制精度高,可以通过调整漏桶的出水速率来精确地控制请求的处理速度;适用于需要平滑处理流量和避免突发请求的场景,例如网络流量控制、防止DDOS攻击等。
然而,漏桶算法也存在一些缺点,例如请求延迟,可能导致某些请求的响应时间变长;不适用于实时性要求高的场景,因为请求需要按照恒定速率进行处理,无法满足即时性要求;无法应对突发流量,因为漏桶的出水速率是固定的,无法根据流量的变化进行动态调整。
// 漏桶算法
@GetMapping("/startLoutong")
public Map<String, Object> startLoutong(@RequestParam Map<String, Object> paramMap) {
String redisKey = "qpsList";
Integer times = 100;
if (paramMap.containsKey("times")) {
times = Integer.valueOf(paramMap.get("times").toString());
}
Long size = redisTemplate.opsForList().size(redisKey);
// 检查队列长度是否超过限制
if (size >= times) {
throw new RuntimeException("qps refuse request");
}
// 添加请求到队列Redis列表的右侧。
Long aLong = redisTemplate.opsForList().rightPush(redisKey, String.valueOf(paramMap));
if (aLong > times) {
//为了防止并发场景。这里添加完成之后也要验证。 即使这样本段代码在高并发也有问题。此处演示作用
// 修剪Redis列表,使其长度为times
redisTemplate.opsForList().trim(redisKey, 0, times - 1);
throw new RuntimeException("qps refuse request");
}
Map<String, Object> map = new HashMap<>();
map.put("success", "success");
return map;
}
模拟消费
@Component
@Slf4j
public class SchedulerTask {
@Autowired
private StringRedisTemplate redisTemplate;
@Scheduled(cron = "*/5 * * * * ?")
private void process() {
//一次性消费两个
log.info("正在消费。。。。。。");
String redisKey = "qpsList";
redisTemplate.opsForList().trim(redisKey, 2, -1);
}
}
// 令牌桶算法
@GetMapping("/startLingpaitong")
public Map<String, Object> startLingpaitong(Map<String, Object> paramMap) {
String redisKey = "lingpaitong";
String token = redisTemplate.opsForList().leftPop(redisKey);
//正常情况需要验证是否合法,防止篡改
if (StringUtils.isEmpty(token)) {
throw new RuntimeException("令牌桶拒绝");
}
Map<String, Object> map = new HashMap<>();
map.put("success", "success");
return map;
}