暗刺,高并发五个利器
文章目錄
- 一、前言
- 二、緩存
- 2.1 緩存本質 + 緩存分類 + 緩存三大特征 + 三種淘汰算法 + 根據業務場景設計過期時間
- 2.1.1 緩存本質
- 2.1.2 緩存分類(本地緩存+分布式緩存=分級緩存)
- 2.1.3 緩存三大特征(命中率+最大空間+淘汰算法)
- 2.1.4 三種淘汰算法(FIFO LFU LRU)
- 2.1.5 根據業務場景設計過期時間
- 2.2 本地緩存(進程內緩存)
- 2.3 分布式緩存
- 2.4 緩存五問題:緩存雪崩 + 緩存穿透 + 緩存擊穿 + 緩存更新 + 數據不一致
- 2.4.1 緩存雪崩定義what + 六個解決方式how
- 2.4.2 緩存穿透定義what + 四個解決方式how
- 2.4.3 緩存擊穿定義what + 四個解決方式how
- 2.4.4 緩存更新問題(緩存數據源:DB和遠程服務 + 四種緩存更新方式 )
- 2.4.5 緩存數據不一致問題
- 三、限流(服務限流,表示處理的對象是服務,包括限流四規則 + 限流四實現)
- 3.1 限流定義
- 3.2 限流四規則(理論)
- 3.3 限流四實現
- 四、降級(即服務降級,表示處理的對象是服務,這里指主動降級)
- 4.1 服務降級定義
- 4.2 服務降級五種方式
- 五、從服務雪崩到服務熔斷(即服務熔斷,表示處理的對象是服務,就是被動降級)
- 5.1 服務雪崩定義
- 5.2 服務雪崩產生的原因
- 5.3 熔斷機制是應對雪崩效應的一種微服務鏈路保護機制
- 六、面試金手指
- 6.1 限流四規則 + 限流四實現
- 6.2 五種措施小結
- 七、小結
一、前言
二、緩存
2.1 緩存本質 + 緩存分類 + 緩存三大特征 + 三種淘汰算法 + 根據業務場景設計過期時間
2.1.1 緩存本質
緩存本質:一個數據模型對象。
緩存作用:解決 軟件響應速度要求快 和 數據庫中查找速度慢 的問題。
2.1.2 緩存分類(本地緩存+分布式緩存=分級緩存)
緩存分為本地緩存和分布式緩存兩種(分級緩存就是應用緩存+分布式緩存):
分布式緩存(即進程間緩存)如redis、memcached等,
本地緩存(即進程內緩存)如ehcache、GuavaCache、Caffeine等。
2.1.3 緩存三大特征(命中率+最大空間+淘汰算法)
第一特征,命中率
定義:命中率=命中數/(命中數+沒有命中數)當某個請求能夠通過訪問緩存而得到響應時,稱為緩存命中。緩存命中率越高,緩存的利用率也就越高。
第二特征,最大空間
定義:最大空間表示緩存中可以容納最大元素的數量。當緩存存放的數據超過最大空間時,就需要根據淘汰算法來淘汰部分數據存放新到達的數據。
第三特征,淘汰算法
定義:緩存的存儲空間有限制,當緩存空間被用滿時,要求保證在穩定服務的同時有效提升命中率,這就由緩存淘汰算法來處理,設計適合自身數據特征的淘汰算法能夠有效提升緩存命中率。
2.1.4 三種淘汰算法(FIFO LFU LRU)
FIFO(first in first out) 先進先出
定義:最先進入緩存的數據在緩存空間不夠的情況下(超出最大元素限制)會被優先被清除掉,以騰出新的空間接受新的數據(定義2:不管是本地緩存還是分布式緩存,為了保證較高性能,都是使用內存來保存數據,但是,由于成本和內存限制,當存儲的數據超過緩存容量時,需要對緩存的數據進行剔除)。
比較的對象:策略算法主要比較緩存元素的創建時間。
業務場景:適用于保證高頻數據有效性場景,優先保障最新數據可用。
LFU(less frequently used)「最少使用」
定義:無論是否過期,僅根據元素的被使用次數判斷,清除使用次數較少的元素釋放空間。
比較的對象:策略算法主要比較元素的hitCount(命中次數)。
業務場景:適用于保證高頻數據有效性場景。
LRU(least recently used)「最近最少使用」
定義:無論是否過期,僅根據元素最后一次被使用的時間戳,清除最遠使用時間戳的元素釋放空間。
比較的對象:策略算法主要比較元素最近一次被get使用時間。
業務場景:比較適用于熱點數據場景,優先保證熱點數據的有效性。
附加:面試考察淘汰算法,手寫一個LRU算法?
回答:繼承LinkedHashMap,因為它里面有一個LRU算法,
回答1:
解釋1:繼承LinkedHashMap得到淘汰算法,內部就是就是一個雙向鏈表,true表示按訪問順序排序
解釋2:該淘汰算法的閾值是100,當前容量size()大于100,返回為true,開始執行LRU策略淘汰對象:將最近最少未使用的 TimeoutInfoHolder 對象剔除掉。
回答2:
解釋1:繼承LinkedHashMap得到淘汰算法,內部就是就是一個雙向鏈表,true表示按訪問順序排序
解釋2:該淘汰算法的閾值是100,當前容量size()大于100,開始淘汰算法
2.1.5 根據業務場景設計過期時間
關于緩存:業務驅動技術,如何設置過期時間,以一個飛機票訂單為例
比如實際工作中我們對于訂單詳情的一個緩存。()我們可能會根據訂單的狀態來來構建緩存。
第一情況,對于已出行、或者已經取消的訂單我們基本上是不會去管的(因為訂單狀態已經終止了,不會再有對于訂單狀態的寫請求),
(1)對于訂單讀操作:實時性要求沒那么高,從緩存中拿
(2)對于訂單寫操作:已經不可修改,沒有寫操作
所以,對于這種訂單我們設置的過期時間是不是就可以久一點,比如7天或者30天。
第二種情況,對于未出行即將起飛的訂單,這時候顧客是不是就會頻繁的去刷新訂單看看,看看有沒有晚點什么的,或者登機口是在哪。
(1)對于讀操作:實時性要求沒那么高,從緩存中拿
(2)對于訂單寫操作,需要更改訂單的狀態的(比如退票、改簽),可以直接不走緩存,直接查詢并修改數據庫。
一般來說,讀多寫少。
所以,對于這種實時性要求比較高的訂單我們過期時間還是要設置的比較短的。
2.2 本地緩存(進程內緩存)
本地緩存定義:應用和緩存都在同一個進程里面,
本地緩存優點:獲取緩存數據的時候純內存操作,沒有額外的網絡開銷,速度非常快。
本地緩存缺點:
本地緩存與業務系統耦合在一起,應用之間無法直接共享緩存的內容。需要每個應用節點單獨的維護自己的緩存。每個節點都需要一份一樣的緩存,對服務器內存造成一種浪費。本地緩存機器重啟、或者宕機都會丟失。
本地緩存應用:本地緩存適用于緩存一些應用中基本不會變化的數據,比如(國家、省份、城市等),一般可以在應用啟動的時候,把需要的數據加載到系統中。
本地緩存三種更新緩存的方式
更新緩存1-定時變量更新(實時性不高):在應用中起一個定時任務(「ScheduledExecutorService」、「TimerTask」等),讓它每隔多久去加載變更(數據變更之后可以修改數據庫最后修改的時間,每次查詢變更數據的時候都可以根據這個最后變更時間加上半小時大于當前時間的數據)的數據重新到緩存里面來。
更新緩存2-定時全量更新(實時性不高):如果覺得這個定時變量更新比較最后修改時間戳麻煩,可以使用全量更新(就跟項目啟動加載數據一樣)。這種方式的話,對數據更新可能會有點延遲。可能這臺機器看到的是更新后的數據,那臺機器看到的數據還是老的(機器發布時間可能不一樣)。
無論是定時變量更新還是定時變量更新,實時性都不高,所以有了廣播訂閱mq消息更新。
更新緩存3-廣播訂閱mq消息(實時性高):如果對實時性有要求的話,使用廣播訂閱mq消息更新。一旦有數據更新mq會把更新數據推送到每一臺機器,實時性好,但是實現起來較為復雜。
小結:本地緩存實時性排名:廣播訂閱mq消息更新>定時變量更新>定時全量更新。
本地緩存舉例
常見本地緩存有以下幾種實現方式:從上述表格我們看出性能最佳的是Caffeine。實際開發中,關于這個本地緩存作為整個多級緩存系統的一部分,里面提供了豐富的api,以及各種各樣的淘汰算法。
2.3 分布式緩存
分布式緩存定義:與應用分離的緩存組件或服務,其最大的優點是自身就是一個獨立的應用,與本地應用隔離,多個應用可直接的共享緩存。
分布式緩存舉例:常見的分布式緩存有redis、MemCache等。
分布式緩存應用(分布式系統架構):
在高并發的環境下,比如春節搶票大戰,一到放票的時間節點,分分鐘大量用戶以及黃牛的各種搶票軟件流量進入12306,這時候如果每個用戶的訪問都去數據庫實時查詢票的庫存,大量讀的請求涌入到數據庫,瞬間Db就會被打爆,cpu直接上升100%,服務馬上就要宕機或者假死。即使進行了分庫分表也是無法避免的。為了減輕db的壓力以及提高系統的響應速度。一般都會在數據庫前面加上一層緩存,甚至可能還會有多級緩存。
| 定義 | 對進程的內存中進行緩存,如JVM堆中,如LRUMap、Ehcache | |
| 優點 | 完全基于內存訪問,沒有遠程交互開銷,性能最好 | 不受限于單機容量,具有良好的水平擴展能力,對較大數據量的場景也能應付自如 |
| 缺點 | 受限于單機容量,一般緩存較小且無法擴展 | 需要進行遠程請求,性能不如本地緩存 |
金手指:多級緩存(本地緩存+分布式緩存)
為了平衡本地緩存和分布式緩存,實際業務中一般采用多級緩存,
本地緩存中只保存訪問頻率最高的部分熱點數據,
分布式緩沖中保存其他的熱點數據。
在目前的一線大廠中,這也是最常用的緩存方案,單考單一的緩存方案往往難以撐住很多高并發的場景。
2.4 緩存五問題:緩存雪崩 + 緩存穿透 + 緩存擊穿 + 緩存更新 + 數據不一致
三者區別:
緩存雪崩:緩存集體失效,這是后端人員的自己的問題,與網絡攻擊無關;
緩存穿透:無效id數據,id為負數或id非常大
緩存擊穿:熱點key,有效id數據
2.4.1 緩存雪崩定義what + 六個解決方式how
緩存雪崩
定義:指大量緩存同一時間段集體失效,或者緩存整體不能提供服務,導致大量的請求全部到達數據庫 對數據CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。因此而形成的一系列連鎖反應造成整個系統奔潰。
圖:
解決方式:
第一,橫向加數量,保證緩存的高可用,使用主從模式和集群模式來盡量保證緩存服務的高可用
優點:使用redis的集群模式,即使個別redis節點下線,緩存還是可以用。一般稍微大點的公司還可能會在多個機房部署Redis。這樣即使某個機房突然停電,或者光纖又被挖斷了,這時候緩存還是可以使用。
Redis集群部署,將熱點數據均勻分布在不同的Redis庫中也能避免全部失效的問題,單個服務都是對應的單個Redis分片,是為了方便數據的管理,但是也同樣有了可能會失效這樣的弊端,失效時間隨機是個好策略。
第二,縱向加數量,使用多級緩存(本地緩存 + 分布式緩存,推薦方式)。
優點:不同級別緩存時間過時時間不一樣,即使某個級別緩存過期了,還有其他緩存級別 兜底。比如我們Redis緩存過期了,我們還有本地緩存。這樣的話即使沒有命中redis,有可能會命中本地緩存。
第三,過期時間,緩存永不過期。
優點:緩存永不過期,就不會發生緩存雪崩。
缺點:會浪費更多的存儲空間,一般應該也不會推薦這種做法。
應用:電商首頁或特別熱門的頁面,理由:電商首頁或特別熱門的頁面,訪問量太大了,一定不能緩存失效,即使加一點存儲空間也可以原諒。
第四,過期時間,使用隨機過期時間(推薦方式)
優點:為每一個key都合理的設計一個過期時間(金手指:在緩存時使用固定時間加上一個小的隨機數),這樣可以避免大量的熱點key在同一時刻集體失效;、
setRedis(Key,value,time + Math.random() * 10000);
第五,過期時間,異步重建緩存。
優點:緩存永不過期,就不會發生緩存雪崩。
缺點:需要維護每個key的過期時間,定時去輪詢這些key的過期時間。例如一個key的value設置的過期時間是30min,那我們可以為這個key設置它自己的一個過期時間為20min。所以當這個key到了20min的時候我們就可以重新去構建這個key的緩存,同時也更新這個key的一個過期時間。
第六,兜底,使用快速失敗的hystrix熔斷策略,減少 DB 瞬間壓力
使用快速失敗的hystrix熔斷策略,減少 DB 瞬間壓力
【緩存雪崩業務場景1】定時任務導致到緩存雪崩
目前電商首頁以及熱點數據都會去做緩存 ,一般緩存都是定時任務去刷新,或者是查不到之后去更新的,定時任務刷新就有一個問題。
舉個簡單的例子:如果所有首頁的Key失效時間都是12小時,中午12點刷新的,我零點有個秒殺活動大量用戶涌入,假設當時每秒 6000 個請求,本來緩存在可以扛住每秒 5000 個請求,但是緩存當時所有的Key都失效了。此時 1 秒 6000 個請求全部落數據庫,數據庫必然扛不住,它會報一下警,真實情況可能DBA都沒反應過來就直接掛了。此時,如果沒用什么特別的方案來處理這個故障,DBA 很著急,重啟數據庫,但是數據庫立馬又被新的流量給打死了。這就是我理解的緩存雪崩。
即使實在DB層面,也會采取措施,不允許這么大的QPS直接打DB去,比如分庫分表,大表分表可能還還算能頂,但是跟用了Redis的差距還是很大
同一時間大面積失效,那一瞬間Redis跟沒有一樣,那這個數量級別的請求直接打到數據庫幾乎是災難性的,你想想如果打掛的是一個用戶服務的庫,那其他依賴他的庫所有的接口幾乎都會報錯,如果沒做熔斷等策略基本上就是瞬間掛一片的節奏。
解決方式:電商首頁或特別熱門的頁面設置為緩存永不失效,理由:電商首頁或特別熱門的頁面,訪問量太大了,一定不能緩存失效,即使加一點存儲空間也可以原諒。
【緩存雪崩業務場景2】如果有大量的key需要設置同一時間過期,一般需要注意什么?
問題:如果大量的key過期時間設置的過于集中,到過期的那個時間點,Redis可能會出現短暫的卡頓現象,嚴重的話會出現緩存雪崩。
解決:我們一般需要在時間上加一個隨機值,使得過期時間分散一些。
實際應用:電商首頁經常會使用定時任務刷新緩存,可能大量的數據失效時間都十分集中,如果失效時間一樣,又剛好在失效的時間點大量用戶涌入,就有可能造成緩存雪崩
2.4.2 緩存穿透定義what + 四個解決方式how
緩存穿透(電商:偽造不存在的訂單號)
定義:指查詢一個不存在的數據,每次通過接口或者去查詢數據庫都查不到這個數據,比如黑客的惡意攻擊,比如知道一個訂單號后,然后就偽造一些不存在的訂單號,然后并發來請求你這個訂單詳情。這些訂單號在緩存中都查詢不到,然后會導致把這些查詢請求全部打到數據庫或者SOA接口。這樣的話就會導致數據庫宕機或者你的服務大量超時。這種查詢不存在的數據就是緩存穿透。小點的單機系統,基本上用postman就能搞死,比如9.9塊錢的阿里云服務
圖:
解決這個問題可以從以下方面入手:
第一,nginx層:這樣可以防止攻擊用戶反復用同一個id暴力攻擊,但是我們要知道正常用戶是不會在單秒內發起這么多次請求的,那網關層Nginx進行配置,讓運維對單個IP每秒訪問次數超出閾值的IP都拉黑
第二,controller層校驗 + 緩存空值
2.1 在controller層增加校驗,比如用戶鑒權校驗,參數做校驗,不合法的參數直接代碼Return,比如:id 做基礎校驗,id <=0的直接攔截等。
2.2 緩存空值(步驟流程):
對于這些不存在的請求,
第1次查詢,從緩存取不到的數據,在數據庫中也沒有取到,在緩存中將對應Key的Value對寫為null;
第2-n次查詢,如果后續這個請求還是原值,redis直接返回,從而避免相同 ID 再次訪問 DB。
第2-n次查詢,如果后續這個請求有新值了,把原來緩存的空值刪除掉(所以一般過期時間可以稍微設置的比較短,如30秒,設置太長會導致正常情況也沒法使用)。
缺點:可能導致緩存中存儲大量無用數據。
第三,固定數據放到緩存中,緩存找不到直接返回
這種方式的話要根據自己的實際業務來進行選擇。比如固定的數據,一些省份信息或者城市信息,可以全部緩存起來,對于這些省份和城市信息,都在緩存里面,根本不用訪問數據庫,緩存找不到,直接返回,避免緩存穿透攻擊。
第四,使用布隆過濾器(BloomFilter 的特點是存在性檢測,如果 BloomFilter 中不存在,那么數據一定不存在;如果 BloomFilter 中存在,實際數據也有可能會不存在。非常適合解決這類的問題)
查詢緩存之前先去布隆過濾器查詢下這個數據是否存在。如果數據不存在,然后直接返回空。這樣的話也會減少底層系統的查詢壓力。原理:使用高效的數據結構和算法快速判斷出你這個Key是否在數據庫中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
金手指:后端程序不要相信前端調用或后端服務調用傳遞的參數(分頁為例):
比如你提供了API接口出去,你有這幾個參數,那我覺得作為被調用方,任何可能的參數情況都應該被考慮到,做校驗,因為你不相信調用你的人,你不知道他會傳什么參數給你。
舉個簡單的例子,你這個接口是分頁查詢的,但是你沒對分頁參數的大小做限制,調用的人萬一一口氣查 Integer.MAX_VALUE 一次請求就要你幾秒,多幾個并發你不就掛了么?是公司同事調用還好大不了發現了改掉,但是如果是黑客或者競爭對手呢?在你雙十一當天就調你這個接口會發生什么,就不用我說了吧。這是之前的Leader跟我說的,我覺得大家也都應該了解下。
2.4.3 緩存擊穿定義what + 四個解決方式how
緩存擊穿
定義:是指緩存里面的一個熱點key(拼多多的五菱宏光神車的秒殺)在某個時間點過期。針對于這一個key有大量并發請求過來然后都會同時去數據庫請求數據,瞬間對數據庫造成巨大的壓力(定義2:某個熱點數據失效時,大量針對這個數據的請求會穿透到數據源)。
解決方式:
第一,過期時間,緩存永不過期。
優點:緩存永不過期,就不會發生緩存擊穿;
缺點:會浪費更多的存儲空間,一般應該也不會推薦這種做法。
第二,過期時間,使用隨機退避方式
使用隨機退避方式,失效時隨機 sleep 一個很短的時間保護服務端,再次查詢,如果失敗再執行更新。
第三,過期時間,異步重建緩存
優點:緩存永不過期,就不會發生緩存擊穿。
缺點:這樣的話需要維護每個key的過期時間,定時去輪詢這些key的過期時間。例如一個key的value設置的過期時間是30min,那我們可以為這個key設置它自己的一個過期時間為20min。所以當這個key到了20min的時候我們就可以重新去構建這個key的緩存,同時也更新這個key的一個過期時間。
第四,互斥鎖(金手指:互斥鎖更新,保證同一個進程中針對同一個數據不會并發請求到 DB,減小 DB 壓力)
前提條件:只能針對于同一個key的情況下,比如你有100個并發請求都要來取A的緩存,這時候我們可以借助redis分布式鎖來構建緩存,讓只有一個請求可以去查詢DB其他99個(沒有獲取到鎖)都在外面等著,等A查詢到數據并且把緩存構建好之后其他99個請求都只需要從緩存取就好了。原理就跟我們java的DCL(double checked locking)思想有點類似。
互斥鎖代碼實現:
2.4.4 緩存更新問題(緩存數據源:DB和遠程服務 + 四種緩存更新方式 )
緩存數據源:DB和遠程服務
緩存的數據在數據源發生變更時需要對緩存進行更新,數據源可能是 DB,也可能是遠程服務。
第一,當數據源是 DB 時,可以在更新完 DB 后就直接更新緩存。這種場景下,更新的方式可以是主動更新。
第二,當數據源不是 DB 而是其他遠程服務,可能無法及時主動感知數據變更,這種情況下一般會選擇對緩存數據設置失效期,也就是數據不一致的最大容忍時間。這種場景下,更新方式可以選擇失效更新,即 key 不存在或失效時先請求數據源獲取最新數據,然后再次緩存,并更新失效期。
附加:遠程服務更新redis的問題:如果依賴的遠程服務在更新時出現異常,則會導致數據不可用。
解決1:使用異步更新,就是當失效時先不清除數據,繼續使用舊的數據,然后由異步線程去執行更新任務。這樣就避免了失效瞬間的空窗期。
解決2:另外還有一種純異步更新方式,定時對數據進行分批更新。
所以,實際使用時可以根據業務場景選擇更新方式。
緩存的讀寫操作
(1)讀的時候,先讀緩存,緩存沒有的話,就讀數據庫,然后取出數據后放入緩存,同時返回響應。
(2)寫的時候,先更新數據庫,然后再刪除緩存。
四種緩存更新方式
緩存更新問題是決定在使用緩存時就該考慮的問題,一般的緩存更新主要有以下幾種更新策略:
1、先更新緩存,再更新數據庫
2、先更新數據庫,再更新緩存
3、先刪除緩存,再更新數據庫
4、先更新數據源庫,再刪除緩存(推薦)
至于選擇哪種更新策略的話,沒有絕對的選擇,可以根據自己的業務情況來選擇適合自己的不過一般
推薦的話是選擇 「先更新數據源庫,再刪除緩存」。
緩存更新:為什么寫的時候,是刪除緩存,而不是更新緩存?
第一,緩存value運算復雜,很多時候,在復雜點的緩存場景,緩存不單單是數據庫中直接取出來的值,而是數據庫中取出來并適當計算的值。比如可能更新了某個表的一個字段,然后其對應的緩存,是需要查詢另外兩個表的數據并進行運算,才能計算出緩存最新的值的。
第二,更新緩存的代價有時候是很高的,所以懶加載,用到才更新緩存:如果你只是刪除緩存的話,那么在 1 分鐘內,這個緩存不過就重新計算一次而已,開銷大幅度降低。用到緩存才去運算緩存。選擇刪除緩存,而不是更新緩存,就是一個 Lazy 計算的思想,不要每次都重新做復雜的計算,不管它會不會用到,而是讓它到需要被使用的時候再重新計算。像 Mybatis,Hibernate,都有懶加載思想。查詢一個部門,部門帶了一個員工的 List,沒有必要說每次查詢部門,都里面的 1000 個員工的數據也同時查出來啊。80% 的情況,查這個部門,就只是要訪問這個部門的信息就可以了。先查部門,同時要訪問里面的員工,那么這個時候只有在你要訪問里面的員工的時候,才會去數據庫里面查詢 1000 個員工。
第三,避免頻繁寫而不是頻繁讀:假設修改數據庫的時候,每次將其對應的緩存更新一份,對于比較復雜的緩存數據計算的場景,頻繁修改一個緩存涉及的多個表,緩存也頻繁更新,但是,這個緩存不會被被頻繁讀(頻繁寫redis但是不頻繁讀redis,寫了就沒意義,消耗性能,所以等到需要的時候再寫redis,懶加載)。比如:一個緩存涉及的表的字段,在 1 分鐘內就修改了 20 次,或者是 100 次,那么緩存更新 20 次、100 次;但是這個緩存在 1 分鐘內只被讀取了 1 次,有大量的冷數據。、
2.4.5 緩存數據不一致問題
數據不一致問題
只要使用緩存,就要考慮如何面對數據不一致問題。
定義:緩存不一致產生的原因一般是主動更新失敗,例如更新 DB 后,更新 Redis 因為網絡原因請求超時;或者是異步更新失敗導致。
解決:
(1)如果服務對耗時不是特別敏感可以增加重試;
(2)如果服務對耗時敏感可以通過異步補償任務來處理失敗的更新,或者短期的數據不一致不會影響業務,那么只要下次更新時可以成功,能保證最終一致性就可以。
緩存與數據庫的數據一致性問題?處理方式?
第一,類比
很簡單,在學習計算機組成原理的時候,在cpu和內存中加上了一個cache,保證內存和cache數據一致性就是兩種方式,寫直達法和寫回法。
現在,在后端和mysql中加一個redis,就要保證redis和mysql中的數據一致性,只要用緩存,就可能會涉及到緩存與數據庫雙存儲雙寫,你只要是雙寫,就一定會有數據一致性的問題,那么你如何解決一致性問題?
類比,cache中存放內存中部分數據,redis中存放數據庫中部分數據。
兩個類比統一,mysql和redis綁定在一起,多個后端系統來訪問它們;互斥變量和服務端程序綁定在一起,多個線程來訪問它們。
mysql和redis數據一致性問題/cache和內存數據一致性問題:寫直達法和寫回法;
多個系統競爭redis問題/多個線程競爭互斥變量問題:線程互斥+線程通信。
第一,如果嚴格要求 “緩存+數據庫” 必須保持一致性的話,使用:讀請求和寫請求串行化,串到一個內存隊列里去。串行化可以保證一定不會出現不一致的情況,但是它也會導致系統的吞吐量大幅度降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。解釋:把一些列的操作都放到隊列里面,順序肯定不會亂,但是并發高了,這隊列很容易阻塞,反而會成為整個系統的弱點,瓶頸。
第二,如果允許緩存可以稍微的跟數據庫偶爾有不一致的話,也就是說如果你的系統不是嚴格要求 “緩存+數據庫” 必須保持一致性的話,不需要這樣使用。
三、限流(服務限流,表示處理的對象是服務,包括限流四規則 + 限流四實現)
3.1 限流定義
限流定義
限流就是限制系統的輸入和輸出流量已達到保護系統的目的。一般來說系統的吞吐量是可以被測算的,為了保證系統的穩定運行,一旦達到的需要限制的閾值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延遲處理,拒絕處理,或者部分拒絕處理等等。
為什么要限流?一句話小結:為了保證系統的穩定運行。
假設我們一個系統一小時之最多只能處理10000個請求,但是一小時流量突增10倍,這突增的流量我們如果不進行限制的話,任由它直接進入系統的話,是不是直接會把我們的系統弄癱瘓,就無法對外提供服務了。
金手指:高并發的五種措施,緩存、限流、降級、熔斷、隔離都是保護系統的穩定運行
3.2 限流四規則(理論)
限流四個規則,
(要記憶,語言已組織好)既然要限流,就是要允許一部分請求進入,阻止另外一部分請求進入,那么根據什么規則來篩選進入的請求和被拒絕的請求呢(即提供什么規則決定誰去好大學,誰去差大學,就是高考)?提供四種:
1、后端不做任何干涉,完全交給不確定的網絡,先達到的請求先處理,后達到的請求后處理,達到閾值直接拒絕服務;
2、后端對服務分級,對于核心服務的請求就處理,對于非核心的請求的服務不處理,又稱服務的主動降級;
3、后端所有請求都處理,但是放到延遲隊列中,一點一點的處理。
4、后端對用戶分級,對于重要用戶的請求優先處理,對于普通用戶的請求普通處理,這就是限流規則。
第一種限流,達到閾值直接拒絕服務,實現限流,最簡單的限流,這種限流方式實現簡單,但是要求閾值設置合理,
這個是最最簡單粗暴的做法了,直接把請求直接拒絕掉。比如早高峰坐地鐵的時候,直接讓進入1000個人,剩下多出來的人不讓坐地鐵了。直接把入站口給關閉了。
第二種限流,服務降級,實現限流,核心:服務分級
將系統的所有功能服務進行一個分級,當系統出現問題,需要緊急限流時,可將不是那么重要的功能進行降級處理,停止服務,這樣可以釋放出更多的資源供給核心功能的去用。比如:有一個功能新用戶注冊完,要給用戶發送多少優惠券。這時候服務降級的話就可以直接把送券服務關掉,讓服務快速響應,提高系統處理能力。
第三種限流,請求放到隊列種,延遲處理,實現限流,核心:隊列
把請求全部放入到隊列中,真正處理的話,就從隊列里面依次去取,這樣的話流量比較大的情況可能會導致處理不及時,會有一定的延時。雙十一零點我們付款的時候,去查詢訂單的狀態是不是也會有一定的延時,不像在平時付完款訂單狀態就變成了付款狀態。
第四種限流,特權處理,實現限流,核心:用戶分級
這個模式需要將用戶進行分類,通過預設的分類,讓系統優先處理需要高保障的用戶群體,其它用戶群的請求就會延遲處理或者直接不處理。我們去銀行辦理業務的時候是不是也會經常需要排隊,但是是不是經常會VIP用戶、什么白金卡用戶,直接不需要排隊,直接一上來就可以辦理業務,還優先處理這些人的業務。是不是特羨慕這些人,哎 羨慕也沒辦法誰叫人家有錢咧。
金手指:第二種和第四種都是分級,有什么不同?
回答:分級的對象不同。第二種是服務分級,即對事不對人,對不重要的服務限流;第四種是用戶分級,即對人不對事,對不重要的用戶限流。
3.3 限流四實現
三種限流組件、三種限流實現方式
第一種,計數器方法(達到閾值直接拒絕訪問)
這是最簡單的限流算法了,系統里面維護一個計數器,來一個請求就加1,請求處理完成就減1,當計數器大于指定的閾值,就拒絕新的請求。是通過全局的總求數于設置的閾值來達到限流的目的。通常應用在池化技術上面比如:「數據庫連接池、線程池」等中應用。這種方式的話限流不是「平均速率」的。扛不住突增的流量。
第二種,滑動窗口算法
一種常見的流量控制技術,用來改善吞吐量的技術
第三種,漏桶算法
上圖,水是可以持續流入漏桶里面的(即所有請求一定會經過漏桶的過濾),底部也是勻速的流出,如果漏桶的水超過桶的大小就會發生益出(即 流入的速率大于底部流出的速率 + 持續一段時間后)。
重點:兩個速率:
流入速率即實際的用戶請求速率或壓力測試的速率,流出速率即服務端處理速率。
一般來說,流出速度是固定的,即不管你請求有多少,速率有多快,我反正就這么個速度處理。當然,特殊情況下,需要加快速度處理,也可以動態調整流出速率。
第四種,令牌桶
上圖解釋:如果令牌的數量超過里桶的限制的話,令牌就會溢出,這時候就直接舍棄多余的令牌。
每個請求過來必須拿到桶里面拿到了令牌才允許請求(拿令牌的速度是不限制的,這就意味著如果瞬間有大量的流量請求進來,可以短時間內拿到大量的令牌),拿不到令牌的話直接拒絕。這個令牌桶的思想是不是跟我們java里面的「Semaphore」 有點類似。Semaphore 是拿信號量,用完了就還回去。但是令牌桶的話,不需要還回去,因為令牌會定時的補充。令牌桶算法我們可以通過
Google開源的guava包創建一個令牌桶算法的限流器。
金手指:令牌桶和漏桶不同點:令牌桶新增了一個勻速生產令牌的中間人以恒定的速度往桶里面放令牌,上面的漏桶,流入速率根本不控制,用戶請求壓力直接達到漏桶來(令牌桶這個勻速流入速率和mq對于mysql請求量的控制很像)。
以上是三種限流組件,大家可以根據這個思想然后去實現各種各樣的限流組件。
金手指:任何限流組件都要設置閾值
第一,不管是哪種限流方法,直接拒絕也要,流量窗口、漏桶、令牌桶也好,限流算法里面一定有一個閾值(解釋:直接拒絕要設置閾值,漏桶令牌桶要設置桶大小),這個閾值設置為多少是不是比較難。閾值設置過大的話,服務可能扛不住,閾值設置小了會把用戶請求給誤殺,資源沒有得到最大的一個利用。
第二,任何限流組件都要設置閾值,這是限流和其他兩種保護系統穩定運行的方式(降級、熔斷)的最大區別,即限流一定要好設置閾值。
四、降級(即服務降級,表示處理的對象是服務,這里指主動降級)
4.1 服務降級定義
服務降級定義:
服務降級是當服務器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行。
舉例:雙十一的時候,我們買東西是不是都不允許修改購物地址,不允許發起退貨,不允許退款還有很多服務都不可以用,只允許用戶選擇商品加入購物車付錢。那天只有一個目的就是讓一些不是很重要的服務所占用的cpu資源都讓出來,給購物,付款這樣的核心服務。這樣的話,用戶付款的速度就會越來越快,畢竟前多少名支付的用戶是有免單機會的(大家都會擠在0點那一刻去付款)。服務降級主要用于當整個微服務架構整體的負載超出了預設的上限閾值或即將到來的流量預計將會超過預設的閾值時,為了保證重要或基本的服務能正常運行,將一些 不重要 或 不緊急 的服務或任務進行服務的 延遲使用 或 暫停使用。 降級就是為了解決資源不足和訪問量增加的矛盾。
4.2 服務降級五種方式
服務降級方式
第一,延遲服務:定時任務處理、或者mq延時處理。比如新用戶注冊送多少優惠券可以提示用戶優惠券會24小時到達用戶賬號中,我們可以選擇再凌晨流量較小的時候,批量去執行送券。
第二,前端主動降價,前端頁面降級:頁面點擊按鈕全部置灰,或者頁面調整成為一個靜態頁面顯示“系統正在維護中,。。。。”。
第三,后端主動降級,關閉非核心服務:比如電商關閉推薦服務、關閉運費險、退貨退款等。保證主流程的核心服務下單付款就好。
第四,寫降級:比如秒殺搶購,我們可以只進行Cache的更新返回,然后通過mq異步扣減庫存到DB,保證最終一致性即可,此時可以將DB降級為Cache。
第五,讀降級:比如多級緩存模式,如果后端服務有問題,可以降級為只讀緩存,這種方式適用于對讀一致性要求不高的場景。
五、從服務雪崩到服務熔斷(即服務熔斷,表示處理的對象是服務,就是被動降級)
5.1 服務雪崩定義
服務雪崩
服務雪崩:說到服務熔斷我們就得先了解下什么是服務雪崩。雪崩效應好比就是蝴蝶效應,說的都是一個小因素的變化,卻往往有著無比強大的力量,以至于最后改變整體結構、產生意想不到的結果。 多個微服務之間調用的時候,比如A服務調用了B服務,B服務調用了C服務,然后C服務由于機器宕機或者網略故障, 然后就會導致B服務調用C服務的時候超時,然后A服務調用B服務也會超時,最終整個鏈路都不可用了,導致整個系統不可用就跟雪蹦一樣。
5.2 服務雪崩產生的原因
雪崩效應產生的幾種場景
1、突增流量:比如一大波爬蟲,或者黑客攻擊等。
2、程序bug:代碼死循環,或者資源未釋放等。
3、硬件原因:機器宕機、機房斷電、光纖被挖斷等。
5.3 熔斷機制是應對雪崩效應的一種微服務鏈路保護機制
熔斷機制是應對雪崩效應的一種微服務鏈路保護機制,在互聯網系統中當下游的服務因為某種原因突然變得不可用或響應過慢,上游服務為了保證自己整體服務的可用性,暫時不再繼續調用目標服務,直接快速返回失敗標志,快速釋放資源。如果目標服務情況好轉則恢復調用。
六、面試金手指
6.1 限流四規則 + 限流四實現
限流四個規則,
(要記憶,語言已組織好)既然要限流,就是要允許一部分請求進入,阻止另外一部分請求進入,那么根據什么規則來篩選進入的請求和被拒絕的請求呢(即提供什么規則決定誰去好大學,誰去差大學,就是高考)?提供四種:
1、后端不做任何干涉,完全交給不確定的網絡,先達到的請求先處理,后達到的請求后處理,達到閾值直接拒絕服務;
2、后端對服務分級,對于核心服務的請求就處理,對于非核心的請求的服務不處理,又稱服務的主動降級;
3、后端所有請求都處理,但是放到延遲隊列中,一點一點的處理。
4、后端對用戶分級,對于重要用戶的請求優先處理,對于普通用戶的請求普通處理,這就是限流規則。
限流四實現
1、第一種,計數器方法(達到閾值直接拒絕訪問)
2、第二種,滑動窗口算法,用于改善吞吐量技術
3、第三種,漏桶算法:流入速率即實際的用戶請求速率或壓力測試的速率,流出速率即服務端處理速率。如果漏桶的水超過桶的大小就會發生益出(即 流入的速率大于底部流出的速率 + 持續一段時間后)。
4、第四種,令牌桶
金手指:令牌桶和漏桶不同點:令牌桶新增了一個勻速生產令牌的中間人以恒定的速度往桶里面放令牌,上面的漏桶,流入速率根本不控制,用戶請求壓力直接達到漏桶來(令牌桶這個勻速流入速率和mq對于mysql請求量的控制很像)。
金手指:任何限流組件都要設置閾值
第一,不管是哪種限流方法,直接拒絕也要,漏桶、令牌桶也好,限流算法里面一定有一個閾值(解釋:直接拒絕要設置閾值,漏桶令牌桶要設置桶大小),這個閾值設置為多少是不是比較難。閾值設置過大的話,服務可能扛不住,閾值設置小了會把用戶請求給誤殺,資源沒有得到最大的一個利用。
第二,任何限流組件都要設置閾值,這是限流和其他兩種保護系統穩定運行的方式(降級、熔斷)的最大區別,即限流一定要好設置閾值。
6.2 五種措施小結
起手式,五種措施的總體比較,拉高逼格
第一,五種措施目的相同:都是犧牲部分用戶的體驗換來服務器的安全,保護系統穩定運行(即
只要保證數據庫絕對不會死,對用戶來說,可能就是點擊幾次刷不出來頁面,但是多點幾次,就可以刷出來一次);
第二,接上面,為了達到系統穩定運行的目的
2.1 三種措施終表現類似:限流降級熔斷,為了達到維護系統穩定運行的目的,最終讓用戶體驗到的是某些功能暫時不可達或不可用;
2.2 其他兩種方式,為了達到系統穩定運行的目的,緩存使用多級緩存(本地緩存+分布式緩存)加快速度達到這個目的;隔離通過設計低耦合后端系統達到這個目的,mysql分庫低耦合,mq分業務低耦合,redis集群低耦合,微服務框架低耦合,nginx業務集群低耦合。
第三,接上面,三種措施粒度一般都是服務級別:限流降級熔斷,粒度一般都是服務級別
隔離在設計上也是服務級別的,服務與服務之間隔離,構建微服務系統,對于其他資源,mysql、mq redis nginx 都是業務模塊級別隔離,也可以細粒度隔離,放到不同服務器上,比如mysql業務模塊的庫級別隔離,可以更加細粒度表級別隔離。
第二,金手指(緩存 + 限流 + 降級 VS 熔斷 + 隔離) 一句話小結
緩存 + 限流 + 降級 立足與單個服務,是為了保證單個服務,單個熱點服務不要發生宕機;
熔斷和隔離 立足與服務間關系,是保證單個服務發生宕機后,不要影響或盡可能小的影響其他服務;進一步闡述熔斷和隔離,隔離是服務宕機前,系統啟動前,服務設計和資源設計,一種設計思想,服務間低耦合的設計思想,包括資源和調用的低耦合,mysql分庫低耦合,mq分業務低耦合,redis集群低耦合,微服務框架低耦合,nginx業務集群低耦合;熔斷是服務宕機后,該服務及調用該服務的服務,熔斷,下一請求快速失敗,如hystrix。
第三,限流 和其他四者區別
定義:限流:根據一定規則阻擋,閾值是事先設定好的。
不同點:限流,被阻擋的請求沒有被拋棄了;降級(主動降級),被降級的服務暫時無法訪問。
被動降級即熔斷,被阻擋的請求直接熔斷失敗。
緩存和隔離與具體實際請求無關
第三,附:限流和降級
限流組件,可以設置每秒的請求,有多少能通過組件,剩余的未通過的請求,怎么辦?走被動降級,即熔斷,可以返回一些默認的值,或者友情提示,或者空白的值。例如,對于某明星爆出什么事情,你發現你去微博怎么刷都空白界面,但是有的人又直接進了,你多刷幾次也出來了,現在知道了吧,那是做了降級。
第四,金手指 熔斷(熔斷和四種區別)
第一,緩存、限流、降級、隔離是在設計為了盡量避免服務發生異常,熔斷是指服務發生異常后,將損害降低到最小。
第二,自治性要求很高: 熔斷模式一般都是服務基于策略的自動觸發,其他的,緩存、限流、降級、隔離是人工干預。
第三,觸發原因不同:熔斷一般是某個具體服務(下游服務)故障引起,而緩存、限流、降級、隔離一般是從整體負荷考慮;
第四,管理目標的層次不同:熔斷其實是一個框架級的處理,每個微服務都需要(無層級之分),而緩存、限流、降級、隔離一般需要對業務有層級之分(比如降級一般是從最外圍服務開始)。
主動降級和被動降級
定義:
降級是主動降級,降低非核心服務級別;
熔斷是被動降級,降低當前宕機服務級別,讓其快速失敗
一個例子囊括主動降級和被動降級
主動降價:在雙十一,淘寶流量劇增,停用一部分服務,比如退款拒絕,退款義務不是那么緊急,就是暫時關閉退款服務,等到以后在開放
限流和熔斷(被動降級):比如下單延遲,下單接口其實沒掛,犧牲部分用戶體驗,保住服務器,你多點幾下是可以成功的,等流量高峰過去了,所有的用戶全部都恢復正常訪問,服務器也沒啥事。
七、小結
高并發五個利器(緩存、限流、降級、熔斷、隔離),結束了。
天天打碼,天天進步!!!
總結
以上是生活随笔為你收集整理的暗刺,高并发五个利器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单链表的简单操作与演示
- 下一篇: 私域流量运营之社交裂变