限流常见方案,值得一看
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
- 一、限流思路 -
1、熔断
2、服务降级
相同点: 目标一致 都是从可用性和可靠性出发,为了防止系统崩溃; 用户体验类似,最终都让用户体验到的是某些功能暂时不可用。 不同点: 触发原因不同,服务熔断一般是某个服务(下游服务,即被调用的服务)故障引起; 而服务降级一般是从整体负荷考虑。
- 二、限流算法 -
1、计数器算法
package com.todaytalents.rcn.parser.util;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 计数器实现限流:
* 每分钟只允许100个请求,第一个请求进去的时间为startTime,在startTime + 60s内只允许100个请求
* 60s内超过100个请求后,则拒绝请求,
* 不超过,允许请求,到第60s 重新设置时间。
*
* @author: Arafat
* @date: 2021/12/29
* @company: 澳B99999
**/
public class CalculatorCurrentLimiting {
/**
* 限流个数
*/
private int maxCount = 100;
/**
* 指定的时间内:秒
*/
private long specifiedTime = 60;
/**
* 原子类计数器
*/
private AtomicInteger atomicInteger = new AtomicInteger(0);
/**
* 起始时间
*/
private long startTime = System.currentTimeMillis();
/**
* @param maxCount 限流个数
* @param specifiedTime 指定的时间内
* @return 返回true 不限流,返回false 则限流
*/
public boolean limit(int maxCount, int specifiedTime) {
atomicInteger.addAndGet(1);
if (1 == atomicInteger.get()) {
startTime = System.currentTimeMillis();
atomicInteger.addAndGet(1);
return true;
}
// 超过时间间隔,重新开始计数
if (System.currentTimeMillis() - startTime > specifiedTime * 1000) {
startTime = System.currentTimeMillis();
atomicInteger.set(1);
return true;
}
// 还在时间间隔内,检查是否超过限流数量
if (maxCount < atomicInteger.get()) {
return false;
}
return true;
}
}
2、滑动窗口
3、漏桶算法
import java.util.concurrent.atomic.AtomicInteger;
/**
* 漏桶算法:把水滴看成请求
*
* @author: Arafat
* @date: 2021/12/29
**/
public class LeakyBucket {
/**
* 桶的容量
*/
private int capacity = 100;
/**
* 桶剩余的水滴的量(初始化的时候桶为空)
*/
private AtomicInteger water = new AtomicInteger(0);
/**
* 水滴的流出的速率 每1000毫秒流出1滴
*/
private int leakRate;
/**
* 第一次请求之后,木桶在这个时间点开始漏水
*/
private long leakTimeStamp;
public LeakyBucket(int leakRate) {
this.leakRate = leakRate;
}
public boolean acquire() {
// 如果是空桶,就用当前时间作为桶开始漏出的时间
if (water.get() == 0) {
leakTimeStamp = System.currentTimeMillis();
water.addAndGet(1);
return capacity == 0 ? false : true;
}
// 先执行漏水,计算剩余水量
int waterLeft = water.get() - ((int) ((System.currentTimeMillis() - leakTimeStamp) / 1000)) * leakRate;
water.set(Math.max(0, waterLeft));
// 重新更新leakTimeStamp
leakTimeStamp = System.currentTimeMillis();
// 尝试加水,并且水还未满
if ((water.get()) < capacity) {
water.addAndGet(1);
return true;
} else {
// 水满,拒绝加水,直接溢出
return false;
}
}
}
/**
* @author Arafat
*/
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/test")
public class TestController {
/**
* 漏桶:水滴的漏出速率是每秒 1 滴
*/
private LeakyBucket leakyBucket = new LeakyBucket(1);
private UserService userService;
/**
* 漏桶限流
*
* @return
*/
@RequestMapping("/searchUserInfoByLeakyBucket")
public Object searchUserInfoByLeakyBucket() {
// 限流判断
boolean acquire = leakyBucket.acquire();
if (!acquire) {
log.info("请您稍后再试!");
return Reply.success("请您稍后再试!");
}
// 若没有达到限流的要求,直接调用接口查询
return Reply.success(userService.search());
}
}
削峰:有大量流量进入时,会发生溢出,从而限流保护服务可用。 缓冲:不至于直接请求到服务器,缓冲压力,消费速度固定,因为计算性能固定。
4、令牌桶算法
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 令牌桶算法限流
*
* @author: Arafat
* @date: 2021/12/30
**/
public class TokensLimiter {
/**
* 最后一次令牌发放时间
*/
public long timeStamp = System.currentTimeMillis();
/**
* 桶的容量
*/
public int capacity = 10;
/**
* 令牌生成速度10/s
*/
public int rate = 10;
/**
* 当前令牌数量
*/
public int tokens ;
/**
* 周期性线程池
*/
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
/**
* 线程池每0.5s发送随机数量的请求,
* 每次请求计算当前的令牌数量,
* 请求令牌数量超出当前令牌数量,则限流。
*/
public void acquire() {
scheduledExecutorService.scheduleWithFixedDelay(() -> {
long now = System.currentTimeMillis();
// 当前令牌数
tokens = Math.min(capacity, (int) (tokens + (now - timeStamp) * rate) / 1000);
//每隔0.5秒发送随机数量的请求
int permits = (int) (Math.random() * 9) + 1;
System.out.println("请求令牌数:" + permits + ",当前令牌数:" + tokens);
timeStamp = now;
if (tokens < permits) {
// 若不到令牌,则拒绝
System.out.println("限流了");
} else {
// 还有令牌,领取令牌
tokens -= permits;
System.out.println("剩余令牌=" + tokens);;
}
}, 1000, 500, TimeUnit.MILLISECONDS);
}
public static void main(String[] args) {
TokensLimiter tokensLimiter = new TokensLimiter();
tokensLimiter.acquire();
}
}
漏桶与令牌桶算法的区别
主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。 在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。 令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。 具体情况具体分析,只有最合适的算法,没有最优的算法。
import com.google.common.util.concurrent.RateLimiter;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @author Arafat
*/
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/test")
public class TestController {
/**
* 每秒钟放入n个令牌,相当于每秒只允许执行n个请求
* n = 1
* n == 5
*/
//private static final RateLimiter RATE_LIMITER = RateLimiter.create(1);
private static final RateLimiter RATE_LIMITER = RateLimiter.create(5);
public static void main(String[] args) {
// 每秒中限制1个请求 0:表示等待超时时间,设置0表示不等待,直接拒绝请求
boolean tryAcquire = RATE_LIMITER.tryAcquire(0, TimeUnit.SECONDS);
// false表示没有获取到token
if (!tryAcquire) {
System.out.println("现在抢购的人数过多,请稍等一下下哦!");
}
// tryAcquire 模拟有20个请求
for (int i = 0; i < 20; i++) {
/**
* 尝试从令牌桶中获取令牌,
* 若获取不到则等待300毫秒看能不能获取到
*/
boolean request = RATE_LIMITER.tryAcquire(300, TimeUnit.MILLISECONDS);
if (request) {
// 获取成功,执行相应逻辑
handle(i);
}
}
// acquire 模拟有20个请求
for (int i = 0; i < 20; i++) {
// 从令牌桶中获取一个令牌,若没有获取到会阻塞直到获取到为止,所以所有的请求都会被执行
RATE_LIMITER.acquire();
// 获取成功,执行相应逻辑
handle(i);
}
}
private static void handle(int i) {
System.out.println("第 " + i + " 次请求OK~~~");
}
}
- 三、集群限流 -
redisOperations.opsForValue().increment()
作者 | 涛姐涛哥
来源 | cnblogs.com/taojietaoge/p/15744243.html
加锋哥微信: java1239 围观锋哥朋友圈,每天推送Java干货!
评论