日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:52000
  • 待審:37
  • 小程序:12
  • 文章:1037587
  • 會員:756

軟件系統有三個追求:高性能、高并發、高可用,俗稱三高。本篇討論高并發,從高并發是什么到高并發應對的策略、緩存、限流、降級等。

引言

1.高并發背景

互聯網行業迅速發展,用戶量劇增,系統面臨巨大的并發請求壓力。

軟件系統有三個追求:高性能、高并發、高可用,俗稱三高。三者既有區別也有聯系,門門道道很多,全面討論需要三天三夜,本篇討論高并發。

2.高并發對系統的挑戰

性能下降、資源競爭和穩定性問題等。

一、什么是高并發

1.高并發的定義

高并發是指系統或應用程序在同一時間段內接收到大量并發請求的能力。具體來說,高并發環境下系統需要能夠同時處理大量的請求,而不會出現性能問題或響應延遲。

2.高并發的特點

  • 大量請求:高并發場景下,系統需要同時處理大量的請求,這些請求可能來自于不同的用戶或客戶端。
  • 同時訪問:這些請求幾乎同時到達系統,需要在短時間內進行處理和響應。
  • 資源競爭:由于大量請求同時到達,系統的資源(如CPU、內存、網絡帶寬等)可能會面臨競爭和爭奪。
  • 響應時間要求高:高并發場景通常對系統的響應速度有較高的要求,用戶期望能夠快速獲取響應結果。

3.高并發場景和應用

高并發場景廣泛應用于熱門網站、電商平臺、社交媒體等互聯網應用中。例如,在電商平臺上有大量用戶同時瀏覽、搜索商品,提交訂單等操作;社交媒體平臺上有大量用戶同時發布、點贊、評論等操作。這些場景需要系統能夠同時處理大量請求,并保證系統的性能、可用性和用戶體驗。

4.高并發的影響

  • 系統性能的下降和延遲增加
  • 資源競爭和資源耗盡
  • 系統穩定性和可用性的挑戰

二、高并發應對策略

  • 緩存:緩解系統負載壓力,提高系統響應速度
  • 限流:控制并發訪問量,保護系統免受過載影響
  • 降級:保證核心功能的穩定性,舍棄非關鍵業務或簡化處理

三、緩存

1.簡介

在網站或App的開發中,緩存機制是一個不可或缺的環節,可以提高網站或APP的訪問速度,降低數據庫壓力。在高并發環境下,緩存機制的作用更加明顯,不僅可以有效減輕數據庫的負載,還可以提高系統的穩定性和性能,從而給用戶帶來更好的體驗。

2.工作原理

緩存的工作原理是先從緩存中獲取數據,如果有數據則直接返回給用戶,如果沒有數據則從慢速設備上讀取實際數據并且將數據放入緩存。

3.常用技術

1)瀏覽器緩存

簡介:瀏覽器緩存是指將網頁中的資源(如html、css、JAVAScript、圖像等)存儲在用戶的瀏覽器內部,以便在后續請求同一資源時可以直接從本地緩存中獲取,而無需再次從服務器下載。

適用場景:瀏覽器緩存適用于那些靜態內容變化較少的網頁和靜態資源,可以顯著提升網站性能和用戶體驗,并減少服務器的負載。

常見用法:

使用瀏覽器緩存可以通過設置響應頭中的Expires和Cache-Control字段來控制緩存的行為。

  • 使用Expires字段:Expires字段指定了緩存的過期時間,是一個具體的日期和時間。服務器可以在響應頭中添加Expires字段,告訴瀏覽器在該時間之前可以直接從緩存中獲取資源,而無需再向服務器發起請求。例如:Expires: Mon, 31 Dec 2022 23:59:59 GMT。
  • 使用Cache-Control字段:Cache-Control字段提供了更靈活的緩存控制選項。可以通過設置max-age指令來指定緩存的最大有效時間,單位是秒。例如:Cache-Control: max-age=3600 表示資源可以在1小時內直接從緩存中獲取。還可以使用其他指令,如no-cache表示緩存但不使用緩存、no-store表示禁止緩存等。

注意事項

瀏覽器緩存存儲實時性不敏感的數據,如商品框架、商家評分、評價和廣告詞。它有過期時間,并通過響應頭進行控制。實時性要求高的數據不適合使用瀏覽器緩存。

2)客戶端緩存

簡介:客戶端緩存是將數據存儲在瀏覽器中,以提高訪問速度和減少服務器請求。

適用場景:在大促期間,為了防止服務端承受瞬間的高流量壓力,可以提前將一些素材(如js/css/image等)下發到客戶端進行緩存,避免在大促期間再次請求這些素材。此外,還可以將一些兜底數據或樣式文件存放在客戶端緩存中,以確保在服務端異?;蚓W絡異常的情況下,保持app的正常運行。

3)CDN緩存

簡介:

CDN(Content Delivery.NETwork)是建立在承載網之上的分布式網絡,由分布在不同區域的邊緣節點服務器組成。

CDN緩存通常用于存放靜態頁面數據、活動頁面、圖片等數據。它有兩種緩存機制:推送機制(將數據主動推送到CDN節點)和拉取機制(首次訪問時從源服務器獲取數據并存儲在CDN節點)。

適用場景:CDN緩存可以提高網站訪問速度,適用于網站訪問量大、訪問速度慢、數據變化不頻繁的場景。

常用工具以及用法

