日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > > 架構師社區(qū)
[導讀]作者 | 王磊? 來源 | Java中文社群 為了上班方便,去年我把自己在北郊的房子租出去了,搬到了南郊,這樣離我上班的地方就近了,它為我節(jié)約了很多的時間成本,我可以用它來做很多有意義的事,最起碼不會因為堵車而鬧心了,幸福感直線上升。 但即使這樣,生活也

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

作者 | 王磊  來源 | Java中文社群


為了上班方便,去年我把自己在北郊的房子租出去了,搬到了南郊,這樣離我上班的地方就近了,它為我節(jié)約了很多的時間成本,我可以用它來做很多有意義的事,最起碼不會因為堵車而鬧心了,幸福感直線上升。

但即使這樣,生活也有其他的煩惱。南郊的居住密度比較大,因此停車就成了頭痛的事,我租的是路兩邊的非固定車位,每次只要下班回來,一定是沒有車位停了,因此我只能和別人的車并排停著,但這樣帶來的問題是,我每天早上都要被挪車的電話給叫醒,心情自然就不用說了。

但后來幾天,我就慢慢變聰明了,我頭天晚上停車的時候,會找第二天限行的車并排停著,這樣我第二天就不用挪車了,這真是限行給我?guī)淼摹熬薮蠹t利”啊。

車輛限行就是一種生活中很常見的限流策略,他除了給我?guī)砹艘陨系暮锰幹?,還給我們美好的生活環(huán)境帶來了一絲改善,并且快速增長的私家車已經給我們的交通帶來了巨大的“負擔,如果再不限行,可能所有的車都要被堵在路上,這就是限流給我們的生活帶來的巨大好處。

從生活回到程序中,假設一個系統(tǒng)只能為 10W 人提供服務,突然有一天因為某個熱點事件,造成了系統(tǒng)短時間內的訪問量迅速增加到了 50W,那么導致的直接結果是系統(tǒng)崩潰,任何人都不能用系統(tǒng)了,顯然只有少人數(shù)能用遠比所有人都不能用更符合我們的預期,因此這個時候我們要使用「限流。

限流分類

限流的實現(xiàn)方案有很多種,磊哥這里稍微理了一下,限流的分類如下所示:

  1. 合法性驗證限流:比如驗證碼、IP 黑名單等,這些手段可以有效的防止惡意攻擊和爬蟲采集;
  2. 容器限流:比如 Tomcat、Nginx 等限流手段,其中 Tomcat 可以設置最大線程數(shù)(maxThreads),當并發(fā)超過最大線程數(shù)會排隊等待執(zhí)行;而 Nginx 提供了兩種限流手段:一是控制速率,二是控制并發(fā)連接數(shù);
  3. 服務端限流:比如我們在服務器端通過限流算法實現(xiàn)限流,此項也是我們本文介紹的重點。

合法性驗證限流為最常規(guī)的業(yè)務代碼,就是普通的驗證碼和 IP 黑名單系統(tǒng),本文就不做過多的敘述了,我們重點來看下后兩種限流的實現(xiàn)方案:容器限流和服務端限流。

容器限流

Tomcat 限流

Tomcat 8.5 版本的最大線程數(shù)在 conf/server.xml 配置中,如下所示:

<Connector port="8080" protocol="HTTP/1.1"
          connectionTimeout="20000"
          maxThreads="150"
          redirectPort="8443" />

其中 maxThreads 就是 Tomcat 的最大線程數(shù),當請求的并發(fā)大于此值(maxThreads)時,請求就會排隊執(zhí)行,這樣就完成了限流的目的。

小貼士:maxThreads 的值可以適當?shù)恼{大一些,此值默認為 150(Tomcat 版本 8.5.42),但這個值也不是越大越好,要看具體的硬件配置,需要注意的是每開啟一個線程需要耗用 1MB 的 JVM 內存空間用于作為線程棧之用,并且線程越多 GC 的負擔也越重。最后需要注意一下,操作系統(tǒng)對于進程中的線程數(shù)有一定的限制,Windows 每個進程中的線程數(shù)不允許超過 2000,Linux 每個進程中的線程數(shù)不允許超過 1000。

Nginx 限流

Nginx 提供了兩種限流手段:一是控制速率,二是控制并發(fā)連接數(shù)。

控制速率

我們需要使用 limit_req_zone 用來限制單位時間內的請求數(shù),即速率限制,示例配置如下:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit;
    }
}

