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移动一次。滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

f58ef5b20c1be52f0fb466ab58df64c2.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();

}

}

}

复制代码

令牌桶

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

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

1fbbb6b132b265eb105435299545bd77.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限流等等。半支烟所写的这些方法只是个人在实际项目总使用过的,或者是参加阿里笔试时写过的方式。

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

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[Java实现系统限流]http://www.zyiz.net/tech/detail-118282.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java实现API调用限流可以通过以下步骤: 1. 定义限流策略 首先,需要定义限流策略。常见的限流策略包括:固定窗口计数器、滑动窗口计数器、令牌桶、漏桶等。可以根据实际需求选择合适的限流策略。例如,可以定义一个固定窗口计数器,每秒只允许调用API 100次。 2. 实现限流器 其次,需要根据限流策略实现一个限流器。可以使用Java中的并发工具包(如Semaphore、AtomicInteger等)来实现。例如,可以使用Semaphore来实现一个固定窗口计数器的限流器。 3. 在API中使用限流器 最后,在API中调用限流器来限制API的调用次数。可以在API的入口处加上限流器的逻辑。例如,可以在API的入口处调用限流器的acquire()方法,来获取限流令牌。如果获取令牌成功,则调用API;否则,返回错误码。 以下是一个使用Semaphore实现固定窗口计数器的限流器示例: ```java import java.util.concurrent.Semaphore; public class ApiRateLimiter { private final Semaphore semaphore; public ApiRateLimiter(int rateLimit) { this.semaphore = new Semaphore(rateLimit); } public void enter() throws InterruptedException { semaphore.acquire(); } public void leave() { semaphore.release(); } } ``` 在API中使用限流器的示例: ```java public class Api { private static final ApiRateLimiter rateLimiter = new ApiRateLimiter(100); public void callApi() { try { rateLimiter.enter(); // 调用API } catch (InterruptedException e) { // 处理异常 } finally { rateLimiter.leave(); } } } ``` 总之,实现Java API调用限流需要定义限流策略、实现限流器和在API中使用限流器三个步骤。这个过程需要编写大量的代码,并且需要考虑线程安全和性能等方面的问题。但是,一旦实现成功,API调用限流可以有效地控制API的调用次数,防止API被滥用和攻击,从而提高系统的安全性和可用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值