常見的CDN緩存工具包括Cloudflare、AkamAI、Fastly和AWS CloudFront等。這些工具提供了全球分布的CDN網絡,以加速內容傳輸和提升性能。它們提供了控制臺和API,用于配置CDN緩存規則、管理緩存內容、刷新和更新緩存等。

4)反向代理緩存

簡介:反向代理緩存是指在反向代理服務器上對請求的響應進行緩存,以提高服務的性能和用戶體驗。它將經常請求的靜態內容緩存在代理服務器上,當有用戶請求同樣的內容時,代理服務器會直接返回緩存的響應,而無需再次向源服務器請求。

適用場景:適用于訪問外部服務速度比較慢,但是數據變化不頻繁的場景。

常用工具以及用法

  • Nginx:一款高性能的反向代理服務器,支持反向代理緩存功能,可通過配置文件進行緩存策略的設置。Nginx代理層緩存主要以Http模塊與proxy_cacahe模塊進行配置即可。
  • Varnish:一個專門用于反向代理緩存的開源軟件,可以高效地緩存并提供快速的響應。
  • Squid:一款功能強大的緩存代理服務器,支持反向代理緩存和正向代理緩存。

5)本地緩存

簡介:本地緩存是將數據或資源存儲在客戶端的存儲介質中,如硬盤、內存或數據庫。它可以是臨時的,只在應用程序運行期間有效,或者可以是持久的,即在不同的應用程序會話中保持有效。

適用場景:本地緩存適用于頻繁訪問數據、離線訪問、減少帶寬消耗和提升用戶體驗的場景。

常用工具以及用法:一般分為磁盤緩存、CPU緩存、應用緩存。

  • 磁盤緩存:存儲在硬盤等永久性存儲介質上,用于加速數據的讀取和訪問。
  • CPU緩存:位于處理器內部的高速存儲器,用于暫時存儲頻繁訪問的數據或指令,提高計算機的性能。
  • 應用緩存:存儲在內存中的應用程序數據或資源,用于提高應用程序的響應速度和用戶體驗。用Java服務來舉例,又分為 堆內緩存 與 堆外緩存 。

6)分布式緩存

簡介:分布式緩存是將緩存數據分散存儲在多臺服務器上的緩存解決方案。

適用場景:高并發讀取、數據共享和協同處理、提供彈性和可擴展性、降低后端請求次數等場景。

常用工具以及用法

  • redis:Redis是一種高性能的鍵值存儲系統,支持豐富的數據類型和靈活的緩存策略??梢允褂肦edis搭建分布式緩存集群,利用其快速的讀寫能力和一致性哈希算法實現數據分片和負載均衡。
  • Memcached:Memcached是一種簡單而快速的分布式內存對象緩存系統,用于減輕數據庫負載和加速動態Web應用程序。它采用分布哈希算法進行數據分片和分布式存儲。
  • Hazelcast:Hazelcast是一個開源的分布式內存數據網格平臺,提供分布式緩存和分布式計算能力。它可以用于構建高吞吐量和高可用性的分布式緩存系統。

4.緩存問題

1)緩存穿透

關鍵詞:強調緩存和數據庫都沒有數據+并發訪問?

緩存穿透是指數據庫和緩存都沒有的數據,每次都要經過緩存去訪問數據庫,大量的請求有可能導致DB宕機。

應對策略

  • 使用布隆過濾器(Bloom Filter):布隆過濾器是一種快速判斷元素是否存在的數據結構,它可以在很小的內存占用下,快速判斷一個元素是否在一個集合中。將所有可能存在的數據哈希到一個足夠大的位數組中,當一個請求過來時,先經過布隆過濾器判斷是否存在于緩存中,如果不存在,則直接返回,避免對數據庫的查詢壓力。
  • 空對象緩存:對于確定不存在的數據,在緩存中也存儲一個空對象,表示該數據不存在。當請求訪問這些不存在的數據時,直接從緩存中返回空對象,避免每次請求都穿透到數據庫層進行查詢。
  • 延遲雙判:當一個查詢請求穿透緩存到達數據庫層后,先在數據庫中進行查詢,如果數據庫也沒有對應的數據,則將這個空結果寫入緩存,并設置一個較短的過期時間。這樣,下次相同的查詢請求就會從緩存中得到空結果,而不會再次穿透到數據庫。
  • 熱點數據預加載:對于一些熱點數據,在系統啟動時或者在緩存過期前提前異步加載到緩存中,確保緩存的熱點數據一直存在,避免被頻繁請求的數據因為緩存過期而導致穿透問題。
  • 限流策略:針對頻繁請求的特定數據,可以設置限流策略,例如使用令牌桶算法或漏桶算法,限制對這些數據的請求頻率,減輕數據庫的壓力。

2)緩存擊穿

關鍵詞:強調單個熱點Key過期+并發訪問

緩存擊穿是指數據庫有,緩存沒有的熱點數據,大量請求訪問這個緩存不存在的數據,最后請求打到DB可能導致DB宕機。

