高併發後端設計-限流篇

系統在設計之初就會有一個預估容量,長時間超過系統能承受的TPS/QPS閾值,系統可能會被壓垮,最終致使整個服務不夠用。爲了不這種狀況,咱們就須要對接口請求進行限流。java

限流的目的是經過對併發訪問請求進行限速或者一個時間窗口內的的請求數量進行限速來保護系統,一旦達到限制速率則能夠拒絕服務、排隊或等待。算法

常見的限流模式有控制併發和控制速率,一個是限制併發的數量,一個是限制併發訪問的速率,另外還能夠限制單位時間窗口內的請求數量。緩存

控制併發數量 屬於一種較常見的限流手段,在實際應用中能夠經過信號量機制(如Java中的Semaphore)來實現。 舉個例子,咱們對外提供一個服務接口,容許最大併發數爲10,代碼實現以下:bash

public class DubboService {    private final Semaphore permit = new Semaphore(10, true);    public void process(){        try{
            permit.acquire();            //業務邏輯處理

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            permit.release();
        }
    }
}
複製代碼

在代碼中,雖然有30個線程在執行,可是隻容許10個併發的執行。Semaphore的構造方法Semaphore(int permits) 接受一個整型的數字,表示可用的許可證數量。Semaphore(10)表示容許10個線程獲取許可證,也就是最大併發數是10。Semaphore的用法也很簡單,首先線程使用Semaphore的acquire()獲取一個許可證,使用完以後調用release()歸還許可證,還能夠用tryAcquire()方法嘗試獲取許可證。併發

控制訪問速率 在咱們的工程實踐中,常見的是使用令牌桶算法來實現這種模式,其餘如漏桶算法也能夠實現控制速率,但在咱們的工程實踐中使用很少,這裏不作介紹,讀者請自行了解。ide

在Wikipedia上,令牌桶算法是這麼描述的:ui

每過1/r秒桶中增長一個令牌。google

桶中最多存放b個令牌,若是桶滿了,新放入的令牌會被丟棄。atom

當一個n字節的數據包到達時,消耗n個令牌,而後發送該數據包。spa

若是桶中可用令牌小於n,則該數據包將被緩存或丟棄。

令牌桶控制的是一個時間窗口內經過的數據量,在API層面咱們常說的QPS、TPS,正好是一個時間窗口內的請求量或者事務量,只不過期間窗口限定在1s罷了。以一個恆定的速度往桶裏放入令牌,而若是請求須要被處理,則須要先從桶裏獲取一個令牌,當桶裏沒有令牌可取時,則拒絕服務。令牌桶的另一個好處是能夠方便的改變速度,一旦須要提升速率,則按需提升放入桶中的令牌的速率。

在咱們的工程實踐中,一般使用Guava中的Ratelimiter來實現控制速率,如咱們不但願每秒的任務提交超過2個:

//速率是每秒兩個許可final RateLimiter rateLimiter = RateLimiter.create(2.0);

void submitTasks(List tasks, Executor executor) {    for (Runnable task : tasks) {
        rateLimiter.acquire(); // 也許須要等待
        executor.execute(task);
    }
}
複製代碼

控制單位時間窗口內請求數 某些場景下,咱們想限制某個接口或服務 每秒/每分鐘/天天 的請求次數或調用次數。例如限制服務每秒的調用次數爲50,實現以下:

import com.google.common.cache.CacheBuilder;import com.google.common.cache.CacheLoader;import com.google.common.cache.LoadingCache;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicLong;

    private LoadingCache<Long, AtomicLong> counter =
            CacheBuilder.newBuilder()                    .expireAfterWrite(2, TimeUnit.SECONDS)                    .build(new CacheLoader<Long, AtomicLong>() {
                        @Override
                        public AtomicLong load(Long seconds) throws Exception {
                            return new AtomicLong(0);
                        }
                    });

    public static long permit = 50;

    public ResponseEntity getData() throws ExecutionException {

        //獲得當前秒
        long currentSeconds = System.currentTimeMillis() / 1000;
        if(counter.get(currentSeconds).incrementAndGet() > permit) {
            return ResponseEntity.builder().code(404).msg("訪問速率過快").build();
        }
        //業務處理

    }
    ```
關於限流部分到此就結束了,下一篇將介紹降級和熔斷機制。複製代碼
相關文章
相關標籤/搜索