以上配置表示,限制每個 IP 訪問的速度為 2r/s,因為 Nginx 的限流統(tǒng)計是基于毫秒的,我們設置的速度是 2r/s,轉換一下就是 500ms 內單個 IP 只允許通過 1 個請求,從 501ms 開始才允許通過第 2 個請求。

我們使用單 IP 在 10ms 內發(fā)并發(fā)送了 6 個請求的執(zhí)行結果如下:

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

從以上結果可以看出他的執(zhí)行符合我們的預期,只有 1 個執(zhí)行成功了,其他的 5 個被拒絕了(第 2 個在 501ms 才會被正常執(zhí)行)。

速率限制升級版

上面的速率控制雖然很精準但是應用于真實環(huán)境未免太苛刻了,真實情況下我們應該控制一個 IP 單位總時間內的總訪問次數(shù),而不是像上面那么精確但毫秒,我們可以使用 burst 關鍵字開啟此設置,示例配置如下:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4;
    }
}

burst=4 表示每個 IP 最多允許4個突發(fā)請求,如果單個 IP 在 10ms 內發(fā)送 6 次請求的結果如下:

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

從以上結果可以看出,有 1 個請求被立即處理了,4 個請求被放到 burst 隊列里排隊執(zhí)行了,另外 1 個請求被拒絕了。

控制并發(fā)數(shù)

利用 limit_conn_zone 和 limit_conn 兩個指令即可控制并發(fā)數(shù),示例配置如下:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

其中 limit_conn perip 10 表示限制單個 IP 同時最多能持有 10 個連接;limit_conn perserver 100 表示 server 同時能處理并發(fā)連接的總數(shù)為 100 個。

小貼士:只有當 request header 被后端處理后,這個連接才進行計數(shù)。

服務端限流

服務端限流需要配合限流的算法來執(zhí)行,而算法相當于執(zhí)行限流的“大腦”,用于指導限制方案的實現(xiàn)。

有人看到「算法」兩個字可能就暈了,覺得很深奧,其實并不是。算法就相當于操作某個事務的具體實現(xiàn)步驟匯總,其實并不難懂,不要被它的表象給嚇到哦~

限流的常見算法有以下三種:

  1. 時間窗口算法
  2. 漏桶算法
  3. 令牌算法

接下來我們分別看來。

1.時間窗口算法

所謂的滑動時間算法指的是以當前時間為截止時間,往前取一定的時間,比如往前取 60s 的時間,在這 60s 之內運行最大的訪問數(shù)為 100,此時算法的執(zhí)行邏輯為,先清除 60s 之前的所有請求記錄,再計算當前集合內請求數(shù)量是否大于設定的最大請求數(shù) 100,如果大于則執(zhí)行限流拒絕策略,否則插入本次請求記錄并返回可以正常執(zhí)行的標識給客戶端。

滑動時間窗口如下圖所示:

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

其中每一小個表示 10s,被紅色虛線包圍的時間段則為需要判斷的時間間隔,比如 60s 秒允許 100 次請求,那么紅色虛線部分則為 60s。

我們可以借助 Redis 的有序集合 ZSet 來實現(xiàn)時間窗口算法限流,實現(xiàn)的過程是先使用 ZSet 的 key 存儲限流的 ID,score 用來存儲請求的時間,每次有請求訪問來了之后,先清空之前時間窗口的訪問量,統(tǒng)計現(xiàn)在時間窗口的個數(shù)和最大允許訪問量對比,如果大于等于最大訪問量則返回 false 執(zhí)行限流操作,負責允許執(zhí)行業(yè)務邏輯,并且在 ZSet 中添加一條有效的訪問記錄,具體實現(xiàn)代碼如下。

我們借助 Jedis 包來操作 Redis,實現(xiàn)在 pom.xml 添加 Jedis 框架的引用,配置如下:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

具體的 Java 實現(xiàn)代碼如下:

import redis.clients.jedis.Jedis;