應對策略

  • 設置熱點數據的熱度時間窗口:對于熱點數據,可以設置一個熱度時間窗口,在這個時間窗口內,如果一個數據被頻繁訪問,就將其緩存時間延長,避免頻繁刷新緩存導致緩存擊穿。
  • 使用互斥鎖或分布式鎖:在緩存失效時,只允許一個線程去查詢數據庫,其他線程等待查詢結果??梢允褂没コ怄i或分布式鎖來實現,確保只有一個線程能夠查詢數據庫,其他線程等待結果,避免多個線程同時查詢數據庫造成數據庫壓力過大。
  • 緩存永不過期:對于一些熱點數據,可以將其緩存設置為永不過期,或者設置一個很長的過期時間,這樣即使緩存失效,也有足夠的時間來刷新緩存,避免緩存擊穿。
  • 異步更新緩存:在緩存失效時,可以異步地去更新緩存,而不是同步地去查詢數據庫并刷新緩存。這樣可以減少對數據庫的直接訪問,并且不會阻塞其他請求的響應。
  • 多級緩存架構:使用多級緩存架構,將熱點數據分散到多個緩存節點上,避免單一緩存節點發生故障導致整個緩存層崩潰。當某個緩存節點失效時,可以從其他緩存節點或數據庫中獲取數據。
  • 熔斷機制:當緩存層發生故障或無法正常工作時,可以設置熔斷機制,直接訪問數據庫,保證系統的正常運行。

3)緩存雪崩

關鍵詞:強調批量Key過期+并發訪問?

緩存雪崩指的是在同一時段大量的緩存鍵(key)同時失效,導致大量請求打到數據庫,最后請求打到DB可能導致DB宕機。

應對策略

  • 使用多級緩存架構:將緩存劃分為多個層級,每個層級的緩存設置不同的過期時間。例如,將熱點數據存儲在近期失效的緩存層級,而將非熱點數據存儲在長期失效的緩存層級。這樣即使某一層級的緩存失效,仍然可以從其他層級獲取數據,避免所有請求直接訪問數據庫。
  • 設置緩存數據的隨機過期時間:在設置緩存數據的過期時間時,加上一個隨機值,使得不同的緩存數據在過期時刻不一致。這樣可以避免大量數據同時過期,減輕數據庫負荷。
  • 分布式鎖或互斥鎖:在緩存失效時,使用分布式鎖或互斥鎖來保證只有一個請求可以重新加載緩存。其他請求等待該請求完成后,直接從緩存中獲取數據。這樣可以避免多個請求同時訪問數據庫。
  • 數據預熱:在系統啟動或者非高峰期,提前將熱點數據加載到緩存中,預熱緩存。這樣即使在高并發時,也能夠從緩存中獲取到數據,減輕數據庫的壓力。
  • 緩存限流:當檢測到緩存失效時,可以對請求進行限流處理,限制并發請求的數量。這樣可以避免大量請求同時訪問數據庫,導致數據庫負載過大。
  • 數據庫優化:對于緩存雪崩問題,除了緩存層面的應對策略,還可以從數據庫層面進行優化,如提升數據庫性能、增加數據庫的容量等,以應對大量請求導致的數據庫壓力。

4)緩存一致性

緩存一致性指的是緩存與DB之間的數據一致性,我們需要通過各種手段來防止緩存與DB不一致,我們要保證緩存與DB的數據一致或者數據最終一致。

應對策略

針對緩存一致性問題,可以從不同的層次來應對:

數據庫層:

  • 在數據庫層面,可以使用事務來確保數據的一致性。通過將讀寫操作放在同一個事務中,可以保證數據的更新和查詢是一致的。
  • 使用數據庫的觸發器或者存儲過程,在數據更新的同時,主動觸發緩存的更新操作,確保緩存與數據庫的數據保持一致。

緩存層:

  • 在緩存層面,可以使用緩存更新策略,通過定時任務、異步消息隊列等方式,定期或者在數據更新時異步地更新緩存,保持緩存與數據庫的數據一致性。
  • 使用互斥鎖或者分布式鎖來保證對緩存的讀寫操作的原子性,避免數據沖突。
  • 設置合適的緩存過期時間,避免緩存數據長時間過期而導致數據不一致的問題。

應用層:

  • 在應用層面,可以采用讀寫分離策略,將讀請求和寫請求分發到不同的節點,讀請求直接從緩存中獲取數據,寫請求則更新數據庫并更新緩存,保持數據的一致性。
  • 使用緩存中間件或者緩存組件,提供自動更新緩存的功能,減少手動維護緩存的復雜性。

監控和報警:

  • 建立監控和報警機制,通過監控緩存層和數據庫層的狀態、數據一致性等指標,及時發現異常情況,并觸發報警,以便及時處理問題。

綜合使用以上層次的策略,可以有效地應對緩存一致性問題,保證數據的一致性和系統的穩定性。不同層次的策略可以相互配合,形成一個完善的緩存一致性解決方案。

5)其他

緩存的好處我們非常受益,用戶的每一次請求都伴隨著無數緩存的誕生,但是緩存同時也給我們帶來了不小的挑戰,比如在上面提到的一些疑難課題:緩存穿透、緩存擊穿、緩存雪崩和緩存一致性。

除此之外,我們還會涉及到其他的一些緩存難題,如:緩存傾斜、緩存阻塞、緩存慢查詢、緩存主從一致性問題、緩存高可用、緩存故障發現與故障恢復、集群擴容收縮、大Key熱Key……

6)小結

以上是對瀏覽器緩存、客戶端緩存、CDN緩存、反向代理緩存、本地緩存和分布式緩存的橫向對比,包括介紹、解決方案/工具、優點和缺點以及適用場景的詳細信息。根據具體需求和系統架構,選擇適合的緩存類型和方案,以提高系統性能、減輕服務器負載、改善用戶體驗和保證數據一致性。

四、限流

1.簡介

再強大的系統,也怕流量短事件內集中爆發,就像銀行怕擠兌一樣,所以,高并發另一個必不可少的模塊就是限流。

限流是一種通過控制請求的速率或數量來保護系統免受過載的技術。流控的精髓是限制單位時間內的請求量,最大程度保障系統的可靠性及可用性。

