帶你瞭解什麼是限流

前言

只有光頭才能變強。java

文本已收錄至個人GitHub倉庫,歡迎Star:github.com/ZhongFuChen…git

以前在學習的時候也接觸不到高併發/大流量這種東西,因此限流固然是沒接觸過的了。在看公司項目的時候,發現有用到限流(RateLimiter),順帶了解一波。github

1、限流基礎知識介紹

爲啥要限流,相信就不用我多說了。redis

  • 好比,我週末去飯店吃飯,可是人太多了,我只能去前臺拿個號,等號碼到個人時候才能進飯店吃飯。若是飯店沒有限流怎麼辦?一到飯點,人都往裏衝,而飯店又處理不了這麼多人流,很容易就出事故(飯店塞滿了人,無路可走。飯店的工做人員崩潰了,處理不過來)
  • 回到代碼世界上也是同樣的,服務器能處理的請求數有限,若是請求量特別大,咱們須要作限流(要麼就讓請求等待,要麼就把請求給扔了)

限流

在代碼世界上,限流有兩種比較常見的算法:算法

  • 令牌桶算法
  • 漏桶算法

1.1 什麼是漏桶算法

好比,如今我有一個桶子,綠色那塊是我能裝水的容量,若是超過我能裝下的容量,再往桶子裏邊倒水,就會溢出來(限流):segmentfault

桶子

咱們目前能夠知道的是:服務器

  • 桶子的容量是固定的(是圖上綠色那塊)
  • 超出了桶子的容量就會溢出(要麼等待,要麼直接丟棄)

OK,如今咱們在桶子裏挖個洞,讓水能夠從洞子裏邊流出來:網絡

挖了個洞,水從洞口流出來

桶子的洞口的大小是固定的,因此水從洞口流出來的速率也是固定的併發

因此總結下來算法所需的參數就兩個:分佈式

  • 桶子的容量
  • 漏水的速率

漏桶算法有兩種實現:

  1. 不容許突發流量的狀況:若是進水的速率大於出水的速率,直接捨棄掉多餘的水。好比,個人桶子容量能裝100L,但個人桶子出水速率是10L/s。此時,若是如今有100L/s的水進來,我只讓10L的水進到桶子,其他的都限流。(限定了請求的速度
  2. 容許必定的突發流量狀況:個人桶子能裝100L,若是如今個人桶子是空的,那麼這100L的水都能進個人桶子。我以10L/s的速率將這些水流出,若是還有100L的水進來,只能限流了。

通過上面的分析咱們就知道:

漏桶算法能夠平滑網絡上的突發流量(由於漏水的速率是固定的)

1.2 什麼是令牌桶算法

如今我有另一個桶子,這個桶子不用來裝水,用來裝令牌:

桶子裝着令牌

令牌會必定的速率扔進桶子裏邊,好比我1秒扔10個令牌進桶子:

以必定的速率扔令牌進桶子

桶子能裝令牌的個數有上限的,好比個人桶子最多隻能裝1000個令牌。

每一個請求進來,就會去桶子拿一個令牌

  • 好比這秒我有1001個請求,我就去桶子裏邊拿1001個令牌,此時可能會出現兩種狀況:
    • 桶子裏邊沒有1001個令牌,只有1000個,那沒拿到令牌的請求只能被阻塞了(等待)
    • 桶子裏邊有1001個令牌,全部請求均可以執行。

令牌桶算法支持網絡上的突發流量

**漏桶和令牌桶的區別:**從上面的例子估計你們也能看出來了,漏桶只能以固定的速率去處理請求,而令牌桶能夠以桶子最大的令牌數去處理請求

2、RateLimiter使用

RateLimiter是Guava的一個限流組件,我這邊的系統就有用到這個限流組件,使用起來十分方便。

引入pom依賴:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>
複製代碼

RateLimiter它是基於令牌桶算法的,API很是簡單,看如下的Demo:

public static void main(String[] args) {
        //線程池
        ExecutorService exec = Executors.newCachedThreadPool();
        //速率是每秒只有3個許可
        final RateLimiter rateLimiter = RateLimiter.create(3.0);

        for (int i = 0; i < 100; i++) {
            final int no = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        //獲取許可
                        rateLimiter.acquire();
                        System.out.println("Accessing: " + no + ",time:"
                                + new SimpleDateFormat("yy-MM-dd HH:mm:ss").format(new Date()));

                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            };
            //執行線程
            exec.execute(runnable);
        }
        //退出線程池
        exec.shutdown();
    }
複製代碼

咱們能夠從結果看出,每秒只能執行三個:

每秒執行三個

3、分佈式限流

RateLimiter是一個單機的限流組件,若是是分佈式應用的話,該怎麼作?

可使用Redis+Lua的方式來實現,大體的lua腳本代碼以下:

local key = "rate.limit:" .. KEYS[1] --限流KEY
local limit = tonumber(ARGV[1])        --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --若是超出限流大小
  return 0
else  --請求數+1,並設置1秒過時
  redis.call("INCRBY", key,"1")
   redis.call("expire", key,"1")
   return current + 1
end
複製代碼

Java代碼以下:

public static boolean accquire() throws IOException, URISyntaxException {
    Jedis jedis = new Jedis("127.0.0.1");
    File luaFile = new File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath() + "limit.lua");
    String luaScript = FileUtils.readFileToString(luaFile);

    String key = "ip:" + System.currentTimeMillis()/1000; // 當前秒
    String limit = "5"; // 最大限制
    List<String> keys = new ArrayList<String>();
    keys.add(key);
    List<String> args = new ArrayList<String>();
    args.add(limit);
    Long result = (Long)(jedis.eval(luaScript, keys, args)); // 執行lua腳本,傳入參數
    return result == 1;
}
複製代碼

解釋:

  • Java代碼傳入key和最大的限制limit參數進lua腳本
  • 執行lua腳本(lua腳本判斷當前key是否超過了最大限制limit)
    • 若是超過,則返回0(限流)
    • 若是沒超過,返回1(程序繼續執行)

參考來源:

更多資料參考:

最後

樂於輸出乾貨的Java技術公衆號:Java3y。公衆號內有200多篇原創技術文章、海量視頻資源、精美腦圖,關注便可獲取!

轉發到朋友圈是對我最大的支持!

以爲個人文章寫得不錯,點

相關文章
相關標籤/搜索