Java 对IP请求进行限流.

高并发系统下, 有三把利器 缓存 降级 限流.

  • 缓存: 将常用数据缓存起来, 减少数据库或者磁盘IO
  • 降级: 保护核心系统, 降低非核心业务请求响应
  • 限流: 在某一个时间窗口内对请求进行限速, 保护系统

 本文主要介绍限流, 常见限流算法中又分为计数器算法, 漏桶算法, 令牌桶算法.

计数器算法

比较简单, 直接用一个map + counter即可实现. 请求来了, 以IP为key,

查询下之前响应次数, 如果调用次数超出MAX_COUT, 返回失败, 属于简单粗暴型选手.

漏桶算法

请求全部进入漏桶, 漏桶恒定速率输出反馈. 这样可以保证数据传输平滑,

但是无法预防突发大量请求, 一秒来了100个请求, 都要阻塞排队, 从小水管输出数据.

令牌桶算法

令牌桶是以固定速度往桶里存令牌, 例如一秒存1000个令牌, 业务请求来了, 直接从桶里获取令牌响应输出.

跟漏桶的差异在于, 他可以预存令牌, 如果一秒钟来了100个请求, 桶里有100个令牌,

那么可以立刻响应给客户端, 而不是排队输出.

令牌桶的实现

guava中提供了令牌桶的一个封装实现RateLimiter, 可以直接调用, 省的我们自己包装ConcurrentHashMap + Timer.

我们预设的场景是服务器端提供一个API供不同客户端查询, 要限流每个IP每秒只能调用两次该API.

首先要定义一个服务器端的缓存, 定期清理即可, 缓存 IP : 令牌桶

 1     // 根据IP分不同的令牌桶, 每天自动清理缓存
 2     private static LoadingCache<String, RateLimiter> caches = CacheBuilder.newBuilder()
 3             .maximumSize(1000)
 4             .expireAfterWrite(1, TimeUnit.DAYS)
 5             .build(new CacheLoader<String, RateLimiter>() {
 6                 @Override
 7                 public RateLimiter load(String key) throws Exception {
 8                     // 新的IP初始化 (限流每秒两个令牌响应)
 9                     return RateLimiter.create(2);
10                 }
11             });

然后在业务代码中进行限流调用

 1     private static void login(int i) throws ExecutionException {
 2         // 模拟IP的key
 3         String ip = String.valueOf(i).charAt(0) + "";
 4         RateLimiter limiter = caches.get(ip);
 5 
 6         if (limiter.tryAcquire()) {
 7             System.out.println(i + " success " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
 8         } else {
 9             System.out.println(i + " failed " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
10         }
11     }

模拟客户端调用

1         for (int i = 0; i < 1000; i++) {
2             // 模拟实际业务请求
3             Thread.sleep(100);
4             login(i);
5         }

完整代码

 1 public class doLimit {
 2 
 3     // 根据IP分不同的令牌桶, 每天自动清理缓存
 4     private static LoadingCache<String, RateLimiter> caches = CacheBuilder.newBuilder()
 5             .maximumSize(1000)
 6             .expireAfterWrite(1, TimeUnit.DAYS)
 7             .build(new CacheLoader<String, RateLimiter>() {
 8                 @Override
 9                 public RateLimiter load(String key) throws Exception {
10                     // 新的IP初始化 (限流每秒两个令牌响应)
11                     return RateLimiter.create(2);
12                 }
13             });
14 
15     public static void main(String[] args) throws InterruptedException, ExecutionException {
16         for (int i = 0; i < 1000; i++) {
17             // 模拟实际业务请求
18             Thread.sleep(100);
19             login(i);
20         }
21     }
22 
23     private static void login(int i) throws ExecutionException {
24         // 模拟IP的key
25         String ip = String.valueOf(i).charAt(0) + "";
26         RateLimiter limiter = caches.get(ip);
27 
28         if (limiter.tryAcquire()) {
29             System.out.println(i + " success " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
30         } else {
31             System.out.println(i + " failed " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
32         }
33     }
34 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java 源码分析

Exectors框架 源码分析

Exectors框架 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,...

2847
来自专栏Java学习之路

从源码看JDK提供的线程池(ThreadPoolExecutor) 一丶什么是线程池二丶ThreadPoolExecutor的使用三丶从源码来看ThreadPoolExecutor

一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关...

47110
来自专栏Java面试通关手册

Java多线程学习(八)线程池与Executor 框架

Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_G...

1974
来自专栏FreeBuf

浅析Windows下堆的结构

简介 Windows下的堆主要有两种,进程的默认堆和自己创建的私有堆。在程序启动时,系统在刚刚创建的进程虚拟地址空间中创建一个进程的默认堆,而且程序也可以通过 ...

2328
来自专栏lzj_learn_note

ThreadPoolExecutor学习笔记

Java有两个线程池类:ThreadPoolExecutor和ScheduledThreadPoolExecutor,且均继承于ExecutorService。...

1.3K6
来自专栏编程

浅析Windows下堆的结构

*本文原创作者:hellowuzekai,本文属FreeBuf原创奖励计划,未经许可禁止转载 简介 Windows下的堆主要有两种,进程的默认堆和自己创建的私有...

27710
来自专栏Java3y

线程池你真不来了解一下吗?

2466
来自专栏Java编程技术

线程池使用FutureTask时候需要注意的一点事

线程池使用FutureTask的时候如果拒绝策略设置为了 DiscardPolicy和 DiscardOldestPolicy并且在被拒绝的任务的F...

1141
来自专栏JMCui

多线程编程学习五(线程池的创建)

一、概述 New Thread的弊端如下:        a、每次New Thread新建对象性能差。        b、线程缺乏统一的管理,可能无限制的新建...

38911
来自专栏java 成神之路

ExecutorCompletionService 源码分析

2708

扫码关注云+社区

领取腾讯云代金券