2.作用

限流是在高并發環境下,為了保護系統的穩定性和可用性而引入的一種策略。通過限制并發請求的數量或頻率,可以防止系統被過多的請求壓垮或耗盡資源。

3.限流算法

常見的流控算法包括:固定窗口、滑動窗口、漏桶、令牌桶、滑動日志等算法。

1)固定窗口算法(計數器)

簡介:固定窗口限流算法(Fixed Window Rate Limiting Algorithm)是一種最簡單的限流算法,其原理是在固定時間窗口(單位時間)內限制請求的數量。

原理:

固定窗口是最簡單的流控算法。即,給定時間窗口,維護一個計數器用于統計訪問次數,并實現以下規則:

  • 如果訪問次數小于閾值,則允許訪問,訪問次數+1;
  • 如果訪問次數超出閾值,則限制訪問,訪問次數不增;
  • 如果超過了時間窗口,計數器清零,并重置清零后的首次成功訪問時間為當前時間。

適用場景:

  • 保護后端服務免受大流量沖擊,避免服務崩潰;
  • 對 API 調用進行限制,保證公平使用;
  • 防止惡意用戶對服務進行洪水攻擊。

實現方式

public class FixedWindowRateLimiter {

private static int counter = 0; // 統計請求數

private static long lastAcquireTime = 0L;

private static final long windowUnit = 1000L; // 假設固定時間窗口是1000ms

private static final int threshold = 10; // 窗口閥值是10

public synchronized boolean tryAcquire() {

long currentTime = System.currentTimeMillis(); // 獲取系統當前時間

if (currentTime - lastAcquireTime > windowUnit) { // 檢查是否在時間窗口內

counter = 0; // 計數器清零

lastAcquireTime = currentTime; // 開啟新的時間窗口

}

if (counter < threshold) { // 小于閥值

counter++; // 計數器加1

return true; // 獲取請求成功

}

return false; // 超過閥值,無法獲取請求

}

}

代碼說明:

使用了一個靜態的counter變量來記錄請求數量,lastAcquireTime變量記錄上一次獲取請求的時間戳。windowUnit表示固定時間窗口的長度,threshold則表示在時間窗口內的請求數閥值。

tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:

  • 獲取當前系統時間 currentTime。
  • 檢查當前時間是否距離上一次獲取請求的時間超過了時間窗口長度 windowUnit。如果超過了時間窗口,則將計數器 counter 清零,并更新 lastAcquireTime 為當前時間,表示進入新的時間窗口。
  • 如果計數器 counter 小于閥值 threshold,則將計數器加1,并返回true表示獲取請求成功。
  • 如果計數器已經達到或超過閥值,則返回false表示無法獲取請求。?

優劣分析

優點:

  • 固定窗口算法非常簡單,易于實現和理解。
  • 性能高

缺點:

  • 存在明顯的臨界問題

比如:假設限流閥值為5個請求,單位時間窗口是1s,如果我們在單位時間內的前0.8-1s1-1.2s,分別并發5個請求。雖然都沒有超過閥值,但是如果算0.8-1.2s內的,則并發數高達10,已經超過單位時間1s不超過5閥值的定義了。

2.滑動窗口算法

簡介:

為了解決臨界突變問題,可以引入滑動窗口。即:把大的時間窗口拆分成若干粒度更細的子窗口,每個子窗口獨立統計,按子窗口時間滑動,統一限流。

當滑動窗口的格子周期劃分的越多,那么滑動窗口的滾動就越平滑,限流的統計就會越精確。

原理:

將單位時間周期分為n個小周期,分別記錄每個小周期內接口的訪問次數,并且根據時間滑動刪除過期的小周期。它可以解決固定窗口臨界值的問題。

假設單位時間還是1s,滑動窗口算法把它劃分為5個小周期,也就是滑動窗口(單位時間)被劃分為5個小格子。每格表示0.2s。每過0.2s,時間窗口就會往右滑動一格。然后呢,每個小周期,都有自己獨立的計數器,如果請求是0.83s到達的,0.8~1.0s對應的計數器就會加1。

假設我們1s內的限流閥值還是5個請求,0.8~1.0s內(比如0.9s的時候)來了5個請求,落在黃色格子里。?

時間過了1.0s這個點之后,又來5個請求,落在紫色格子里。如果是固定窗口算法,是不會被限流的,但是滑動窗口的話,每過一個小周期,它會右移一個小格。過了1.0s這個點后,會右移一小格,當前的單位時間段是0.2~1.2s,這個區域的請求已經超過限定的5了,已觸發限流啦,實際上,紫色格子的請求都被拒絕。

實現方式

import java.util.LinkedList;

import java.util.Queue;

public class SlidingWindowRateLimiter {

private Queue<Long> timestamps; // 存儲請求的時間戳隊列

private int windowsize; // 窗口大小,即時間窗口內允許的請求數量

private long windowDuration; // 窗口持續時間,單位:毫秒

public SlidingWindowRateLimiter(int windowSize, long windowDuration) {

this.windowSize = windowSize;

this.windowDuration = windowDuration;

this.timestamps = new LinkedList<>();

}

public synchronized boolean tryAcquire() {

long currentTime = System.currentTimeMillis(); // 獲取當前時間戳

// 刪除超過窗口持續時間的時間戳

while (!timestamps.isEmpty() && currentTime - timestamps.peek() > windowDuration) {

timestamps.poll();

}

if (timestamps.size() < windowSize) { // 判斷當前窗口內請求數是否小于窗口大小

timestamps.offer(currentTime); // 將當前時間戳加入隊列

return true; // 獲取請求成功

}

return false; // 超過窗口大小,無法獲取請求

}

}

