-
2021-04-16 01:52:04
通常在高并发和大流量的情况下,一般限流是必须的。为了保证服务器正常的压力。那我们就聊一下几种限流的算法。
计数器
计数器是一种最常用的一种方法,在一段时间间隔内,处理请求的数量固定的,超的就不做处理。
demo
public function SpeedCounter()
{
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
// 最大请求数量
$maxCount = 100;
//每分钟内,一个用户只能访问10次
$interval =60;
//请求总数量
$zcount = $redis->incr('zcont');
//判断是否超过最大值
if ($zcount<=$maxCount) {
//业务处理
$user = [
11,21,31,41,51,61
];
foreach ($user as $val) {
$key = $val;
$check = $redis->exists($key);
if ($check) {
$sum = $redis->incr($key);
if ($sum<=5){
//业务处理
echo "每个用户在规定的时间内只能访问5次 $sum";
} else {
echo "你已经购买过 $sum";
}
} else {
//print_r($redis->get($key)) ;
///请购买
echo "请购买";
$sum = $redis->incr($key);
$redis->Expire($key,$interval);
}
}
} else {
//超过请求数量
$redis->Expire('zcont',$interval);
echo '超出请求'.$zcount;
}
漏桶算法
漏桶的大小是固定的,处理速度也是固定的,但是请求的速率的不固定的。在突发的情况下,会丢弃很多请求。
/**
* **漏桶的大小是固定的,处理速度也是固定的,但是请求的速率的不固定的。在突发的情况下,会丢弃很多请求。**
*/
function LeackBucket() {
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
//桶的容量
$maxCount = 1000;
//时间
$interval = 10;
//每分钟流出的数量
$speed = 20;
//用户
$time = $redis->time();
$key = $time[0].$time[1];
//时间判断
//$redis->del('outCount');
$check = $redis->exists('outCount');
// echo $check;
if ($check){
//出桶的速率的请求数量
$outCount = $redis->incr('outCount');
if ($outCount<=$speed){
//业务处理
echo "规定的时间内只能访问20次 $outCount";
} else {
echo "你已经超过每分钟的访问 $outCount";
}
} else {
$outCount = $redis->incr('outCount');
// echo $outCount;
$redis->Expire('outCount',$interval);
echo "时间过了";exit;
}
}
令牌桶
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.
令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.
/**
* 令牌
*/
function TrafficShaper(){
$redis = new \Redis();
$redis->pconnect('127.0.0.1', 6379);
//桶的容量
$maxCount = 10;
//当前容量
$curnum = $maxCount-$redis->get('token')-1;
echo $curnum;
if ($curnum>0){
//业务逻辑
//成功后
$token = $redis->incr('token');
echo "===$token";
} else {
echo "没有令牌了";
$redis->set('token',0);
}
}
更多相关内容 -
使用池化技术、令牌桶算法和漏桶算法实现限流的原理
2020-09-11 19:14:45漏桶算法4. 计数器算法 1. 池化技术 池化资源技术的限流其实就是通过计算器算法来控制全局的总并发数,例如常用的线程池中核心线程数和最大线程数的设置、数据库连接池中对于最大连接数的限制等等。就数据库连接池...
1. 池化技术
池化资源技术的限流其实就是通过计算器算法来控制全局的总并发数,例如常用的线程池中核心线程数和最大线程数的设置、数据库连接池中对于最大连接数的限制等等。就数据库连接池技术而言,为了避免并发场景下连接数超过数据库所能承载的最大上限,合理的运用连接池技术,可以有效的限制单个进程内能够申请到的最大连接数,确保在并发环境下连接数不会超过资源阈值。
2. 令牌桶算法
令牌桶算法主要用于限制流量的平均流入速率,并允许出现一定程度上的突发流量,假设令牌桶的容量为 n n n,
那么算法执行的流程为:
- 每秒向令牌桶中放入 n n n个令牌,即每 1 n \frac{1}{n} n1秒放入一个令牌的平均速率来提供可以使用的令牌
- 令牌桶的容量自始至终都是固定的,最多只能放入 n n n个令牌,桶满则溢出
- 当一个 n n n个字节的请求包到达时,将消耗 n n n个令牌,然后再发送该数据包
- 如果桶中的令牌数小于 n n n,则该数据包将被执行限流处理
Guava Cache中的RateLimiter抽象类能够以一种简便的方式实现流量的平均流入速率限流,起到类似令牌桶算法的效果,
为了使用Guava Cache,首先需要创建工程,导入依赖
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.0.1-jre</version> </dependency>
如果想要使用RateLimiter,可以调用它的
create()
来指定令牌桶的容量。当需要令牌时,请求需要调用acquire()
方法。如下所示,此时令牌桶容量为5,假设请求数也是5,那么5个请求都可以近乎同时获取令牌,并不会触发限流。@Test void testLimit(){ RateLimiter limiter = RateLimiter.create(5); for (int i = 0; i < 5; i++) { double waitTime = limiter.acquire(); System.out.println(waitTime); } }
0.0 0.198812 0.199176 0.199727 0.19905
如果此时有了突发流量,某个请求需要一次申请5个令牌,那么后续的请求就会被限流。大约等待1秒后,后续请求才可以继续从桶中拿令牌。
@Test void testLimitMore() { RateLimiter limiter = RateLimiter.create(5); for (int i = 0; i < 5; i++) { System.out.println(limiter.acquire(5)); System.out.println(limiter.acquire()); System.out.println("--------"); } }
0.0 0.999008 -------- 0.198522 0.998839 -------- 0.199423 0.999741 -------- 0.196243 0.999406 -------- 0.199218 0.99949 --------
调用
acquire()
获取令牌不到,请求将会一直等待。如果需要请求在获取不到令牌后,直接丢弃或是短暂等待,可以调用tryAcquire()
的无参和带参形式。那么RateLimiter底层是如何实现流量的平均流入速率限流的效果的呢?下面我们通过源码看一下它的create()
、acquire()
和tryacquire()
的实现。RateLimiter的
create()
最基本的实现如下:public static RateLimiter create(double permitsPerSecond) { return create(permitsPerSecond, RateLimiter.SleepingStopwatch.createFromSystemTimer()); }
其中的
permitsPerSecond
可用于设置限流速率从慢速过渡到平均速率的缓存时间。当前,RateLimiter中还提供了其他重载的形式,用于创建不同需求的limiter。acquire()
的实现如下:@CanIgnoreReturnValue public double acquire() { return acquire(1); } @CanIgnoreReturnValue public double acquire(int permits) { // 计算获取令牌所需等待的时间 long microsToWait = reserve(permits); // 进行线程sleep stopwatch.sleepMicrosUninterruptibly(microsToWait); return 1.0 * microsToWait / SECONDS.toMicros(1L); }
它会根据设置的桶容量来计算获取一个令牌所需要等待的时间。
tryacquire()
的实现如下:public boolean tryAcquire(int permits) { return tryAcquire(permits, 0, MICROSECONDS); } public boolean tryAcquire(int permits, long timeout, TimeUnit unit) { long timeoutMicros = max(unit.toMicros(timeout), 0); checkPermits(permits); long microsToWait; synchronized (mutex()) { long nowMicros = stopwatch.readMicros(); if (!canAcquire(nowMicros, timeoutMicros)) { return false; } else { microsToWait = reserveAndGetWaitLength(permits, nowMicros); } } stopwatch.sleepMicrosUninterruptibly(microsToWait); return true; }
RateLimiter具体的源码解析阅读:
3. 漏桶算法
漏桶算法允许以任意的速率向桶中流入水滴,但由于桶的容量是固定的,如果桶已满,那么水滴将溢出被丢弃。而且桶流出水滴的速率是固定的,起到限制数据的传输速率。
上述两种算法虽然都可以在高并发场景下起到限流的效果,但是两者的限流方向是相反的。令牌桶算法限制的是向桶中放入令牌的数量,允许一定程度上的突发流量。当用户请求所需的令牌数多于桶中剩下的令牌数,就会被执行限流操作。
而漏桶算法控制的是令牌流出的速率,并且流出的速率还是固定的,主要用于平滑网络流量。例如,假设桶只能接受大小为10的数据包,那么当数据包大于10时,请求将被执行限流处理,由于流出速率固定,那么每秒处理的数据包大小将固定,从而起到平滑流量的作用。
4. 计数器算法
单位时间内会有一个计数器负责计数,不停的和阈值进行比较,当等于阈值时触发限流逻辑。它主要用于限制单位时间内的总并发数,假设规定每秒可以处理的请求数为100,那么一秒内请求一次,计数器值加1。如果一秒内计数器值到达阈值,那么后续的请求就被执行限流处理。并且只有达到临界值时,计数器才会被重置。
当等于阈值时触发限流逻辑。它主要用于限制单位时间内的总并发数,假设规定每秒可以处理的请求数为100,那么一秒内请求一次,计数器值加1。如果一秒内计数器值到达阈值,那么后续的请求就被执行限流处理。并且只有达到临界值时,计数器才会被重置。
-
令牌漏桶算法实现限流
2020-04-17 16:19:33公司项目漏桶算法限流1.思维流程图:
2.代码如下
<?php class RateLimit { private $minNum = 100; //单个用户每分访问数 //主函数 public function handle($uid) { $minNumKey = $uid . '_minNum'; $dayNumKey = $uid . '_dayNum'; $resMin = $this->getRedis($minNumKey, $this->minNum, 60); if (!$resMin['status']) { return ['status'=>0,'info'=>'人数较多,请稍候']; }else{ return ['status'=>1,'info'=>'正常']; } } protected function getRedis($key, $initNum, $expire) { $nowtime = time(); $result = ['status' => true, 'msg' => '']; $ip = "127.0.0.1"; $port = 6379; $redis = new \Redis(); $redis->connect($ip, $port); $redis->watch($key); $limitVal = $redis->get($key); if ($limitVal) { $limitVal = json_decode($limitVal, true); $newNum = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($nowtime - $limitVal['time']))); if ($newNum > 0) { $redisVal = json_encode(['num' => $newNum, 'time' => time()]); } else { return ['status' => false, 'msg' => '当前时刻令牌消耗完!']; } } else { $redisVal = json_encode(['num' => $initNum, 'time' => time()]); } $redis->multi(); $redis->set($key, $redisVal); $rob_result = $redis->exec(); if (!$rob_result) { $result = ['status' => false, 'msg' => '访问频次过多!']; } return $result; } }
-
限流算法-令牌桶、漏桶算法之java实现
2022-02-28 17:02:46限流算法-令牌桶、漏桶算法之java实现介绍
令牌桶算法:
一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌;如下:
a.假设是2r/s,则每500毫秒添加n个令牌;
b.桶中最多存放x个令牌,桶满时,再添加会拒绝;
c.请求去获取令牌,如果令牌足够,允许通过;如果令牌不足,拒绝请求或放入缓冲区等待;漏桶算法:
漏桶容量固定,按照固定速率流出水滴;如果桶是空的,不用流,如果桶满了,再流入水滴,则拒绝服务。
区别
令牌桶控制的是平均流入速率,速率可高可低,允许有突发请求;漏桶控制的是恒定的流出速率,从而平滑流入速率。
java实现
令牌桶算法:
package com.cvnavi.oa.report; import org.apache.kafka.common.protocol.types.Field; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class TokenLimiter { private ArrayBlockingQueue<String> blockingQueue; //容量大小 private int limit; //令牌的产生间隔 private int period; //令牌每次产生的个数 private int amount; public TokenLimiter(int limit, int period, int amount) { this.limit = limit; this.period = period; this.amount = amount; blockingQueue = new ArrayBlockingQueue<>(limit); } private void init() { for(int i = 0; i < limit; i++) { blockingQueue.add("lp"); } } private void addToken(int amount) { for(int i = 0; i < amount; i++) { //溢出返回false blockingQueue.offer("lp"); } } /** * 获取令牌 * @return */ public boolean tryAcquire() { //队首元素出队 return blockingQueue.poll() != null ? true : false; } /** * 生产令牌 */ private void start(Object lock) { Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> { synchronized (lock) { addToken(this.amount); lock.notify(); } }, 500, this.period, TimeUnit.MILLISECONDS); } /** * 先生产2个令牌,减少4个令牌;再每500ms生产2个令牌,减少4个令牌 */ public static void main(String[] args) throws InterruptedException { int period = 500; TokenLimiter limiter = new TokenLimiter(8, period, 2); limiter.init(); Object lock = new Object(); limiter.start(lock); //让线程先产生2个令牌(溢出) synchronized (lock) { lock.wait(); } for(int i = 0; i < 8; i++) { for(int j = 0; j < 4; j++) { String s = i + "," + j + ":"; if(limiter.tryAcquire()) { System.out.println(s + "拿到令牌"); } else{ System.out.println(s + "拒绝"); } } Thread.sleep(period); } } }
漏桶算法:package com.cvnavi.report; public class Funnel { //漏斗容量 int capacity; //漏水速度 double leakingRate; //漏斗剩余空间(不是水的大小) int leftQuota; //上次漏斗漏水的时间 long leakingTs; public Funnel(int capacity, double leakingRate, int leftQuota, long leakingTs) { this.capacity = capacity; this.leakingRate = leakingRate; this.leftQuota = leftQuota; this.leakingTs = leakingTs; } private void makeSpace() { long nowTs = System.currentTimeMillis(); //这个是间隔的时间 long deltaTs = nowTs - this.leakingTs; //漏掉的水 int deltaQuota = (int)(deltaTs * leakingRate); //间隔时间太长,溢出 if (deltaQuota < 0){ this.leftQuota = capacity; this.leakingTs = nowTs; return; } //说明漏的时间不够 if (deltaQuota < 1) { return; } this.leakingTs = nowTs; this.leftQuota = this.leftQuota + deltaQuota; if (this.leftQuota > this.capacity) { this.leftQuota = this.capacity; } } /** * 流入 * @param quota * @return */ public Boolean water(int quota) { makeSpace(); //表示量充足 if (leftQuota >= quota){ leftQuota = leftQuota - quota; return true; } //剩余量不够 return false; } } package com.cvnavi.report; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; public class FunnelLimitRate { private static Map<String,Funnel> funnels = new HashMap<>(); public static void main(String[] args) throws InterruptedException { String userId= "2019"; String actionKey = "rgister"; int capacity = 8; double leakingRate = 0.001; int leftQuota = 5; Funnel funnel = funnels.get(userId); if (funnel == null) { funnel = new Funnel(capacity, leakingRate, leftQuota, System.currentTimeMillis()); } //每1000ms(1s),流入1的水 //每1000ms,流出1000*leakingRate(而且要>1)的水 for (int i = 0; i < 8; i++) { Boolean isBoolean = funnel.water(1); TimeUnit.MILLISECONDS.sleep(1000); System.out.println(isBoolean + " " + funnel.leftQuota); } } }
-
基于令牌桶算法实现的SpringBoot无锁限流插件
2019-08-07 18:39:19基于令牌桶算法实现的SpringBoot无锁限流插件,支持方法级别、系统级别的限流,提供快速失败与CAS阻塞两种方案,开箱即用! -
redis 漏桶算法—Lua
2021-04-22 20:06:22漏桶算法 import java.io.IOException;import java.nio.charset.Charset;import org.springframework.core.io.ClassPathResource;import com.google.common.io.Files;import redis.clients.jedis.Jedis;/****@author... -
高并发解决方案限流技术-----漏桶算法限流
2021-04-15 19:18:101,漏桶算法漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:一个固定容量的漏桶,按照常量固定速率流出水滴;... -
漏桶算法代码简单实现
2022-03-18 09:19:55漏桶算法的意义在于能够平滑请求,不给下游服务造成过大压力,特别适用于突发流量或者定时任务拉取大量数据时,需要处理大量数据或者请求的场景。 使用单线程的for循环太慢,使用线程池仍无法避免一瞬间会发起很多... -
【限流算法】java实现漏桶算法
2019-11-30 20:02:06本文实现了一种基本的漏桶算法 漏桶算法思想:以固定速率消费请求,漏桶容量固定,每次用户请求都得放入桶中,桶满则拒绝请求或等待。达到平滑网络请求的效果。 代码逻辑:线程池每0.5s发送随机数量的请求,每次... -
如何实现漏桶算法与令牌桶算法
2020-12-29 00:34:50/*** ${DESCRIPTION} * *@authormengxp *@version1.0 * @create 2018-01-20 23:11 * 漏桶算法测试 * 实现漏桶算法 实现多线程生产者消费者模型 限流 **/ public classBuckerTest {public static voidmain(String[] ... -
coding++:Semaphore—RateLimiter-漏桶算法-令牌桶算法
2021-03-12 19:59:27关于限流 目前存在两大类,从线程个数(jdk1.5 Semaphore)和RateLimiter速率(guava)Semaphore:从线程个数限流RateLimiter:从速率限流 目前常见的算法是漏桶算法和令牌算法令牌桶算法。相比漏桶算法而言区别在于,... -
漏桶算法详解
2020-07-21 17:18:26本篇介绍漏桶算法,具体的漏桶算法概念如下: 漏桶算法跟令牌桶比较类似,但实际上是两种策略。想了解令牌桶算法的可以看之前的文章。 下面我们看一下维基百科的图片: 如图所示,我们可以看到,整个算法其实十分... -
redis--lua实现漏桶算法限流
2021-04-26 18:46:48纯粹无聊写的,没啥大用,本来是想保证原子性,但是写完发现虽然内部逻辑保证了但是,调用时还是会无法保证原子性,实际完全可以写在java里然后加个分布式锁优雅解决,不过既然写了直接删了太... //获取该渠道桶的初始. -
Go-ratelimit基于令牌桶算法和漏桶算法来实现的限速限流Golang实现
2019-08-14 03:39:03ratelimit 基于令牌桶算法和漏桶算法来实现的限速限流,Golang实现 -
限流之漏桶算法
2022-01-09 17:24:45漏桶算法比较形象,设想有一个桶,桶的底部有一个洞,当装上水的时候,水会一滴一滴地从底部漏掉。当装的水太满,水会溢出,但底部漏水的速度还是不变的。 底部漏水的速度就是系统处理的速度,桶里存储的水就是上游... -
go漏桶算法
2020-06-12 09:21:38go漏桶算法 伪代码 // 定义漏桶结构 type leakyBucket struct { timestamp time.Time // 当前注水时间戳 (当前请求时间戳) capacity float64 // 桶的容量(接受缓存的请求总量) rate float64// 水流出的速度... -
Guava提供的RateLimiter限流原理以及漏桶算法和令牌桶算法
2021-05-13 23:00:40文章目录漏桶算法、令牌桶算法思路及使用场景RateLimiter实现原理SmoothBurstySmoothWarmingUp 漏桶算法、令牌桶算法思路及使用场景 在介绍RateLimiter之前我们先看下常说的漏桶算法和令牌桶算法,看下两种算法的... -
实现一个简单的漏桶算法
2020-03-02 15:42:55look code package bucket; public class LeakyBucket { //桶容量 private int maxVolume; //流出速度 private int outSpeed; //当前容量 private int volume; //当前流出量 private int stream... -
漏桶算法-php
2020-08-18 14:00:231,概览 最近研究nginx的限流,limit_req_zone。 其功能就是限制大访问量下的请求数量,防止服务器故障。 核心逻辑就是: ...比较感兴趣它的实现,查阅资料发现利用了漏桶算法。就顺便带着学习一下。 漏 -
漏桶算法与令牌桶算法
2021-06-21 16:07:00漏桶算法(有点像Kafka,大批量的数据吞吐) 漏桶算法的主要思路为: 在nginx层与controller层加一层(即漏桶层), 用于接收nginx收到的大批量的请求,接收的请求的速度是没有控制的,但是如果超过了漏桶层的最大容量则... -
nginx 实现网关限流之一 漏桶算法
2020-06-16 22:07:09漏桶算法(Leaky Bucket)是网络世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过... -
漏桶、令牌桶算法原理与简单实现
2019-06-01 15:55:19漏桶算法 漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制... -
Go-Goratelimiter漏桶算法速率限制的一个Golang实现
2019-08-14 03:38:09Go rate limiter:漏桶算法速率限制的一个Golang实现 -
漏桶算法和令牌桶算法
2021-05-20 16:22:22目录 一、漏桶和令牌桶介绍 二、Redis+lua脚本 + 令牌桶算法 实现限流控制 1、自定义一个注解,用来给限流的方法标注 ...漏桶算法与令牌桶算法的区别在于:l漏桶算法能够强行限制数据的传输速率。l令牌桶算法能... -
【高并发应用场景】高并发系统限流-漏桶算法和令牌桶算法
2021-11-04 12:05:24令牌桶算法三、限流工具类RateLimiter四、适用场景五、服务治理---限流(令牌桶算法)六、高并发系统限流中的漏桶算法和令牌桶算法,通过流量整形和速率限制提升稳定性七、聊聊高并发系统之限流特技**限流算法****... -
高并发限流算法之漏桶算法&令牌桶算法
2019-07-31 10:23:25今天我们就记录介绍一下高并发情况下,最常用的两种限流算法:漏桶算法和令牌桶算法的原理,方便更好的理解。比如nginx中的限制访问速率的算法就是漏桶算法。 原文详情请查看我的个人博客: 右键在新标签页中...