在開發高并發高可用系統時有三把利器用來保護系統:
緩存
、降級
和限流
。(1)緩存的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高并發流量的銀彈;
(2)降級是當服務出問題或者影響到核心流程的性能則需要暫時屏蔽掉,待高峰或者問題解決后再打開;
(3)而有些場景并不能用緩存和降級來解決,比如稀缺資源(秒殺、搶購)、寫服務(如評論、下單)、頻繁的復雜查詢(評論的最后幾頁),因此需有一種手段來限制這些場景的并發/請求量,即限流。
后面兩條主要是為了提高系統的高可用
限流的目的是通過對并發訪問/請求進行限速或者一個時間窗口內的的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務(定向到錯誤頁或告知資源沒有了)、排隊或等待(比如秒殺、評論、下單)、降級(返回兜底數據或默認數據,如商品詳情頁庫存默認有貨)
常見的限流算法有:令牌桶
、漏桶
。計數器也可以進行粗暴限流實現。
漏桶算法
漏桶算法思路很簡單,水(請求)先進入到漏桶里,漏桶以一定的速度出水,當水流入速度過大會直接溢出,可以看出漏桶算法能強行限制數據的傳輸速率
。
線程池就是一個例子
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
對于很多應用場景來說,除了要求能夠限制數據的平均傳輸速率外,還要求允許某種程度的突發傳輸。這時候漏桶算法可能就不合適了, 令牌桶算法
更為適合
令牌桶(Token bucket)
令牌桶算法的基本過程如下:
每秒會有 r 個令牌放入桶中,或者說,每過 1/r 秒桶中增加一個令牌
桶中最多存放 b 個令牌,如果桶滿了,新放入的令牌會被丟棄
當一個 n 字節的數據包到達時,消耗 n 個令牌,然后發送該數據包
如果桶中可用令牌小于 n,則該數據包將被緩存或丟棄
我們可以使用 Guava 的 RateLimiter 來實現基于令牌桶的流量控制。RateLimiter 令牌桶算法的單桶實現,RateLimiter 對簡單的令牌桶算法做了一些工程上的優化,具體的實現是 SmoothBursty。需要注意的是,RateLimiter 的另一個實現 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。
SmoothBursty 有一個可以放 N 個時間窗口產生的令牌的桶,系統空閑的時候令牌就一直攢著,最好情況下可以扛 N 倍于限流值的高峰而不影響后續請求,就像三峽大壩一樣能扛千年一遇的洪水.
使用
1、在我們的java項目中pom.xml文件中添加依賴
<!-- guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0-rc2</version>
</dependency>
2、代碼
package com.dubbo.demo;
import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by haichenglhc on 27/02/2017.
*
* @author haichenglhc
* @date 2017/02/27
*/
public class TT {
private static final ConcurrentMap<String, RateLimiter> resourceRateLimiterMap =
new ConcurrentHashMap<String, RateLimiter>();
public static void createFlowLimitMap(String resource, double qps) {
RateLimiter limiter = resourceRateLimiterMap.get(resource);
if (limiter == null) {
limiter = RateLimiter.create(qps);
resourceRateLimiterMap.putIfAbsent(resource, limiter);
}
limiter.setRate(qps);
}
public static boolean enter(int i, String resource) throws Exception {
RateLimiter limiter = resourceRateLimiterMap.get(resource);
if (limiter == null) {
throw new Exception(resource);
}
// System.out.println(limiter.acquire());
if (!limiter.tryAcquire()) {
System.out.println(i + " >>>>>>>>>>>>>>>>>被限流了>>>>>>>>");
return true;
}
return false;
}
static class TestWork implements Runnable {
private int i;
public TestWork() {
}
public TestWork(int i) {
this.i = i;
}
@Override
public void run() {
try {
if (!enter(i,"test")) {
System.out.println(i + " ++++++++++++ 沒有被限流");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
String source = "test";
double qps = 10;
createFlowLimitMap(source, qps);
Thread.sleep(1000l);
ExecutorService pools = Executors.newFixedThreadPool(40);
for (int i = 0; i < 16; i++) {
TestWork testWork = new TestWork(i);
pools.execute(testWork);
}
}
}