代碼解讀

在以上代碼中,使用了一個Queue(隊列)來存儲請求的時間戳。構造函數中傳入窗口大小 windowSize 和窗口持續時間 windowDuration。

tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:

  • 獲取當前系統時間戳 currentTime。
  • 從隊列中刪除超過窗口持續時間的時間戳,確保隊列中只保留窗口內的時間戳。
  • 判斷當前窗口內請求數是否小于窗口大小 windowSize。
  • 如果小于窗口大小,將當前時間戳加入隊列,并返回true表示獲取請求成功。
  • 如果已經達到或超過窗口大小,表示請求數已滿,返回false表示無法獲取請求。

使用這個滑動窗口限流算法,可以限制在一定時間窗口內的請求頻率,超過窗口大小的請求會被限制。您可以根據實際需求和業務場景進行調整和使用。?

適用場景:同固定窗口的場景,且對流量限制要求較高的場景,需要更好地應對突發流量。

優劣分析

優勢:

  • 簡單易懂
  • 精度高(通過調整時間窗口的大小來實現不同的限流效果)
  • 可擴展性強(可以非常容易地與其他限流算法結合使用)

劣勢:

  • 突發流量無法處理(無法應對短時間內的大量請求,但是一旦到達限流后,請求都會直接暴力被拒絕。這樣我們會損失一部分請求,這其實對于產品來說,并不太友好),需要合理調整時間窗口大小。

3.漏桶算法

簡介:基于(出口)流速來做流控。在網絡通信中常用于流量整形,可以很好地解決平滑度問題。

特點:

  • 可以以任意速率流入水滴到漏桶(流入請求)
  • 漏桶具有固定容量,出水速率是固定常量(流出請求)
  • 如果流入水滴超出了桶的容量,則流入的水滴溢出(新請求被拒絕)

原理

  • 思想:將數據包看作是水滴,漏桶看作是一個固定容量的水桶,數據包像水滴一樣從桶的頂部流入桶中,并通過桶底的一個小孔以一定的速度流出,從而限制了數據包的流量
  • 工作原理:對于每個到來的數據包,都將其加入到漏桶中,并檢查漏桶中當前的水量是否超過了漏桶的容量。如果超過了容量,就將多余的數據包丟棄。如果漏桶中還有水,就以一定的速率從桶底輸出數據包,保證輸出的速率不超過預設的速率,從而達到限流的目的。

代碼實現

public class LeakyBucketRateLimiter {

private long capacity; // 漏桶容量,即最大允許的請求數量

private long rate; // 漏水速率,即每秒允許通過的請求數量

private long water; // 漏桶當前水量

private long lastTime; // 上一次請求通過的時間戳

public LeakyBucketRateLimiter(long capacity, long rate) {

this.capacity = capacity;

this.rate = rate;

this.water = 0;

this.lastTime = System.currentTimeMillis();

}

public synchronized boolean tryAcquire() {

long now = System.currentTimeMillis();

long elapsedTime = now - lastTime;

// 計算漏桶中的水量

water = Math.max(0, water - elapsedTime * rate / 1000);

if (water < capacity) { // 判斷漏桶中的水量是否小于容量

water++; // 漏桶中的水量加1

lastTime = now; // 更新上一次請求通過的時間戳

return true; // 獲取請求成功

}

return false; // 漏桶已滿,無法獲取請求

}

}

代碼解讀

在以上代碼中,capacity表示漏桶的容量,即最大允許的請求數量;rate表示漏水速率,即每秒允許通過的請求數量。water表示漏桶中當前的水量,lastTime表示上一次請求通過的時間戳。

tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:

  • 獲取當前系統時間戳 now。
  • 計算從上一次請求通過到當前的時間間隔 elapsedTime。
  • 根據漏水速率和時間間隔,計算漏桶中的水量。
  • 判斷漏桶中的水量是否小于容量。
  • 如果小于容量,漏桶中的水量加1,更新上一次請求通過的時間戳,并返回true表示獲取請求成功。
  • 如果已經達到或超過容量,漏桶已滿,返回false表示無法獲取請求。?

適用場景

一般用于保護第三方的系統,比如自身的系統需要調用第三方的接口,為了保護第三方的系統不被自身的調用打垮,便可以通過漏斗算法進行限流,保證自身的流量平穩的打到第三方的接口上。

優劣分析

優勢:

  • 可以平滑限制請求的處理速度,避免瞬間請求過多導致系統崩潰或者雪崩。
  • 可以控制請求的處理速度,使得系統可以適應不同的流量需求,避免過載或者過度閑置。
  • 可以通過調整桶的大小和漏出速率來滿足不同的限流需求,可以靈活地適應不同的場景。

劣勢:

  • 需要對請求進行緩存,會增加服務器的內存消耗。
  • 對于流量波動比較大的場景,需要較為靈活的參數配置才能達到較好的效果。
  • 但是面對突發流量的時候,漏桶算法還是循規蹈矩地處理請求,這不是我們想看到的啦。流量變突發時,我們肯定希望系統盡量快點處理請求,提升用戶體驗嘛。

4.令牌桶算法

簡介:基于(入口)流速來做流控的一種限流算法。

原理:該算法維護一個固定容量的令牌桶,每秒鐘會向令牌桶中放入一定數量的令牌。當有請求到來時,如果令牌桶中有足夠的令牌,則請求被允許通過并從令牌桶中消耗一個令牌,否則請求被拒絕。

