java验证码限流_Java实现系统限流

在微服务系统中,缓存、限流、熔断是保证系统高可用的三板斧,今天我们就来聊聊限流。

限流是保障系统高可用的方式之一,当然啦也是大厂高频面试题,如果阿里的面试官问一句:“如何实现每秒钟1K个请求的限流?”,你要是分分钟给他写上几种限流方案,那岂不香哉,哈哈:smirk:! 话不多说,我来列几种常用限流实现方式。

Guava RateLimiter

Guava是Java领域很优秀的开源项目,包含了日常开发常用的集合、String、缓存等, 其中RateLimiter是常用限流工具。

RateLimiter是基于令牌桶算法实现的,如果每秒10个令牌,内部实现,会每100ms生产1个令牌。

使用Guava RateLimiter

引入pom依赖:

com.google.guava

guava

23.0

复制代码

代码:

public class GuavaRateLimiterTest{

//比如每秒生产10个令牌,相当于每100ms生产1个令牌

private RateLimiter rateLimiter = RateLimiter.create(10);

/**

* 模拟执行业务方法

*/

public void exeBiz(){

if (rateLimiter.tryAcquire(1)) {

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("线程" + Thread.currentThread().getName() + ":执行业务逻辑");

} else {

System.out.println("线程" + Thread.currentThread().getName() + ":被限流");

}

}

public static void main(String[] args) throws InterruptedException{

GuavaRateLimiterTest limiterTest = new GuavaRateLimiterTest();

Thread.sleep(500);//等待500ms,让limiter生产一些令牌

//模拟瞬间生产100个线程请求

for (int i = 0; i < 100; i++) {

new Thread(limiterTest::exeBiz).start();

}

}

}

复制代码

滑窗计数

打个比方,某接口每秒允许100个请求,设置一个滑窗,窗口中有10个格子,每个格子占100ms,每100ms移动一次。滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

ebfacd2effce94872cb3bf52b0d16133.png

代码:

/**

* 滑窗计数器

*/

