本文主要是介绍几种常用限流算法之计数器算法/漏桶算法/令牌桶算法对比?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
定义 | 优点 | 缺点 | 实现方式 | |
计数器算法 | 从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。 | 简单、方便 | 突刺现象 | AutomicLong LongAddter |
漏桶算法 | 算法内部有一个容器,类似生活用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。 | 可以消除突刺现象 | 不管上面流量多大,下面流出的速度始终保持不变。可能突然进来很多请求,没来得及处理的请求就先放在桶里,如果桶满了,那么新进来的请求就丢弃。 无法应对短时间的突发流量 | 队列保存请求,线程池定期从队列中获取请求并执行 |
令牌桶算法 | 存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。 | 可以应对短时间的突发流量 | 队列保存令牌,线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。 |
突刺现象
如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝。
限流算法
漏桶算法示意图
令牌桶算法示意图
推荐用LongAdder
先来看一下AtomicLong.incrementAndGet()方法的源码
public final long incrementAndGet() {return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
接着跟踪Unsafe类的getAndAddLong方法
可以清楚地看到,AtomicLong的原子性自增操作,是通过CAS实现的。
在多线程竞争不激烈的情况下,这样做是合适的。但是如果线程竞争激烈,会造成大量线程在原地打转、不停尝试去修改值,但是老是发现值被修改了,于是继续自旋。这样浪费了大量的CPU资源。
而且,由于AtomicLong持有的成员变量value是volatile关键字修饰的,线程修改了临界资源后,需要刷新到其他线程,也是要费一番功夫的。
而LongAdder也有一个volatile修饰的base值,但是当竞争激烈时,多个线程并不会一直自旋来修改这个值,而是采用了分段的思想。竞争激烈时,各个线程会分散累加到自己所对应的Cell[]数组的某一个数组对象元素中,而不会大家共用一个。
这样做,可以把不同线程对应到不同的Cell中进行修改,降低了对临界资源的竞争。本质上,是用空间换时间。
LongAdder源码
public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) {boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))longAccumulate(x, null, uncontended);}}
这篇关于几种常用限流算法之计数器算法/漏桶算法/令牌桶算法对比?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!