實現方式

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.ScheduledThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

public class TokenBucketRateLimiter {

private long capacity; // 令牌桶容量,即最大允許的請求數量

private long rate; // 令牌產生速率,即每秒產生的令牌數量

private long tokens; // 當前令牌數量

private ScheduledExecutorService scheduler; // 調度器

public TokenBucketRateLimiter(long capacity, long rate) {

this.capacity = capacity;

this.rate = rate;

this.tokens = capacity;

this.scheduler = new ScheduledThreadPoolExecutor(1);

scheduleRefill(); // 啟動令牌補充任務

}

private void scheduleRefill() {

scheduler.scheduleAtFixedRate(() -> {

synchronized (this) {

tokens = Math.min(capacity, tokens + rate); // 補充令牌,但不超過容量

}

}, 1, 1, TimeUnit.SECONDS); // 每秒產生一次令牌

}

public synchronized boolean tryAcquire() {

if (tokens > 0) { // 判斷令牌數量是否大于0

tokens--; // 消耗一個令牌

return true; // 獲取請求成功

}

return false; // 令牌不足,無法獲取請求

}

}

代碼解讀

capacity表示令牌桶的容量,即最大允許的請求數量;rate表示令牌產生速率,即每秒產生的令牌數量。tokens表示當前令牌數量,scheduler是用于調度令牌補充任務的線程池。

在構造方法中,初始化令牌桶的容量和當前令牌數量,并啟動令牌補充任務scheduleRefill()。

scheduleRefill()方法使用調度器定期執行令牌補充任務,每秒補充一次令牌。在補充任務中,通過加鎖的方式更新令牌數量,確保線程安全。補充的令牌數量為當前令牌數量加上產生速率,但不超過令牌桶的容量。

tryAcquire()方法使用synchronized關鍵字來實現線程安全,在方法中進行以下操作:

判斷令牌數量是否大于0。

  • 如果令牌數量大于0,表示令牌足夠,消耗一個令牌,并返回true表示獲取請求成功。
  • 如果令牌數量為0,表示令牌不足,返回false表示無法獲取請求。?

GuavaRateLimiter限流組件,就是基于令牌桶算法實現的。

適用場景

一般用于保護自身的系統,對調用者進行限流,保護自身的系統不被突發的流量打垮。如果自身的系統實際的處理能力強于配置的流量限制時,可以允許一定程度的流量突發,使得實際的處理速率高于配置的速率,充分利用系統資源。

優劣分析

優勢:

  • 穩定性高:令牌桶算法可以控制請求的處理速度,可以使系統的負載變得穩定。
  • 精度高:令牌桶算法可以根據實際情況動態調整生成令牌的速率,可以實現較高精度的限流。
  • 彈性好:令牌桶算法可以處理突發流量,可以在短時間內提供更多的處理能力,以處理突發流量。

劣勢:

  • 實現復雜:相對于固定窗口算法等其他限流算法,令牌桶算法的實現較為復雜。對短時請求難以處理:在短時間內有大量請求到來時,可能會導致令牌桶中的令牌被快速消耗完,從而限流。這種情況下,可以考慮使用漏桶算法。
  • 時間精度要求高:令牌桶算法需要在固定的時間間隔內生成令牌,因此要求時間精度較高,如果系統時間不準確,可能會導致限流效果不理想。

5.滑動日志算法(比較冷門)

簡介:滑動日志限速算法需要記錄請求的時間戳,通常使用有序集合來存儲,我們可以在單個有序集合中跟蹤用戶在一個時間段內所有的請求。

原理:

滑動日志算法可以用于實現限流功能,即控制系統在單位時間內處理請求的數量,以保護系統免受過載的影響。以下是滑動日志算法用于限流的原理:

  • 劃分時間窗口:將時間劃分為固定的時間窗口,例如每秒、每分鐘或每小時等。
  • 維護滑動窗口:使用一個滑動窗口來記錄每個時間窗口內的請求次數。這個滑動窗口可以是一個固定長度的隊列或數組。
  • 請求計數:當一個請求到達時,將其計數加一并放入當前時間窗口中。
  • 滑動:隨著時間的流逝,滑動窗口會根據當前時間窗口的長度,移除最舊的請求計數,并將新的請求計數添加到最新的時間窗口中。
  • 限流判斷:在每個時間窗口結束時,統計滑動窗口中的請求計數總和,并與預設的閾值進行比較。如果總請求數超過閾值,則觸發限流處理。
  • 限流處理:一旦觸發限流,可以采取不同的處理策略,如拒絕請求、延遲處理、返回錯誤信息等。具體的限流策略可以根據實際情況進行選擇。

通過滑動日志算法進行限流,可以實現對單位時間內的請求進行精確控制。它基于實時統計的方式,能夠動態地適應請求流量的變化,并且在內存使用上比較高效。同時,通過調整時間窗口的長度和閾值的設置,可以靈活地控制限流的精度和靈敏度。

實現方式

import java.util.LinkedList;

import java.util.List;