public class RedisLimit {
    // Redis 操作客戶端
    static Jedis jedis = new Jedis("127.0.0.1"6379);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 15; i++) {
            boolean res = isPeriodLimiting("java"310);
            if (res) {
                System.out.println("正常執(zhí)行請求:" + i);
            } else {
                System.out.println("被限流:" + i);
            }
        }
        // 休眠 4s
        Thread.sleep(4000);
        // 超過最大執(zhí)行時間之后,再從發(fā)起請求
        boolean res = isPeriodLimiting("java"310);
        if (res) {
            System.out.println("休眠后,正常執(zhí)行請求");
        } else {
            System.out.println("休眠后,被限流");
        }
    }

    /**
     * 限流方法(滑動時間算法)
     * @param key      限流標識
     * @param period   限流時間范圍(單位:秒)
     * @param maxCount 最大運行訪問次數(shù)
     * @return
     */

    private static boolean isPeriodLimiting(String key, int period, int maxCount) {
        long nowTs = System.currentTimeMillis(); // 當前時間戳
        // 刪除非時間段內的請求數(shù)據(jù)(清除老訪問數(shù)據(jù),比如 period=60 時,標識清除 60s 以前的請求記錄)
        jedis.zremrangeByScore(key, 0, nowTs - period * 1000);
        long currCount = jedis.zcard(key); // 當前請求次數(shù)
        if (currCount >= maxCount) {
            // 超過最大請求次數(shù),執(zhí)行限流
            return false;
        }
        // 未達到最大請求數(shù),正常執(zhí)行業(yè)務
        jedis.zadd(key, nowTs, "" + nowTs); // 請求記錄 +1
        return true;
    }
}

以上程序的執(zhí)行結果為:

正常執(zhí)行請求:0

正常執(zhí)行請求:1

正常執(zhí)行請求:2

正常執(zhí)行請求:3

正常執(zhí)行請求:4

正常執(zhí)行請求:5

正常執(zhí)行請求:6

正常執(zhí)行請求:7

正常執(zhí)行請求:8

正常執(zhí)行請求:9

被限流:10

被限流:11

被限流:12

被限流:13

被限流:14

休眠后,正常執(zhí)行請求

此實現(xiàn)方式存在的缺點有兩個:

  • 使用 ZSet 存儲有每次的訪問記錄,如果數(shù)據(jù)量比較大時會占用大量的空間,比如 60s 允許 100W 訪問時;
  • 此代碼的執(zhí)行非原子操作,先判斷后增加,中間空隙可穿插其他業(yè)務邏輯的執(zhí)行,最終導致結果不準確。

2.漏桶算法

漏桶算法的靈感源于漏斗,如下圖所示:

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

滑動時間算法有一個問題就是在一定范圍內,比如 60s 內只能有 10 個請求,當?shù)谝幻霑r就到達了 10 個請求,那么剩下的 59s 只能把所有的請求都給拒絕掉,而漏桶算法可以解決這個問題。

漏桶算法類似于生活中的漏斗,無論上面的水流倒入漏斗有多大,也就是無論請求有多少,它都是以均勻的速度慢慢流出的。當上面的水流速度大于下面的流出速度時,漏斗會慢慢變滿,當漏斗滿了之后就會丟棄新來的請求;當上面的水流速度小于下面流出的速度的話,漏斗永遠不會被裝滿,并且可以一直流出。

漏桶算法的實現(xiàn)步驟是,先聲明一個隊列用來保存請求,這個隊列相當于漏斗,當隊列容量滿了之后就放棄新來的請求,然后重新聲明一個線程定期從任務隊列中獲取一個或多個任務進行執(zhí)行,這樣就實現(xiàn)了漏桶算法。

上面我們演示 Nginx 的控制速率其實使用的就是漏桶算法,當然我們也可以借助 Redis 很方便的實現(xiàn)漏桶算法。

我們可以使用 Redis 4.0 版本中提供的 Redis-Cell 模塊,該模塊使用的是漏斗算法,并且提供了原子的限流指令,而且依靠 Redis 這個天生的分布式程序就可以實現(xiàn)比較完美的限流了。

Redis-Cell 實現(xiàn)限流的方法也很簡單,只需要使用一條指令 cl.throttle 即可,使用示例如下:

> cl.throttle mylimit 15 30 60
1)(integer)0 # 0 表示獲取成功,1 表示拒絕
2)(integer)15 # 漏斗容量
3)(integer)14 # 漏斗剩余容量
4)(integer)-1 # 被拒絕之后,多長時間之后再試(單位:秒)-1 表示無需重試
5)(integer)2 # 多久之后漏斗完全空出來

其中 15 為漏斗的容量,30 / 60s 為漏斗的速率。

3.令牌算法

在令牌桶算法中有一個程序以某種恒定的速度生成令牌,并存入令牌桶中,而每個請求需要先獲取令牌才能執(zhí)行,如果沒有獲取到令牌的請求可以選擇等待或者放棄執(zhí)行,如下圖所示:

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

我們可以使用 Google 開源的 guava 包,很方便的實現(xiàn)令牌桶算法,首先在 pom.xml 添加 guava 引用,配置如下:

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.2-jre</version>
</dependency>

具體實現(xiàn)代碼如下:

import com.google.common.util.concurrent.RateLimiter;

