基于redis实现的四种常见的限流策略 (2)

滑动时间窗口是将时间更加细化,上面我们是通过redis#setnx实现的。这里我们就无法通过他统一记录了。我们应该加上更小的时间单元存储到一个集合汇总。然后根据集合的总量计算限流。redis的zsett数据结构就和符合我们的需求。

为什么选择zset呢,因为redis的zset中除了值以外还有一个权重。会根据这个权重进行排序。如果我们将我们的时间单元及时间戳作为我们的权重,那么我们获取统计的时候只需要按照一个时间戳范围就可以了。

因为zset内元素是唯一的,所以我们的值采用uuid或者雪花算法一类的id生成器

controller @RequestMapping(value = "/startList",method = RequestMethod.GET) public Map<string,object> startList(@RequestParam Map<string, object=""> paramMap) { return testService.startList(paramMap); } service String redisKey = "qpsZset"; Integer times = 100; if (paramMap.containsKey("times")) { times = Integer.valueOf(paramMap.get("times").toString()); } long currentTimeMillis = System.currentTimeMillis(); long interMills = inter * 1000L; Long count = redisTemplate.opsForZSet().count(redisKey, currentTimeMillis - interMills, currentTimeMillis); 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; 结果测试

image-20210508150557980

和固定时间窗口采用相同的并发。为什么上面也会出现临界状况呢。因为在代码里时间单元间隔比固定时间间隔采用还要大 。 上面演示固定时间窗口时间单元是1S出现了最坏情况。而滑动时间窗口设计上就应该间隔更短。而我设置成10S 也没有出现坏的情况

这里就说明滑动比固定的优处了。如果我们调更小应该更加不会出现临界问题,不过说到底他还是避免不了临界出现的问题

漏桶算法

滑动时间窗口虽然可以极大程度的规避临界值问题,但是始终还是避免不了

另外时间算法还有个致命的问题,他无法面对突如其来的大量流量,因为他在达到限流后直接就拒绝了其他额外流量

针对这个问题我们继续优化我们的限流算法。 漏桶算法应运而生

image-20210508154607271

优点

面对限流更加的柔性,不在粗暴的拒绝。

增加了接口的接收性

保证下流服务接收的稳定性。均匀下发

缺点

我觉得没有缺点。非要鸡蛋里挑骨头那我只能说漏桶容量是个短板

实现 controller @RequestMapping(value = "/startLoutong",method = RequestMethod.GET) public Map<string,object> startLoutong(@RequestParam Map<string, object=""> paramMap) { return testService.startLoutong(paramMap); } service

在service中我们通过redis的list的功能模拟出桶的效果。这里代码是实验室性质的。在真实使用中我们还需要考虑并发的问题

@Override public Map<string, object=""> startLoutong(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"); } Long aLong = redisTemplate.opsForList().rightPush(redisKey, paramMap); if (aLong > 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 public class SchedulerTask { @Autowired RedisTemplate redisTemplate; private String redisKey="qpsList"; @Scheduled(cron="*/1 * * * * ?") private void process(){ //一次性消费两个 System.out.println("正在消费。。。。。。"); redisTemplate.opsForList().trim(redisKey, 2, -1); } } 测试

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwpzdp.html