public class SlidingLogRateLimiter {

private int requests; // 請求總數

private List<Long> timestamps; // 存儲請求的時間戳列表

private long windowDuration; // 窗口持續時間,單位:毫秒

private int threshold; // 窗口內的請求數閥值

public SlidingLogRateLimiter(int threshold, long windowDuration) {

this.requests = 0;

this.timestamps = new LinkedList<>();

this.windowDuration = windowDuration;

this.threshold = threshold;

}

public synchronized boolean tryAcquire() {

long currentTime = System.currentTimeMillis(); // 獲取當前時間戳

// 刪除超過窗口持續時間的時間戳

while (!timestamps.isEmpty() && currentTime - timestamps.get(0) > windowDuration) {

timestamps.remove(0);

requests--;

}

if (requests < threshold) { // 判斷當前窗口內請求數是否小于閥值

timestamps.add(currentTime); // 將當前時間戳添加到列表

requests++; // 請求總數增加

return true; // 獲取請求成功

}

return false; // 超過閥值,無法獲取請求

}

}

代碼解讀

在以上代碼中,requests表示請求總數,timestamps用于存儲請求的時間戳列表,windowDuration表示窗口持續時間,threshold表示窗口內的請求數閥值。

在構造函數中傳入窗口內的請求數閥值和窗口持續時間。

tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:

  • 獲取當前系統時間戳 currentTime。
  • 刪除超過窗口持續時間的時間戳,同時更新請求總數。
  • 判斷當前窗口內請求數是否小于閥值。
  • 如果小于閥值,將當前時間戳添加到列表,請求總數增加,并返回true表示獲取請求成功。
  • 如果已經達到或超過閥值,表示請求數已滿,返回false表示無法獲取請求。

使用這個滑動日志限流算法,可以限制在一定時間窗口內的請求頻率,超過閥值的請求會被限制。您可以根據實際需求和業務場景進行調整和使用。

適用場景:實時性要求高,且需要精確控制請求速率的高級限流場景。

優劣分析

優勢:

  • 滑動日志能夠避免突發流量,實現較為精準的限流;
  • 更加靈活,能夠支持更加復雜的限流策略 如多級限流,每分鐘不超過100次,每小時不超過300次,每天不超過1000次,我們只需要保存最近24小時所有的請求日志即可實現。

劣勢:

  • 占用存儲空間要高于其他限流算法。

6.幾種算法小結

7.常用工具

1)RateLimiter(單機)

簡介:基于令牌桶算法實現的一個多線程限流器,它可以將請求均勻的進行處理,當然他并不是一個分布式限流器,只是對單機進行限流。它可以應用在定時拉取接口數。通過aop、filter、Interceptor 等都可以達到限流效果。

用法

以下是一個基本的 RateLimiter 用法示例:

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

public class RateLimiterDemo {

public static void main(String[] args) {

// 創建一個每秒允許2個請求的RateLimiter

RateLimiter rateLimiter = RateLimiter.create(2.0);

while (true) {

// 請求RateLimiter一個令牌

rateLimiter.acquire();

// 執行操作

doSomeLimitedOperation();

}

}

private static void doSomeLimitedOperation() {

// 模擬一些操作

System.out.println("Operation executed at: " + System.currentTimeMillis());

}

}

在這個例子中,RateLimiter.create(2.0) 創建了一個每秒鐘只允許2個操作的限速器。rateLimiter.acquire() 方法會阻塞當前線程直到獲取到許可,確保調用 doSomeLimitedOperation() 操作的頻率不會超過限制。

RateLimiter 還提供了其他的方法,例如tryAcquire(),它會嘗試獲取許可而不會阻塞,立即返回獲取成功或失敗的結果。還可以設置等待時間上限,比如 tryAcquire(long timeout, TimeUnit unit) 可以設置最大等待時間。

Guava的RateLimiter非常靈活,它支持平滑突發限制(SmoothBursty)和平滑預熱限制(SmoothWarmingUp)等多種模式,可以根據特定的應用場景來選擇合適的限流策略。

2)sentinel(單機或者分布式)

簡介:Sentinel是阿里巴巴開源的一款面向分布式系統的流量控制和熔斷降級組件。它提供了實時的流量控制、熔斷降級、系統負載保護和實時監控等功能,可以幫助開發者保護系統的穩定性和可靠性。

單機模式

  • DefaultController:是一個非常典型的滑動窗口計數器算法實現,將當前統計的qps和請求進來的qps進行求和,小于限流值則通過,大于則計算一個等待時間,稍后再試;
  • ThrottlingController:漏斗算法的實現,實現思路已經在源碼片段中加了備注;
  • WarmUpController:實現參考了Guava的帶預熱的RateLimiter,區別是Guava側重于請求間隔,類似前面提到的令牌桶,而Sentinel更關注于請求數,和令牌桶算法有點類似;
  • WarmUpRateLimiterController:低水位使用預熱算法,高水位使用滑動窗口計數器算法排隊。

集群模式

Sentinel 集群限流服務端有兩種啟動方式:

  • 嵌入模式(Embedded)適合應用級別的限流,部署簡單,但對應用性能有影響;
  • 獨立模式(Alone)適合全局限流,需要獨立部署。

用法

Sentinel的用法主要包括以下幾個方面:

  • 引入依賴:在項目中引入Sentinel的相關依賴,可以使用Maven或Gradle進行依賴管理。例如,在Maven項目的pom.xml文件中添加以下依賴:

<dependency>

<groupId>com.alibaba.csp</groupId>

<artifactId>sentinel-core</artifactId>

<version>1.8.2</version>

</dependency>

  • 配置規則:根據實際需求,配置Sentinel的流量控制規則、熔斷降級規則等。可以通過編程方式或配置文件方式進行規則的配置。例如,可以在啟動類中使用注解方式配置流量控制規則:
  •  

@SentinelResource(value = "demo", blockHandler = "handleBlock")