import java.time.Instant;

/**
 * Guava 實現(xiàn)限流
 */

public class RateLimiterExample {
    public static void main(String[] args) {
        // 每秒產生 10 個令牌(每 100 ms 產生一個)
        RateLimiter rt = RateLimiter.create(10);
        for (int i = 0; i < 11; i++) {
            new Thread(() -> {
                // 獲取 1 個令牌
                rt.acquire();
                System.out.println("正常執(zhí)行方法,ts:" + Instant.now());
            }).start();
        }
    }
}

以上程序的執(zhí)行結果為:

正常執(zhí)行方法,ts:2020-05-15T14:46:37.175Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.237Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.339Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.442Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.542Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.640Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.741Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.840Z

正常執(zhí)行方法,ts:2020-05-15T14:46:37.942Z

正常執(zhí)行方法,ts:2020-05-15T14:46:38.042Z

正常執(zhí)行方法,ts:2020-05-15T14:46:38.142Z

從以上結果可以看出令牌確實是每 100ms 產生一個,而 acquire() 方法為阻塞等待獲取令牌,它可以傳遞一個 int 類型的參數(shù),用于指定獲取令牌的個數(shù)。它的替代方法還有 tryAcquire(),此方法在沒有可用令牌時就會返回 false 這樣就不會阻塞等待了。當然 tryAcquire() 方法也可以設置超時時間,未超過最大等待時間會阻塞等待獲取令牌,如果超過了最大等待時間,還沒有可用的令牌就會返回 false。

注意:使用 guava 實現(xiàn)的令牌算法屬于程序級別的單機限流方案,而上面使用 Redis-Cell 的是分布式的限流方案。

總結

本文提供了 6 種具體的實現(xiàn)限流的手段,他們分別是:Tomcat 使用 maxThreads 來實現(xiàn)限流;Nginx 提供了兩種限流方式,一是通過 limit_req_zone 和 burst 來實現(xiàn)速率限流,二是通過 limit_conn_zonelimit_conn 兩個指令控制并發(fā)連接的總數(shù)。最后我們講了時間窗口算法借助 Redis 的有序集合可以實現(xiàn),還有漏桶算法可以使用 Redis-Cell 來實現(xiàn),以及令牌算法可以解決 Google 的 guava 包來實現(xiàn)。

需要注意的是借助 Redis 實現(xiàn)的限流方案可用于分布式系統(tǒng),而 guava 實現(xiàn)的限流只能應用于單機環(huán)境。如果你嫌棄服務器端限流麻煩,甚至可以在不改代碼的情況下直接使用容器限流(Nginx 或 Tomcat),但前提是能滿足你的業(yè)務需求。

好了,文章到這里就結束了,期待我們下期再會~

參考 & 鳴謝

https://www.cnblogs.com/biglittleant/p/8979915.html

特別推薦一個分享架構+算法的優(yōu)質內容,還沒關注的小伙伴,可以長按關注一下:

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

長按訂閱更多精彩▼

人人都能看懂的 6 種限流實現(xiàn)方案!(純干貨)

如有收獲,點個在看,誠摯感謝


免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: 驅動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設備,其驅動電源的性能直接關系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅動電源設計中至關重要的兩個環(huán)節(jié),集成化方案的設計成為提升電機驅動性能的關鍵。

關鍵字: 工業(yè)電機 驅動電源

LED 驅動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設備的使用壽命。然而,在實際應用中,LED 驅動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設計、生...

關鍵字: 驅動電源 照明系統(tǒng) 散熱

根據(jù)LED驅動電源的公式,電感內電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關鍵字: LED 設計 驅動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產業(yè)的重要發(fā)展方向。電動汽車的核心技術之一是電機驅動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅動系統(tǒng)中的關鍵元件,其性能直接影響到電動汽車的動力性能和...

關鍵字: 電動汽車 新能源 驅動電源

在現(xiàn)代城市建設中,街道及停車場照明作為基礎設施的重要組成部分,其質量和效率直接關系到城市的公共安全、居民生活質量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關鍵字: 發(fā)光二極管 驅動電源 LED

LED通用照明設計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關鍵字: LED 驅動電源 功率因數(shù)校正

在LED照明技術日益普及的今天,LED驅動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關鍵字: LED照明技術 電磁干擾 驅動電源

開關電源具有效率高的特性,而且開關電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅動電源

關鍵字: LED 驅動電源 開關電源

LED驅動電源是把電源供應轉換為特定的電壓電流以驅動LED發(fā)光的電壓轉換器,通常情況下:LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: LED 隧道燈 驅動電源
關閉