public class SliderWindowRateLimiter implements Runnable{

//每秒允许的最大访问数

private final long maxVisitPerSecond;

//将每秒时间划分N个块

private final int block;

//每个块存储的数量

private final AtomicLong[] countPerBlock;

//滑动窗口划到了哪个块儿,可以理解为滑动窗口的起始下标位置

private volatile int index;

//目前总的数量

private AtomicLong allCount;

/**

* 构造函数

*

* @param block,每秒钟划分N个窗口

* @param maxVisitPerSecond 每秒最大访问数量

*/

public SliderWindowRateLimiter(int block, long maxVisitPerSecond){

this.block = block;

this.maxVisitPerSecond = maxVisitPerSecond;

countPerBlock = new AtomicLong[block];

for (int i = 0; i < block; i++) {

countPerBlock[i] = new AtomicLong();

}

allCount = new AtomicLong(0);

}

/**

* 判断是否超过最大允许数量

*

* @return

*/

public boolean isOverLimit(){

return currentQPS() > maxVisitPerSecond;

}

/**

* 获取目前总的访问数

*

* @return

*/

public long currentQPS(){

return allCount.get();

}

/**

* 请求访问进来,判断是否可以执行业务逻辑

*/

public void visit(){

countPerBlock[index].incrementAndGet();

allCount.incrementAndGet();

if (isOverLimit()) {

System.out.println(Thread.currentThread().getName() + "被限流" + ",currentQPS:" + currentQPS() + ",index:" + index);

} else {

System.out.println(Thread.currentThread().getName() + "执行业务逻辑" + ",currentQPS:" + currentQPS() + ",index:" + index);

}

}

/**

* 定时执行器,

* 每N毫秒滑块移动一次,然后再设置下新滑块的初始化数字0,然后新的请求会落到新的滑块上

* 同时总数减掉新滑块上的数字,并且重置新的滑块上的数量

*/

@Override

public void run(){

index = (index + 1) % block;

long val = countPerBlock[index].getAndSet(0);

allCount.addAndGet(-val);

}

public static void main(String[] args){

SliderWindowRateLimiter sliderWindowRateLimiter = new SliderWindowRateLimiter(10, 100);

//固定的速率移动滑块

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

scheduledExecutorService.scheduleAtFixedRate(sliderWindowRateLimiter, 100, 100, TimeUnit.MILLISECONDS);

//模拟不同速度的请求

new Thread(() -> {

while (true) {

sliderWindowRateLimiter.visit();

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

//模拟不同速度的请求

new Thread(() -> {

while (true) {

sliderWindowRateLimiter.visit();

try {

Thread.sleep(50);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

}

}

复制代码

信号量

利用Semaphore,每隔固定速率,释放Semaphore的资源。线程获取到资源,则执行业务代码。

代码:

public class SemaphoreOne{

private static Semaphore semaphore = new Semaphore(10);

public static void bizMethod() throws InterruptedException{

if (!semaphore.tryAcquire()) {

System.out.println(Thread.currentThread().getName() + "被拒绝");

return;

}

System.out.println(Thread.currentThread().getName() + "执行业务逻辑");

Thread.sleep(500);//模拟处理业务逻辑需要1秒

semaphore.release();

}

public static void main(String[] args){

Timer timer = new Timer();

timer.scheduleAtFixedRate(new TimerTask() {

@Override

public void run(){

semaphore.release(10);

System.out.println("释放所有锁");

}

}, 1000, 1000);

for (int i = 0; i < 10000; i++) {

try {

Thread.sleep(10);//模拟每隔10ms就有1个请求进来

} catch (InterruptedException e) {

e.printStackTrace();

}

new Thread(() -> {

try {

SemaphoreOne.bizMethod();

} catch (InterruptedException e) {

e.printStackTrace();

}

}).start();

}

}

}

复制代码

令牌桶

令牌桶算法:一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,如有剩余容量则添加,没有则放弃。如果有请求进来,则需要先从桶里获取令牌,当桶里没有令牌可取时,则拒绝任务。

令牌桶的优点是:可以改变添加令牌的速率,一旦提高速率,则可以处理突发流量。

3743d2e4a10671c8beb2c831aee9a8b5.png

代码:

public class TokenBucket{

/**

* 定义的桶

*/

public class Bucket{

//容量

int capacity;

//速率,每秒放多少

int rateCount;

//目前token个数

AtomicInteger curCount = new AtomicInteger(0);

public Bucket(int capacity, int rateCount){

this.capacity = capacity;

this.rateCount = rateCount;

}

public void put(){

if (curCount.get() < capacity) {

System.out.println("目前数量==" + curCount.get() + ", 我还可以继续放");

curCount.addAndGet(rateCount);

}

}

public boolean get(){

if (curCount.get() >= 1) {

curCount.decrementAndGet();

return true;

}

return false;

}

}

@Test

public void testTokenBucket() throws InterruptedException{

Bucket bucket = new Bucket(5, 2);

//固定线程,固定的速率往桶里放数据,比如每秒N个

ScheduledThreadPoolExecutor scheduledCheck = new ScheduledThreadPoolExecutor(1);

scheduledCheck.scheduleAtFixedRate(() -> {

bucket.put();

}, 0, 1, TimeUnit.SECONDS);

//先等待一会儿,让桶里放点token

Thread.sleep(6000);

//模拟瞬间10个线程进来拿token

for (int i = 0; i < 10; i++) {

new Thread(() -> {

if (bucket.get()) {

System.out.println(Thread.currentThread() + "获取到了资源");

} else {

System.out.println(Thread.currentThread() + "被拒绝");

}

}).start();

}

//等待,往桶里放token

Thread.sleep(3000);

//继续瞬间10个线程进来拿token

for (int i = 0; i < 10; i++) {

new Thread(() -> {

if (bucket.get()) {

System.out.println(Thread.currentThread() + "获取到了资源");

} else {

System.out.println(Thread.currentThread() + "被拒绝");

}

}).start();

}

}

}

复制代码

总结

本文主要介绍几种限流方法:Guava RateLimiter、简单计数、滑窗计数、信号量、令牌桶,当然啦,限流算法还有漏桶算法、nginx限流等等。半支烟所写的这些方法只是个人在实际项目总使用过的,或者是参加阿里笔试时写过的方式。

如果你更好的想法,也欢迎跟我一起交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(转)三层相关案例(及常见的错误)

原文地址&#xff1a;http://www.cnblogs.com/zysbk/archive/2012/10/16/2726895.html三层相关案例&#xff08;及常见的错误&#xff09; 今天我们再次带领大家学习三层的知识,由于三层这块的内容在工作中用的比较的多.所以,我再把登录验证密码的题目做一遍.题目中没有了验证用…

数据库的几个概念:主键,外键,索引,唯一索引

主键&#xff1a;主键是数据表的唯一索引&#xff0c;比如学生表里有学号和姓名&#xff0c;姓名可能有重名的&#xff0c;但学号确是唯一的&#xff0c;你要从学生表中搜索一条纪录如查找一个人&#xff0c;就只能根据学号去查找&#xff0c;这才能找出唯一的一个&#xff0c;…

java ee实验新闻_JAVAEE第四次作业-JSP显示新闻

JAVAEE第四次作业-JSP显示新闻一、任务使用mysql创建新闻表用户登录时&#xff0c;用servlet获取用户名密码&#xff0c;查询数据库是否存在如果是正确的用户名密码&#xff0c;查询新闻表&#xff0c;将新闻数据传给JSP首页JSP首页用EL表达式显示新闻标题列表二、任务1实验过程…

sql数据库连接不上本地数据库的解决办法

如果sql数据库连接本地的sql数据库连接不上即用.连接不上了&#xff0c;错误内容为&#xff1a; 可以执行一下以下操作&#xff1a;开始-sql—配置工具—sql server 外围应用配置器—服务和连接的外围应用配置器—如果服务状态为&#xff1a;已停止&#xff0c;则启动一下即可。…

python2 unicode编码_如何优雅解决python2.x的unicode编码优雅输出?

python2.x字符编码有一个这样的问题&#xff0c;类似下面这样&#xff1a;>>> d {usubType: u\u5f55\u97f3\u5ba4\u7248,uname: u\u5468\u6770\u4f26\u7684\u5e8a\u8fb9\u6545\u4e8b}>>> print d{usubType: u\u5f55\u97f3\u5ba4\u7248,uname: u\u5468\u6770…

在load事件中关闭窗体

protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); if (isFormClose) { this.Close(); } } 转载于:https://www.cnblogs.com/zhuzhuxia/archive/2012/10/25/2739063.html

java js中 function函数报错_浅析JS中对函数function的理解(基础篇)

正文&#xff1a;我们知道&#xff0c;在js中&#xff0c;函数实际上是一个对象&#xff0c;每个函数都是Function类型的实例&#xff0c;并且都与其他引用类型一样具有属性和方法。因此&#xff0c;函数名实际上是指向函数对象的指针&#xff0c;不与某个函数绑定。在常见的两…

前端学习(576):margin无效情形之内联特性导致无效

当负值到达一定情况开始失效 内联的特性受到限制

jstl tag

<% taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions %> ${fu:length(xxxList)}转载于:https://www.cnblogs.com/adolfmc/archive/2012/10/26/2740675.html

java windows so文件_windows下编译使用NDK,调用SO文件 | 学步园

下载后把压缩包解压出来&#xff0c;例如&#xff1a;D:\ndk&#xff0c;目录下的ndk-build.cmd就是用来编译的批处理命令。这里以D:\ndk\samples\hello-jni为例&#xff0c;打开D:\ndk\samples\hello-jni\jni\hello-jni.c查看代码&#xff1a;/** Copyright (C) 2009 The Andr…

Discuz! 防御CC攻击的设置办法

防御 CC 攻击 Discuz!5.5 在以往抗 CC 的基础上又加了两种方法&#xff0c;可以根据实际遭受攻击的情况&#xff0c;通过配置组合出适合的抵抗方法。限于篇幅&#xff0c;不详细阐述对抗原理&#xff0c;现将配置方法做下简要说明。配置文件 config.inc.php $attackevasive 0…

php 注销session_php浏览器关闭页面怎么注销session

php浏览器关闭session的方法&#xff1a;首先使用“session_start();”开启session&#xff1b;然后清空session信息&#xff1b;接着清除客户端sessionid&#xff1b;最后通过“session_destroy();”彻底销毁session即可。1、每个页面都必须开启session_start()后才能在每个页…

static的作用

static的作用 在C语言中&#xff0c;static的字面意思很容易把我们导入歧途&#xff0c;其实它的作用有三条。 &#xff08;1&#xff09;先来介绍它的第一条也是最重要的一条&#xff1a;隐藏。 当我们同时编译多个文件时&#xff0c;所有未加static前缀的全局变量和函数都具有…