public String demo() {

// ...

}

  • 啟動Agent:在應用啟動時,啟動Sentinel的Agent,開啟對系統的流量控制和熔斷降級功能的保護??梢酝ㄟ^命令行啟動Agent,或者在代碼中進行啟動。例如,在Spring Boot的啟動類中添加如下代碼:
  •  

public static void main(String[] args) {

System.setProperty("csp.sentinel.dashboard.server", "localhost:8080"); // 設置控制臺地址

System.setProperty("project.name", "your-project-name"); // 設置應用名稱

com.alibaba.csp.sentinel.init.InitExecutor.doInit();

SpringApplication.run(YourApplication.class, args);

}

  • 監控和管理:使用Sentinel的控制臺進行實時監控、配置管理等操作??梢酝ㄟ^瀏覽器訪問Sentinel的控制臺界面,查看系統的運行情況和流量控制情況。通過控制臺,可以對規則進行動態修改,查看監控數據和告警信息。

3)Nginx(分布式)

簡介:Nginx從網關這一層面考慮,可以作為最前置的網關,抵擋大部分的網絡流量,因此使用Nginx進行限流也是一個很好的選擇,在Nginx中,也提供了常用的基于限流相關的策略配置。

用法

Nginx 提供了兩種限流方法:一種是控制速率,另一種是控制并發連接數。

  • 控制速率

我們需要使用 limit_req_zone 用來限制單位時間內的請求數,即速率限制,

因為Nginx的限流統計是基于毫秒的,我們設置的速度是 2r/s,轉換一下就是500毫秒內單個IP只允許通過1個請求,從501ms開始才允許通過第2個請求。

  • 控制速率優化版

上面的速率控制雖然很精準但是在生產環境未免太苛刻了,實際情況下我們應該控制一個IP單位總時間內的總訪問次數,而不是像上面那樣精確到毫秒,我們可以使用 burst 關鍵字開啟此設置。

burst=4意思是每個IP最多允許4個突發請求

  • 控制并發數

利用 limit_conn_zone 和 limit_conn 兩個指令即可控制并發數

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

注意:只有當 request header 被后端處理后,這個連接才進行計數。?

五、降級

1.簡介

降級是在高并發或異常情況下舍棄非關鍵業務或簡化處理的一種技術手段。

按類型可分為有感降級,無感降級。

  • 有感降級:主要是通過一定的監控感知到異常出現或即將出現,對調用服務進行快速失敗返回或者進行切換,在指標回正的時候恢復服務調用,這個也可以稱為熔斷。
  • 無感降級:系統不作感知,在調用服務出現異常則自動忽略,進行空返回或無操作。降級的本質為作為服務調用方去規避提供方帶來的風險。

2.原理

在限流中,服務調用方為每一個調用的服務維護一個有限狀態機,在這個狀態機會有三種狀態:關閉(調用遠程服務)、半打開(嘗試調用遠程服務)和打開(返回錯誤)。這三種狀態之間切換的過程如下:?

當調用失敗的次數累積到一定的閾值時,熔斷機制從關閉態切換到打開態。一般在實現時,如果調用成功一次,就會重置調用失敗次數。

當熔斷處于打開狀態時,我們會啟動一個計時器,當計時器超時后,狀態切換到半打開態。也可以通過設置一個定時器,定期的探測服務是否恢復。

當熔斷處于半打開狀態時,請求可以達到后端服務,如果累計一定的成功次數后,狀態切換到關閉態;如果出現調用失敗的情況,則切換到打開態。

3.常用工具

  • 降級開源組件:sentinel和Hystrix(不展開)
  • 手動降級:可采用系統配置開關來控制

4.其他

1)熔斷

簡介:熔斷在程序中,表示“斷開”的意思。如發生了某事件,程序為了整體的穩定性,所以暫時(斷開)停止服務一段時間,以保證程序可用時再被使用。

熔斷和降級的區別

  • 概念不同

熔斷程序為了整體的穩定性,所以暫時(斷開)停止服務一段時間;降級(Degradation)降低級別的意思,它是指程序在出現問題時,仍能保證有限功能可用的一種機制;

  • 觸發條件不同

不同框架的熔斷和降級的觸發條件是不同,以Hystrix為例:

  • Hystrix 熔斷觸發條件

默認情況 hystrix 如果檢測到 10 秒內請求的失敗率超過 50%,就觸發熔斷機制。之后每隔 5 秒重新嘗試請求微服務,如果微服務不能響應,繼續走熔斷機制。如果微服務可達,則關閉熔斷機制,恢復正常請求。

  • Hystrix 降級觸發條件

默認情況下,hystrix 在以下 4 種條件下都會觸發降級機制:

  • 方法拋出 HystrixBadRequestException
  • 方法調用超時
  • 熔斷器開啟攔截調用
  • 線程池或隊列或信號量已滿
  • 歸屬關系不同

熔斷時可能會調用降級機制,而降級時通常不會調用熔斷機制。因為熔斷是從全局出發,為了保證系統穩定性而停用服務,而降級是退而求其次,提供一種保底的解決方案,所以它們的歸屬關系是不同(熔斷 > 降級)。

小結

  • 緩存、限流和降級是應對高并發的三大利器,能夠提升系統性能、保護資源和保證核心功能。
  • 組合使用緩存、限流和降級策略,根據具體場景靈活調整和優化。
  • 在高并發環境下,綜合使用三大利器是應對挑戰的有效策略。

作者丨駱天

來源丨公眾號:阿里云開發者(ID:ali_tech)

分享到:
標簽:高并發
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 52000

    網站

  • 12

    小程序

  • 1037587

    文章

  • 756

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定