當前位置:首頁 » 硬碟大全 » 集群限流緩存bat
擴展閱讀
webinf下怎麼引入js 2023-08-31 21:54:13
堡壘機怎麼打開web 2023-08-31 21:54:11

集群限流緩存bat

發布時間: 2023-01-31 00:53:35

⑴ 數據多的時候為什麼要使用redis而不用mysql

通常來說,當數據多、並發量大的時候,架構中可以引入Redis,幫助提升架構的整體性能,減少Mysql(或其他資料庫)的壓力,但不是使用Redis,就不用MySQL。

因為Redis的性能十分優越,可以支持每秒十幾萬此的讀/寫操作,並且它還支持持久化、集群部署、分布式、主從同步等,Redis在高並發的場景下數據的安全和一致性,所以它經常用於兩個場景:

緩存

判斷數據是否適合緩存到Redis中,可以從幾個方面考慮: 會經常查詢么?命中率如何?寫操作多麼?數據大小?

我們經常採用這樣的方式將數據刷到Redis中:查詢的請求過來,現在Redis中查詢,如果查詢不到,就查詢資料庫拿到數據,再放到緩存中,這樣第二次相同的查詢請求過來,就可以直接在Redis中拿到數據;不過要注意【緩存穿透】的問題。

緩存的刷新會比較復雜,通常是修改完資料庫之後,還需要對Redis中的數據進行操作;代碼很簡單,但是需要保證這兩步為同一事務,或最終的事務一致性。

高速讀寫

常見的就是計數器,比如一篇文章的閱讀量,不可能每一次閱讀就在資料庫裡面update一次。

高並發的場景很適合使用Redis,比如雙11秒殺,庫存一共就一千件,到了秒殺的時間,通常會在極為短暫的時間內,有數萬級的請求達到伺服器,如果使用資料庫的話,很可能在這一瞬間造成資料庫的崩潰,所以通常會使用Redis(秒殺的場景會比較復雜,Redis只是其中之一,例如如果請求超過某個數量的時候,多餘的請求就會被限流)。

這種高並發的場景,是當請求達到伺服器的時候,直接在Redis上讀寫,請求不會訪問到資料庫;程序會在合適的時間,比如一千件庫存都被秒殺,再將數據批量寫到資料庫中。


所以通常來說,在必要的時候引入Redis,可以減少MySQL(或其他)資料庫的壓力,兩者不是替代的關系 。

我將持續分享Java開發、架構設計、程序員職業發展等方面的見解,希望能得到你的關注。

Redis和MySQL的應用場景是不同的。

通常來說,沒有說用Redis就不用MySQL的這種情況。

因為Redis是一種非關系型資料庫(NoSQL),而MySQL是一種關系型資料庫。

和Redis同類的資料庫還有MongoDB和Memchache(其實並沒有持久化數據)

那關系型資料庫現在常用的一般有MySQL,SQL Server,Oracle。

我們先來了解一下關系型資料庫和非關系型資料庫的區別吧。

1.存儲方式

關系型資料庫是表格式的,因此存儲在表的行和列中。他們之間很容易關聯協作存儲,提取數據很方便。而Nosql資料庫則與其相反,他是大塊的組合在一起。通常存儲在數據集中,就像文檔、鍵值對或者圖結構。

2.存儲結構

關系型資料庫對應的是結構化數據,數據表都預先定義了結構(列的定義),結構描述了數據的形式和內容。這一點對數據建模至關重要,雖然預定義結構帶來了可靠性和穩定性,但是修改這些數據比較困難。而Nosql資料庫基於動態結構,使用與非結構化數據。因為Nosql資料庫是動態結構,可以很容易適應數據類型和結構的變化。

3.存儲規范

關系型資料庫的數據存儲為了更高的規范性,把數據分割為最小的關系表以避免重復,獲得精簡的空間利用。雖然管理起來很清晰,但是單個操作設計到多張表的時候,數據管理就顯得有點麻煩。而Nosql數據存儲在平面數據集中,數據經常可能會重復。單個資料庫很少被分隔開,而是存儲成了一個整體,這樣整塊數據更加便於讀寫

4.存儲擴展

這可能是兩者之間最大的區別,關系型資料庫是縱向擴展,也就是說想要提高處理能力,要使用速度更快的計算機。因為數據存儲在關系表中,操作的性能瓶頸可能涉及到多個表,需要通過提升計算機性能來克服。雖然有很大的擴展空間,但是最終會達到縱向擴展的上限。而Nosql資料庫是橫向擴展的,它的存儲天然就是分布式的,可以通過給資源池添加更多的普通資料庫伺服器來分擔負載。

5.查詢方式

關系型資料庫通過結構化查詢語言來操作資料庫(就是我們通常說的SQL)。SQL支持資料庫CURD操作的功能非常強大,是業界的標准用法。而Nosql查詢以塊為單元操作數據,使用的是非結構化查詢語言(UnQl),它是沒有標準的。關系型資料庫表中主鍵的概念對應Nosql中存儲文檔的ID。關系型資料庫使用預定義優化方式(比如索引)來加快查詢操作,而Nosql更簡單更精確的數據訪問模式。

6.事務

關系型資料庫遵循ACID規則(原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)),而Nosql資料庫遵循BASE原則(基本可用(Basically Availble)、軟/柔性事務(Soft-state )、最終一致性(Eventual Consistency))。由於關系型資料庫的數據強一致性,所以對事務的支持很好。關系型資料庫支持對事務原子性細粒度控制,並且易於回滾事務。而Nosql資料庫是在CAP(一致性、可用性、分區容忍度)中任選兩項,因為基於節點的分布式系統中,很難全部滿足,所以對事務的支持不是很好,雖然也可以使用事務,但是並不是Nosql的閃光點。

7.性能

關系型資料庫為了維護數據的一致性付出了巨大的代價,讀寫性能比較差。在面對高並發讀寫性能非常差,面對海量數據的時候效率非常低。而Nosql存儲的格式都是key-value類型的,並且存儲在內存中,非常容易存儲,而且對於數據的 一致性是 弱要求。Nosql無需sql的解析,提高了讀寫性能。

8.授權方式

大多數的關系型資料庫都是付費的並且價格昂貴,成本較大(MySQL是開源的,所以應用的場景最多),而Nosql資料庫通常都是開源的。

所以,在實際的應用環境中,我們一般會使用MySQL存儲我們的業務過程中的數據,因為這些數據之間的關系比較復雜,我們常常會需要在查詢一個表的數據時候,將其他關系表的數據查詢出來,例如,查詢某個用戶的訂單,那至少是需要用戶表和訂單表的數據。

查詢某個商品的銷售數據,那可能就會需要用戶表,訂單表,訂單明細表,商品表等等。

而在這樣的使用場景中,我們使用Redis來存儲的話,也就是KeyValue形式存儲的話,其實並不能滿足我們的需要。

即使Redis的讀取效率再高,我們也沒法用。

但,對於某些沒有關聯少,且需要高頻率讀寫,我們使用Redis就能夠很好的提高整個體統的並發能力。

例如商品的庫存信息,我們雖然在MySQL中會有這樣的欄位,但是我們並不想MySQL的資料庫被高頻的讀寫,因為使用這樣會導致我的商品表或者庫存表IO非常高,從而影響整個體統的效率。

所以,對於這樣的數據,且有沒有什麼復雜邏輯關系(就只是隸屬於SKU)的數據,我們就可以放在Redis裡面,下單直接在Redis中減掉庫存,這樣,我們的訂單的並發能力就能夠提高了。

個人覺得應該站出來更正一下,相反的數據量大,更不應該用redis。


為什麼?

因為redis是內存型資料庫啊,是放在內存里的。

設想一下,假如你的電腦100G的資料,都用redis來存儲,那麼你需要100G以上的內存!

使用場景

Redis最明顯的用例之一是將其用作緩存。只是保存熱數據,或者具有過期的cache。

例如facebook,使用Memcached來作為其會話緩存。



總之,沒有見過哪個大公司數據量大了,換掉mysql用redis的。


題主你錯了,不是用redis代替MySQL,而是引入redis來優化。

BAT里越來越多的項目組已經採用了redis+MySQL的架構來開發平台工具。

如題主所說,當數據多的時候,MySQL的查詢效率會大打折扣。我們通常默認如果查詢的欄位包含索引的話,返回是毫秒級別的。但是在實際工作中,我曾經遇到過一張包含10個欄位的表,1800萬+條數據,當某種場景下,我們不得不根據一個未加索引的欄位進行精確查詢的時候,單條sql語句的執行時長有時能夠達到2min以上,就更別提如果用like這種模糊查詢的話,其效率將會多麼低下。

我們最開始是希望能夠通過增加索引的方式解決,但是面對千萬級別的數據量,我們也不敢貿然加索引,因為一旦資料庫hang住,期間的所有資料庫寫入請求都會被放到等待隊列中,如果請求是通過http請求發過來的,很有可能導致服務發生分鍾級別的超時不響應。

經過一番調研,最終敲定的解決方案是引入redis作為緩存。redis具有運行效率高,數據查詢速度快,支持多種存儲類型以及事務等優勢,我們把經常讀取,而不經常改動的數據放入redis中,伺服器讀取這類數據的時候時候,直接與redis通信,極大的緩解了MySQL的壓力。

然而,我在上面也說了,是redis+MySQL結合的方式,而不是替代。原因就是redis雖然讀寫很快,但是不適合做數據持久層,主要原因是使用redis做數據落盤是要以效率作為代價的,即每隔制定的時間,redis就要去進行數據備份/落盤,這對於單線程的它來說,勢必會因「分心」而影響效率,結果得不償失。

樓主你好,首先糾正下,數據多並不是一定就用Redis,Redis歸屬於NoSQL資料庫中,其特點擁有高性能讀寫數據速度,主要解決業務效率瓶頸。下面就詳細說下Redis的相比MySQL優點。( 關於Redis詳細了解參見我近期文章:https://www.toutiao.com/i6543810796214813187/ )

讀寫異常快

Redis非常快,每秒可執行大約10萬次的讀寫速度。

豐富的數據類型

Redis支持豐富的數據類型,有二進制字元串、列表、集合、排序集和散列等等。這使得Redis很容易被用來解決各種問題,因為我們知道哪些問題可以更好使用地哪些數據類型來處理解決。

原子性

Redis的所有操作都是原子操作,這確保如果兩個客戶端並發訪問,Redis伺服器能接收更新的值。

豐富實用工具 支持異機主從復制

Redis支持主從復制的配置,它可以實現主伺服器的完全拷貝。

以上為開發者青睞Redis的主要幾個可取之處。但是,請注意實際生產環境中企業都是結合Redis和MySQL的特定進行不同應用場景的取捨。 如緩存——熱數據、計數器、消息隊列(與ActiveMQ,RocketMQ等工具類似)、位操作(大數據處理)、分布式鎖與單線程機制、最新列表(如新聞列表頁面最新的新聞列表)以及排行榜等等 可以看見Redis大顯身手的場景。可是對於嚴謹的數據准確度和復雜的關系型應用MySQL等關系型資料庫依然不可替。

web應用中一般採用MySQL+Redis的方式,web應用每次先訪問Redis,如果沒有找到數據,才去訪問MySQL。

本質區別

1、mysql:數據放在磁碟 redis:數據放在內存。

首先要知道mysql存儲在磁碟里,redis存儲在內存里,redis既可以用來做持久存儲,也可以做緩存,而目前大多數公司的存儲都是mysql + redis,mysql作為主存儲,redis作為輔助存儲被用作緩存,加快訪問讀取的速度,提高性能。

使用場景區別

1、mysql支持sql查詢,可以實現一些關聯的查詢以及統計;

2、redis對內存要求比較高,在有限的條件下不能把所有數據都放在redis;

3、mysql偏向於存數據,redis偏向於快速取數據,但redis查詢復雜的表關系時不如mysql,所以可以把熱門的數據放redis,mysql存基本數據。

mysql的運行機制

mysql作為持久化存儲的關系型資料庫,相對薄弱的地方在於每次請求訪問資料庫時,都存在著I/O操作,如果反復頻繁的訪問資料庫。第一:會在反復鏈接資料庫上花費大量時間,從而導致運行效率過慢;第二:反復地訪問資料庫也會導致資料庫的負載過高,那麼此時緩存的概念就衍生了出來。

Redis持久化

由於Redis的數據都存放在內存中,如果沒有配置持久化,redis重啟後數據就全丟失了,於是需要開啟redis的持久化功能,將數據保存到磁碟上,當redis重啟後,可以從磁碟中恢復數據。redis提供兩種方式進行持久化,一種是RDB持久化(原理是將Reids在內存中的資料庫記錄定時mp到磁碟上的RDB持久化),另外一種是AOF(append only file)持久化(原理是將Reids的操作日誌以追加的方式寫入文件)。

redis是放在內存的~!

數據量多少絕對不是選擇redis和mysql的准則,因為無論是mysql和redis都可以集群擴展,約束它們的只是硬體(即你有沒有那麼多錢搭建上千個組成的集群),我個人覺得數據讀取的快慢可能是選擇的標准之一,另外工作中往往是兩者同是使用,因為mysql存儲在硬碟,做持久化存儲,而redis存儲在內存中做緩存提升效率。

關系型資料庫是必不可少的,因為只有關系型資料庫才能提供給你各種各樣的查詢方式。如果有一系列的數據會頻繁的查詢,那麼就用redis進行非持久化的存儲,以供查詢使用,是解決並發性能問題的其中一個手段

⑵ 關於API網關(四)——限流

通俗的說,流量控制就是控制用戶請求的策略,主要包括:許可權、限流、流量調度。
許可權上一篇已經講過了,這一篇講限流,下一篇講流量調度。
限流是指限制用戶調用的頻率(QPS/QPM)或者次數。

流量限制,站在用戶或者運營的角度看,最直觀能感受到的作用是——收費
各大主流開放平台的對外API,一般都有一些免費的額度,可以供個人測試用,一旦想大規模調用,就需要付費購買更大的額度(頻率、次數),根據調用次數或者頻率進行收費。一旦超過擁有的額度,就會被限制調用。

其實這才是限流最大的用處,只是用戶或者運營同學無感,所以不太被大多數人了解。
網關後面是各個服務,各個服務的介面通過網關透出去給用戶調用。理論上說,用戶的流量是不可預知的,隨時可能來一波,一旦流量的峰值超過了服務的承載能力,服務就掛了,比如有大新聞發生時的某浪微博,比如前些年的12306.
所以, 網關必須保證,放過去到達後端服務的流量一定不可以超過服務可以承載的上限 。這個上限,是網關和各個服務協商出來的。

由簡到難,限流可以 分為單機限流、單集群限流、全集群限流
這里不討論具體的如漏桶、令牌桶等限流演算法,只說概念和思想。

單機限流的思想很簡單,就是每個機器的限流值 x 機器數量 = 總的限流值。
舉個例子,A用戶的QPS限制是100,網關部署了10台機器,那麼,每台機器限制10QPS就可以了。
先說好處,這種方法實現起來非常簡單,每台機器在本地內存計算qps就可以了,超過閾值就拒流。
不過單機限流的缺陷也十分明顯,主要體現在兩點:
 當網關部署的機器數量發生變化時,每台機器的限流值需要根據機器數調整。現實中,因為擴容、縮容、機器宕機等原因,機器數的變化是常有的事。
 單機限流的前提是,每台網關承載的用戶的流量是平均的,但是事實上,在某些時間,用戶的流量並不是完全平均分布在每台機器上的。
舉個例子:
10台機器,每台限qps10,其中3台每台實際qps是15,因為超限導致用戶流量被拒。其餘7台每台qps是7。這樣用戶總的qps = 15 * 3 + 7 * 7 = 94. 用戶qps並沒有超限,但是卻有一部分流量被拒了,這樣就很有問題。
實際上,單台限流的閾值也會設置的稍微大一些,以抵消流量不均的問題。
因為上面的問題, 單機限流通常作為一種兜底的備用手段,大多數時候用的還是集群限流

先來看一個示意圖:

相比單機限流,集群限流的計數工作上移到redis集群內進行,解決了單機限流的缺陷。
但是集群限流也不是完美的,因為引入了redis,那麼,當網關和redis之間的網路抖動、redis本身故障時,集群限流就失效了,這時候,還是得依靠單機限流進行兜底。
也就是說, 集群限流 + 單機限流配合,才是一個比穩妥的方案

接下來我們來思考這樣一個問題:大型網關一般都是多機房、多地域部署的,當然,後端的服務也是多機房、多地域部署的,在保護服務這一點來說,集群限流是夠用了。但是對用戶來說,還是有一些問題:
比如,用戶購買的QPS上限是30,我們的網關部署在中國北、中、南三個地域,那麼這30QPS怎麼分配呢?
平均肯定不行,用戶的流量可能是明顯不均衡的,比如用戶的業務主要集中在中國北方,那麼用戶的流量大部分都會進入北方的網關,網關如果限制QPS為10的話,用戶肯定來投訴。
那每個地域都限制為30行不行?也不行,如果用戶的流量比較均勻的分布在各個地域,那麼用戶購買了30QPS,實際上可能使用了90QPS,這太虧了。
按照解決單機限流流量不均的思路,搞一個公共的redis集群來計數行不行?
也不行,受限於信號傳播速度和天朝的廣闊疆域,每個流量都計數,肯定不現實,rt太高會導致限流失去意義,帶寬成本也會變得極其昂貴,對redis的規格要求也會很高。總之,很貴還解決不了問題。
有一種巧妙的解決辦法是:本地集群階梯計數 + 全集群檢查。
還是剛才的例子:
限流閾值時90,那麼三個地域各自計數,當本地域的數值達到30時,去其他兩個地域取一次對方當前的計數值,三個地域的計數值加起來,如果超了,告訴另外兩個地域超了,開始拒流。如果沒超,本地QPS每上漲10,重復一次上述的動作。
這樣就能有效的減少與redis的交互次數,同時實現了全地域真·集群限流。
當然,這種全地域集群限流,因為rt和階梯計數間隔的存在,一定是不準的,但是,比單集群限流還是好很多。

當某個用戶流量特別大的時候,redis計數就會遇到典型的熱點key問題,導致redis集群單節點壓力過大, 有兩種辦法可以解決這個問題:打散和抽樣。

打散是指,把熱點key加一些後綴,使其變成多個key,從而hash到不通的redis節點上,均攤壓力。
比如熱點key是abcd,那麼打散後,key變成了abcd1、abcd2、abcd3、abcd4。技術時,輪流加1、2、3、4的後綴就可以了。

抽樣是指,針對熱點key,不是每個每個請求到來時都進行計數,而是進行一個抽樣,比如每10個請求記一次數,這樣redis的壓力就會降低到十分之一。

說著把流量調度的也說完了哈哈,那下一篇再說說監控好了,順便推一下我現在在用的國產網關:GOKU,來自Eolinker。我覺得比KONG好用,感興趣的同學可以自行去了解一下。
www.eolinker.com

⑶ 高並發,你真的理解透徹了嗎


高並發,幾乎是每個程序員都想擁有的經驗。原因很簡單:隨著流量變大,會遇到各種各樣的技術問題,比如介面響應超時、CPU load升高、GC頻繁、死鎖、大數據量存儲等等,這些問題能推動我們在技術深度上不斷精進。

在過往的面試中,如果候選人做過高並發的項目,我通常會讓對方談談對於高並發的理解,但是能系統性地回答好此問題的人並不多。

大概分成這樣幾類:

1、對數據化的指標沒有概念 :不清楚選擇什麼樣的指標來衡量高並發系統?分不清並發量和QPS,甚至不知道自己系統的總用戶量、活躍用戶量,平峰和高峰時的QPS和TPS等關鍵數據。

3、理解片面,把高並發設計等同於性能優化 :大談並發編程、多級緩存、非同步化、水平擴容,卻忽視高可用設計、服務治理和運維保障。

4、掌握大方案,卻忽視最基本的東西 :能講清楚垂直分層、水平分區、緩存等大思路,卻沒意識去分析數據結構是否合理,演算法是否高效,沒想過從最根本的IO和計算兩個維度去做細節優化。

這篇文章,我想結合自己的高並發項目經驗,系統性地總結下高並發需要掌握的知識和實踐思路,希望對你有所幫助。內容分成以下3個部分:


高並發意味著大流量,需要運用技術手段抵抗流量的沖擊,這些手段好比操作流量,能讓流量更平穩地被系統所處理,帶給用戶更好的體驗。

我們常見的高並發場景有:淘寶的雙11、春運時的搶票、微博大V的熱點新聞等。除了這些典型事情,每秒幾十萬請求的秒殺系統、每天千萬級的訂單系統、每天億級日活的信息流系統等,都可以歸為高並發。

很顯然,上面談到的高並發場景,並發量各不相同, 那到底多大並發才算高並發呢?

1、不能只看數字,要看具體的業務場景。不能說10W QPS的秒殺是高並發,而1W QPS的信息流就不是高並發。信息流場景涉及復雜的推薦模型和各種人工策略,它的業務邏輯可能比秒殺場景復雜10倍不止。因此,不在同一個維度,沒有任何比較意義。

2、業務都是從0到1做起來的,並發量和QPS只是參考指標,最重要的是:在業務量逐漸變成原來的10倍、100倍的過程中,你是否用到了高並發的處理方法去演進你的系統,從架構設計、編碼實現、甚至產品方案等維度去預防和解決高並發引起的問題?而不是一味的升級硬體、加機器做水平擴展。

此外,各個高並發場景的業務特點完全不同:有讀多寫少的信息流場景、有讀多寫多的交易場景, 那是否有通用的技術方案解決不同場景的高並發問題呢?

我覺得大的思路可以借鑒,別人的方案也可以參考,但是真正落地過程中,細節上還會有無數的坑。另外,由於軟硬體環境、技術棧、以及產品邏輯都沒法做到完全一致,這些都會導致同樣的業務場景,就算用相同的技術方案也會面臨不同的問題,這些坑還得一個個趟。

因此,這篇文章我會將重點放在基礎知識、通用思路、和我曾經實踐過的有效經驗上,希望讓你對高並發有更深的理解。


先搞清楚高並發系統設計的目標,在此基礎上再討論設計方案和實踐經驗才有意義和針對性。

高並發絕不意味著只追求高性能,這是很多人片面的理解。從宏觀角度看,高並發系統設計的目標有三個:高性能、高可用,以及高可擴展。

1、高性能:性能體現了系統的並行處理能力,在有限的硬體投入下,提高性能意味著節省成本。同時,性能也反映了用戶體驗,響應時間分別是100毫秒和1秒,給用戶的感受是完全不同的。

2、高可用:表示系統可以正常服務的時間。一個全年不停機、無故障;另一個隔三差五齣線上事故、宕機,用戶肯定選擇前者。另外,如果系統只能做到90%可用,也會大大拖累業務。

3、高擴展:表示系統的擴展能力,流量高峰時能否在短時間內完成擴容,更平穩地承接峰值流量,比如雙11活動、明星離婚等熱點事件。

這3個目標是需要通盤考慮的,因為它們互相關聯、甚至也會相互影響。

比如說:考慮系統的擴展能力,你會將服務設計成無狀態的,這種集群設計保證了高擴展性,其實也間接提升了系統的性能和可用性。

再比如說:為了保證可用性,通常會對服務介面進行超時設置,以防大量線程阻塞在慢請求上造成系統雪崩,那超時時間設置成多少合理呢?一般,我們會參考依賴服務的性能表現進行設置。

再從微觀角度來看,高性能、高可用和高擴展又有哪些具體的指標來衡量?為什麼會選擇這些指標呢?

2.2.1 性能指標

通過性能指標可以度量目前存在的性能問題,同時作為性能優化的評估依據。一般來說,會採用一段時間內的介面響應時間作為指標。

1、平均響應時間:最常用,但是缺陷很明顯,對於慢請求不敏感。比如1萬次請求,其中9900次是1ms,100次是100ms,則平均響應時間為1.99ms,雖然平均耗時僅增加了0.99ms,但是1%請求的響應時間已經增加了100倍。

2、TP90、TP99等分位值:將響應時間按照從小到大排序,TP90表示排在第90分位的響應時間, 分位值越大,對慢請求越敏感。

3、吞吐量:和響應時間呈反比,比如響應時間是1ms,則吞吐量為每秒1000次。

通常,設定性能目標時會兼顧吞吐量和響應時間,比如這樣表述:在每秒1萬次請求下,AVG控制在50ms以下,TP99控制在100ms以下。對於高並發系統,AVG和TP分位值必須同時要考慮。

另外,從用戶體驗角度來看,200毫秒被認為是第一個分界點,用戶感覺不到延遲,1秒是第二個分界點,用戶能感受到延遲,但是可以接受。

因此,對於一個 健康 的高並發系統,TP99應該控制在200毫秒以內,TP999或者TP9999應該控制在1秒以內。

2.2.2 可用性指標

高可用性是指系統具有較高的無故障運行能力,可用性 = 正常運行時間 / 系統總運行時間,一般使用幾個9來描述系統的可用性。

對於高並發系統來說,最基本的要求是:保證3個9或者4個9。原因很簡單,如果你只能做到2個9,意味著有1%的故障時間,像一些大公司每年動輒千億以上的GMV或者收入,1%就是10億級別的業務影響。

2.2.3 可擴展性指標

面對突發流量,不可能臨時改造架構,最快的方式就是增加機器來線性提高系統的處理能力。

對於業務集群或者基礎組件來說,擴展性 = 性能提升比例 / 機器增加比例,理想的擴展能力是:資源增加幾倍,性能提升幾倍。通常來說,擴展能力要維持在70%以上。

但是從高並發系統的整體架構角度來看,擴展的目標不僅僅是把服務設計成無狀態就行了,因為當流量增加10倍,業務服務可以快速擴容10倍,但是資料庫可能就成為了新的瓶頸。

像MySQL這種有狀態的存儲服務通常是擴展的技術難點,如果架構上沒提前做好規劃(垂直和水平拆分),就會涉及到大量數據的遷移。

因此,高擴展性需要考慮:服務集群、資料庫、緩存和消息隊列等中間件、負載均衡、帶寬、依賴的第三方等,當並發達到某一個量級後,上述每個因素都可能成為擴展的瓶頸點。

了解了高並發設計的3大目標後,再系統性總結下高並發的設計方案,會從以下兩部分展開:先總結下通用的設計方法,然後再圍繞高性能、高可用、高擴展分別給出具體的實踐方案。

通用的設計方法主要是從「縱向」和「橫向」兩個維度出發,俗稱高並發處理的兩板斧:縱向擴展和橫向擴展。

3.1.1 縱向擴展(scale-up)

它的目標是提升單機的處理能力,方案又包括:

1、提升單機的硬體性能:通過增加內存、 CPU核數、存儲容量、或者將磁碟 升級成SSD 等堆硬體的方式來提升。

2、提升單機的軟體性能:使用緩存減少IO次數,使用並發或者非同步的方式增加吞吐量。

3.1.2 橫向擴展(scale-out)

因為單機性能總會存在極限,所以最終還需要引入橫向擴展,通過集群部署以進一步提高並發處理能力,又包括以下2個方向:

1、做好分層架構:這是橫向擴展的提前,因為高並發系統往往業務復雜,通過分層處理可以簡化復雜問題,更容易做到橫向擴展。

上面這種圖是互聯網最常見的分層架構,當然真實的高並發系統架構會在此基礎上進一步完善。比如會做動靜分離並引入CDN,反向代理層可以是LVS+Nginx,Web層可以是統一的API網關,業務服務層可進一步按垂直業務做微服務化,存儲層可以是各種異構資料庫。

2、各層進行水平擴展:無狀態水平擴容,有狀態做分片路由。業務集群通常能設計成無狀態的,而資料庫和緩存往往是有狀態的,因此需要設計分區鍵做好存儲分片,當然也可以通過主從同步、讀寫分離的方案提升讀性能。

下面再結合我的個人經驗,針對高性能、高可用、高擴展3個方面,總結下可落地的實踐方案。

3.2.1 高性能的實踐方案

1、集群部署,通過負載均衡減輕單機壓力。

2、多級緩存,包括靜態數據使用CDN、本地緩存、分布式緩存等,以及對緩存場景中的熱點key、緩存穿透、緩存並發、數據一致性等問題的處理。

3、分庫分表和索引優化,以及藉助搜索引擎解決復雜查詢問題。

4、考慮NoSQL資料庫的使用,比如HBase、TiDB等,但是團隊必須熟悉這些組件,且有較強的運維能力。

5、非同步化,將次要流程通過多線程、MQ、甚至延時任務進行非同步處理。

6、限流,需要先考慮業務是否允許限流(比如秒殺場景是允許的),包括前端限流、Nginx接入層的限流、服務端的限流。

7、對流量進行 削峰填谷 ,通過 MQ承接流量。

8、並發處理,通過多線程將串列邏輯並行化。

9、預計算,比如搶紅包場景,可以提前計算好紅包金額緩存起來,發紅包時直接使用即可。

10、 緩存預熱 ,通過非同步 任務 提前 預熱數據到本地緩存或者分布式緩存中。

11、減少IO次數,比如資料庫和緩存的批量讀寫、RPC的批量介面支持、或者通過冗餘數據的方式幹掉RPC調用。

12、減少IO時的數據包大小,包括採用輕量級的通信協議、合適的數據結構、去掉介面中的多餘欄位、減少緩存key的大小、壓縮緩存value等。

13、程序邏輯優化,比如將大概率阻斷執行流程的判斷邏輯前置、For循環的計算邏輯優化,或者採用更高效的演算法。

14、各種池化技術的使用和池大小的設置,包括HTTP請求池、線程池(考慮CPU密集型還是IO密集型設置核心參數)、資料庫和Redis連接池等。

15、JVM優化,包括新生代和老年代的大小、GC演算法的選擇等,盡可能減少GC頻率和耗時。

16、鎖選擇,讀多寫少的場景用樂觀鎖,或者考慮通過分段鎖的方式減少鎖沖突。

上述方案無外乎從計算和 IO 兩個維度考慮所有可能的優化點,需要有配套的監控系統實時了解當前的性能表現,並支撐你進行性能瓶頸分析,然後再遵循二八原則,抓主要矛盾進行優化。

3.2.2 高可用的實踐方案

1、對等節點的故障轉移,Nginx和服務治理框架均支持一個節點失敗後訪問另一個節點。

2、非對等節點的故障轉移,通過心跳檢測並實施主備切換(比如redis的哨兵模式或者集群模式、MySQL的主從切換等)。

3、介面層面的超時設置、重試策略和冪等設計。

4、降級處理:保證核心服務,犧牲非核心服務,必要時進行熔斷;或者核心鏈路出問題時,有備選鏈路。

5、限流處理:對超過系統處理能力的請求直接拒絕或者返回錯誤碼。

6、MQ場景的消息可靠性保證,包括procer端的重試機制、broker側的持久化、consumer端的ack機制等。

7、灰度發布,能支持按機器維度進行小流量部署,觀察系統日誌和業務指標,等運行平穩後再推全量。

8、監控報警:全方位的監控體系,包括最基礎的CPU、內存、磁碟、網路的監控,以及Web伺服器、JVM、資料庫、各類中間件的監控和業務指標的監控。

9、災備演練:類似當前的「混沌工程」,對系統進行一些破壞性手段,觀察局部故障是否會引起可用性問題。

高可用的方案主要從冗餘、取捨、系統運維3個方向考慮,同時需要有配套的值班機制和故障處理流程,當出現線上問題時,可及時跟進處理。

3.2.3 高擴展的實踐方案

1、合理的分層架構:比如上面談到的互聯網最常見的分層架構,另外還能進一步按照數據訪問層、業務邏輯層對微服務做更細粒度的分層(但是需要評估性能,會存在網路多一跳的情況)。

2、存儲層的拆分:按照業務維度做垂直拆分、按照數據特徵維度進一步做水平拆分(分庫分表)。

3、業務層的拆分:最常見的是按照業務維度拆(比如電商場景的商品服務、訂單服務等),也可以按照核心介面和非核心介面拆,還可以按照請求源拆(比如To C和To B,APP和H5 )。


高並發確實是一個復雜且系統性的問題,由於篇幅有限,諸如分布式Trace、全鏈路壓測、柔性事務都是要考慮的技術點。另外,如果業務場景不同,高並發的落地方案也會存在差異,但是總體的設計思路和可借鑒的方案基本類似。

高並發設計同樣要秉承架構設計的3個原則:簡單、合適和演進。"過早的優化是萬惡之源",不能脫離業務的實際情況,更不要過度設計,合適的方案就是最完美的。

作者簡介:985碩士,前亞馬遜工程師,現大廠技術管理者。

⑷ 阿里sentinel源碼解析

sentinel是阿里巴巴開源的流量整形(限流、熔斷)框架,目前在github擁有15k+的star,sentinel以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。

我們以sentinel的主流程入手,分析sentinel是怎麼搜集流量指標,完成流量整形的。

首先我們先看一個sentinel的簡單使用demo,只需要調用SphU.entry獲取到entry,然後在完成業務方法之後調用entry.exit即可。

SphU.entry會調用Env.sph.entry,將name和流量流向封裝成StringResourceWrapper,然後繼續調用entry處理。

進入CtSph的entry方法,最終來到entryWithPriority,調用InternalContextUtil.internalEnter初始化ThreadLocal的Context,然後調用lookProcessChain初始化責任鏈,最終調用chain.entry進入責任鏈進行處理。

InternalContextUtil.internalEnter會調用trueEnter方法,主要是生成DefaultNode到contextNameNodeMap,然後生成Context設置到contextHolder的過程。

lookProcessChain已經做過優化,支持spi載入自定義的責任鏈bulider,如果沒有定義則使用默認的DefaultSlotChainBuilder進行載入。默認載入的slot和順序可見鎮樓圖,不再細說。

最後來到重頭戲chain.entry進入責任鏈進行處理,下面會按照順序分別對每個處理器進行分析。
首先來到NodeSelectorSlot,主要是獲取到name對應的DefaultNode並緩存起來,設置為context的當前節點,然後通知下一個節點。

下一個節點是ClusterBuilderSlot,繼續對DefaultNode設置ClusterNode與OriginNode,然後通知下一節點。

下一個節點是LogSlot,只是單純的列印日誌,不再細說。

下一個節點是StatisticSlot,是一個後置節點,先通知下一個節點處理完後,
1.如果沒有報錯,則對node、clusterNode、originNode、ENTRY_NODE的線程數、通過請求數進行增加。
2.如果報錯是PriorityWaitException,則只對線程數進行增加。
3.如果報錯是BlockException,設置報錯到node,然後對阻擋請求數進行增加。
4.如果是其他報錯,設置報錯到node即可。

下一個節點是FlowSlot,這個節點就是重要的限流處理節點,進入此節點是調用checker.checkFlow進行限流處理。

來到FlowRuleChecker的checkFlow方法,調用ruleProvider.apply獲取到資源對應的FlowRule列表,然後遍歷FlowRule調用canPassCheck校驗限流規則。

canPassCheck會根據rule的限流模式,選擇集群限流或者本地限流,這里分別作出分析。

passLocalCheck是本地限流的入口,首先會調用選出限流的node,然後調用canPass進行校驗。

會根據以下規則選中node。
1.strategy是STRATEGY_DIRECT。
1.1.limitApp不是other和default,並且等於orgin時,選擇originNode。
1.2.limitApp是other,選擇originNode。
1.3.limitApp是default,選擇clusterNode。
2.strategy是STRATEGY_RELATE,選擇clusterNode。
3.strategy是STRATEGY_CHAIN,選擇node。

選擇好對應的node後就是調用canPass校驗限流規則,目前sentinel有三種本地限流規則:普通限流、勻速限流、冷啟動限流。

普通限流的實現是DefaultController,就是統計當前的線程數或者qps加上需要通過的數量有沒有大於限定值,小於等於則直接通過,否則阻擋。

勻速限流的實現是RateLimiterController,使用了AtomicLong保證了latestPassedTime的原子增長,因此停頓的時間是根據latestPassedTime-currentTime計算出來,得到一個勻速的睡眠時間。

冷啟動限流的實現是WarmUpController,是sentinel中最難懂的限流方式,其實不太需要關注這些復雜公式的計算,也可以得出冷啟動的限流思路:
1.當qps已經達到溫熱狀態時,按照正常的添加令牌消耗令牌即可。
2.當qps處於過冷狀態時,會添加令牌使得演算法繼續降溫。
3.當qps逐漸回升,大於過冷的邊界qps值時,不再添加令牌,慢慢消耗令牌使得逐漸增大單位時間可通過的請求數,讓演算法繼續回溫。
總結出一點,可通過的請求數跟令牌桶剩餘令牌數量成反比,以達到冷啟動的作用。

接下來是集群限流,passClusterCheck是集群限流的入口,會根據flowId調用clusterSerivce獲取指定數量的token,然後根據其結果判斷是否通過、睡眠、降級到本地限流、阻擋。

接下來看一下ClusterService的處理,會根據ruleId獲取到對應的FlowRule,然後調用ClusterFlowChecker.acquireClusterToken獲取結果返回。ClusterFlowChecker.acquireClusterToken的處理方式跟普通限流是一樣的,只是會將集群的請求都集中在一個service中處理,來達到集群限流的效果,不再細說。

FlowSlot的下一個節點是DegradeSlot,是熔斷處理器,進入時會調用performChecking,進而獲取到CircuitBreaker列表,然後調用其tryPass校驗是否熔斷。

來到AbstractCircuitBreaker的tryPass方法,主要是判斷熔斷器狀態,如果是close直接放行,如果是open則會校驗是否到達開啟halfopen的時間,如果成功將狀態cas成halfopen則繼續放行,其他情況都是阻攔。

那怎麼將熔斷器的狀態從close變成open呢?怎麼將halfopen變成close或者open呢?sentinel由兩種熔斷器:錯誤數熔斷器ExceptionCircuitBreaker、響應時間熔斷器ResponseTimeCircuitBreaker,都分析一遍。
當業務方法報錯時會調用Tracer.traceEntry將報錯設置到entry上。

當調用entry.exit時,會隨著責任鏈來到DegradeSlot的exit方法,會遍歷熔斷器列表調用其onRequestComplete方法。

ExceptionCircuitBreaker的onRequestComplete會記錄錯誤數和總請求數,然後調用繼續處理。
1.當前狀態是open時,不應該由熔斷器底層去轉換狀態,直接退出。
2.當前狀態是halfopen時,如果沒有報錯,則將halfopen變成close,否則將halfopen變成open。
3.當前狀態時close時,則根據是否總請求達到了最低請求數,如果達到了話再比較錯誤數/錯誤比例是否大於限定值,如果大於則直接轉換成open。

ExceptionCircuitBreaker的onRequestComplete會記錄慢響應數和總請求數,然後調用繼續處理。
1.當前狀態是open時,不應該由熔斷器底層去轉換狀態,直接退出。
2.當前狀態是halfopen時,如果當前響應時間小於限定值,則將halfopen變成close,否則將halfopen變成open。
3.當前狀態時close時,則根據是否總請求達到了最低請求數,如果達到了話再比較慢請求數/慢請求比例是否大於限定值,如果大於則直接轉換成open。

下一個節點是AuthoritySlot,許可權控制器,這個控制器就是看當前origin是否被允許進入請求,不允許則報錯,不再細說。

終於來到最後一個節點SystemSlot了,此節點是自適應處理器,主要是根據系統自身負載(qps、最大線程數、最高響應時間、cpu使用率、系統bbr)來判斷請求是否能夠通過,保證系統處於一個能穩定處理請求的安全狀態。

尤其值得一提的是bbr演算法,作者參考了tcp bbr的設計,通過最大的qps和最小的響應時間動態計算出可進入的線程數,而不是一個粗暴的固定可進入的線程數,為什麼能通過這兩個值就能計算出可進入的線程數?可以網上搜索一下tcp bbr演算法的解析,十分巧妙,不再細說。

⑸ 軟體更新丨Spring Cloud Alibaba發布第二個版本,Spring發來賀電

還是熟悉的面孔,還是熟悉的味道,不同的是,這次的 配方升級 了。

時隔 51天,Spencer Gibb再次在Spring官網的博客頁面宣布:Spring Cloud Alibaba發布了其開源後的 第二個版本0.2.1 ,隨後,Spring Cloud 官方Twitter也轉發了此消息。聖誕節的前一周,Josh Long向他的老朋友許曉斌發來賀電:

視頻地址:https://spring.io/blog/2018/12/26/spring-tips-bootiful-alibaba

一、新版本概要

Spring Cloud Alibaba RocketMQ

Spring Cloud Alibaba SchelerX

Spring Cloud Alibaba Nacos Config

Spring Cloud Alibaba Nacos Discovery

Spring Cloud Alibaba Sentinel

二、新版本背後的思考

Spring Cloud Alibaba Nacos Discovery

Nacos Discovery 在這個版本最大的更新就是支持在初始化的時候不使用本地文件緩存,目前初始化的時候已經默認不使用本地文件緩存。

為什麼要有緩存?首先我們來了解一下本地緩存的概念,為什麼需要這個本地緩存?

我們都知道,服務注冊與發現應該只是服務調用中的輔助性的一個環節,而不是一個關鍵的環節。一個良好的服務注冊與發現的設計,需要保證以下兩點。

要實現以上兩點,緩存就不可或缺,而為了適應不同的場景,緩存又可以分成內存緩存和本地文件緩存,他們的概念和適用場景如下。

內存中的緩存

將服務提供者列表維護在內存中,每次調用時直接從內存中的列表獲取節點即可。內存緩存通過定時任務更新,或者在收到服務注冊中心的推送之後再更新。確保了即使在服務注冊中心宕機的情況下,也能保證服務仍能正常調用。

本地文件緩存

將上述提到的內存中的緩存,保留在本地的某個文件中,這樣在調用服務注冊中心失敗的時候,可以從本機的文件緩存中獲取服務提供者列表。這樣就保證了在服務注冊中心宕機的情況下,應用在重啟後也能找到服務提供者。

為什麼要關閉

有了以上背景知識後,讀者可能會有疑問,既然緩存這么好,你們為什麼默認要把它置為默認關閉呢?

我們在發布出第一個版本之後,很多用戶反饋,為什麼我服務下線之後還是有節點,仍舊可以被查詢到呢?這樣導致我這個監控數據完全不準,你們這個有 bug,完全不對。其實這是阿里巴巴在多年業務積累的經驗,對於服務發現來說,一個即使是已經過時的節點,也比沒有任何數據好。而且還有可能其實這個服務只是和服務注冊中心失去了心跳,但是應用本身是正常的。

當然,這也暴露了我們設計中存在的一些問題,沒有把服務發現本身,以及緩存的分層給做好,兩者糅合在一塊。所以在這一次的版本更新中我們還是選擇適配開源標准,默認關閉了本地文件緩存,留了一個開關給用戶自由選擇。

Spring Cloud Alibaba Nacos Config

Nacos Config 在這個版本中有兩個大的特性,支持了「共享配置」,修正了動態刷新的語義和行為。

共享配置的實現

在第一個版本還沒發布到時候,社區里對配置管理中心的討論就沒停止過,其中聽到最多的反饋應該就是支持多個應用共享一個配置。我們也通過 github issue 的方式,徵集了很多意見,詳情見 #12 , #141。

後來我們將這個模型抽象了一下,認清這些需求本質是一個應用可以從多個 DataID 和 GroupID 組合中獲取配置,並且這些配置還可以單獨指定優先順序和是否動態刷新。

最後我們推薦了這種使用方式,既保證了使用場景的靈活性,又保證了業務語義的明確性。更多詳情可以參考 WIKI。

注意 data-id 的值必須帶文件擴展名,文件擴展名支持 properties、yaml 和 yml。通過這種自定義擴展的配置項,既可以支持一個應用從多個配置項中獲取數據,也解決多個應用間配置共享的問題。

頭腦風暴,@fudali 同學還提出了更加靈活的一種方式 #161,就是可以通過一個配置項來配置所有的 DataID 的信息,然後可以通過修改這個配置項,可以修改整體配置項的邏輯。

這是一個非常好的想法,只不過這一期中我們沒有實現,原因是這種方式太靈活了。我們認為配置管理其實是一件很嚴肅的事情,太靈活導致生產中變更比較不可控。

雖然目前的邏輯也可以支持這種用法,但是我們並沒有把這種模式當做推薦模式,後續如果社區有更多的反饋認為這是一個強烈的需求,歡迎提 PR。

動態刷新的修正

簡單好用、實時可監控的動態刷新也許是 Nacos Config 相對於其他開源中間件相比最核心的優勢了,不同於 Spring Cloud Config Server 必須使用 Spring Cloud Bus 才能實現動態刷新,Nacos Config 無需依賴其他任何中間件就可以實現實時動態刷新,而且推送成功與否和刷新 歷史 還支持實時查詢。

但是我們發現在第一個版本中的實現發現兩個問題:

在這個版本中,我們修復了這兩個問題。

首先,動態刷新不再是直接去調用 ContextRefresher.refresh() 方法,而是 publish 了一個 RefreshEvent,讓 spring-cloud-commons 里的 RefreshEventListener 去觸發這個 ContextRefresher.refresh() 方法。

其次,我們修正了動態刷新的語義後,這一次是真正做到了,只有 refresh 屬性為 true 的配置項,才會在運行的過程中變更為新的值,refresh 屬性為 false 的配置項再也不用擔心應用在運行的過程中發生莫名其妙的變更了。

更深入一點,在上個月 SpringOne Tour 中,我們和 Spring Cloud 的創始人 Spencer 聊到 Spring Cloud 的 Context.refresh() 成本太高,會刷新整個 Spring Context。他反復強調了兩次 Context.refresh() 只對 @RefreshScope 和 @ConfigurationProperties 有效,成本一點也不高。

之前我們接收到很多社區的反饋都是 Nacos Config 動態刷新支不支持 xxxx,支不支持 xxxx。之前我們都是回答只支持 @RefreshScope 和 @ConfigurationProperties ,如果他內置沒有支持,那就得自己加上相應的註解。

今天我們可以很愉快地回復,他監聽了 RefreshEvent 也能直接支持。而且如果添加 @RefreshScope 和 @ConfigurationProperties 都不滿足你的需求時,可以通過實現自己的 RefreshEventListener 更多高級的玩法。

Spring Cloud Alibaba Sentinel

Sentinel 在這個版本中有三個大的特性:全面支持 FeignClient ,完善了 RestTemplate 的支持,添加了熱點限流、集群限流。

FeignClient 集成 Sentinel

其實在這之前,Sentinel 支持 FeignClient 已經設計了很久了,之前我們的想法做一個兼容性較強的方案,支持 Sentinel 和 Hystrix 在 FeignClient 中同時使用,盡量做到對原有應用的侵入性做到最小。

這個方案我們也思考調研了很久,但是實現難度確實比較大,需要修改 FeignClient 的代碼才能實現兩者共存。正好前段時間在 Spring Cloud 屆最大的新聞就是 Hystrix 宣布不在維護了,於是我們就換了一個思路,直接使用 Sentinel 替代 Hystrix,不再去追求支持兩者共存。

我們實現了自己的 Feign.Builder,在構建的 FeignClient 執行調用的過程中,通過 SentinelInvocationHandler 完成 Sentinel 的流量統計和保護的動作。如果想使用 Sentinel 為 FeignClient 限流降級,首先需要引入 sentinel-starter 的依賴,然後打開 Sentinel 限流降級的開關 feign.sentinel.enabled=true ,就完成了 Sentinel 的接入。如果需要使用更加定製化的功能,則需要在 @FeignClient 添加 fallback 和 configuration 這些屬性的配置。

注意 @FeignClient 註解中的所有屬性,Sentinel 都做了兼容。

RestTemplate 集成 Sentinel

Spring Cloud Alibaba Sentinel 支持對 RestTemplate 的服務調用使用 Sentinel 進行保護,補全了 Hystrix 這一塊的空白。接入的方式也不復雜,在構造 RestTemplate bean 的時候需要加上 @SentinelRestTemplate 註解,然後在控制台配置相應的規則即可。

在觸發了限流降級時,默認的處理方式是返回 RestTemplate request block by sentinel 信息。

RestTemplate 的限流降級 ?Sentinel 也承包了!

熱點參數限流和集群限流

首先解釋一下什麼是熱點參數限流和集群限流。

熱點參數限流會統計傳入參數中的熱點參數,並根據配置的限流閾值與模式,對包含熱點參數的資源調用進行限流。熱點參數限流可以看做是一種特殊的流量控制,僅對包含熱點參數的資源調用生效。

集群流控主要解決的問題是:當我們需要控制整個集群流量總量,但是單機流量可能會不均勻,如果是單機維度去限制的話會無法精確地限制總體流量,因此需要引入集群維度的流量控制。

Sentinel v1.4.0 的 新功能 ,也能第一時間愉快地在 Spring Cloud Alibaba 上使用了。

三、新組件

Spring Cloud Alibaba RocketMQ

Spring Cloud Stream 是一個用於構建基於消息的微服務應用框架,它基於 SpringBoot 來創建具有生產級別的單機 Spring 應用,並且使用 Spring Integration 與 Broker 進行連接。它提供了消息中間件的統一抽象,推出了 publish-subscribe、consumer groups、partition 這些統一的概念。

RocketMQ 是一款開源的分布式消息系統,基於高可用分布式集群技術,提供低延時的、高可靠的消息發布與訂閱服務。具有以下特點:能夠保證嚴格的消息順序、提供豐富的消息拉取模式、高效的訂閱者水平擴展能力、實時的消息訂閱機制、億級消息堆積能力。

在這次的 Spring Cloud Stream Binder RocketMQ 的實現中,我們適配了 Spring Cloud Stream 對於 message 抽象的 API,支持了 RocketMQ 的事務消息。消息訂閱時支持以 tags、SQL 表達式過濾消息,同時還支持順序、並發、延遲以及廣播消費模式。

Spring Cloud Alibaba SchelerX

SchelerX 是阿里中間件團隊開發的一款分布式任務調度產品,提供秒級、精準、高可靠、高可用的定時(基於 Cron 表達式)任務調度服務,同時提供分布式的任務執行模型,如網格任務,網格任務支持海量子任務均勻分配到所有 Worker(schelerx-client)上執行。

簡單易用的輕量分布式任務調度

您不需要關心調度邏輯,只需要在在 JobProcessor 介面的實現中添加業務邏輯即可,然後在自主運維控制台配置上一個 Job 即可完成使用。

高可用的分布式任務

不管是 SchelerX 服務端還是客戶端都是分布式架構設計,任務可以在多台客戶端機器里的任何一台機器執行,如果客戶端出現宕機的情況,服務端會自動選擇正常運行的客戶端去執行 Job,每個 Job 在服務端的不同機器均有備份,SchelerX 服務端任意宕掉部分機器仍能保證 Job 正常調度。

友好的用戶界面

SchelerX 提供了非常友好的頁面方便您創建、刪除或修改 Job,提供了立即觸發執行一次的功能,方便您測試以及關鍵時刻手動立即執行一次,還提供了 歷史 執行記錄查詢的功能,您可以看到任何一個 Job 過去 100 次的 歷史 執行記錄。

功能強大

提供了秒級、精準的定時任務調度服務,且提供了豐富的任務執行模型,包括單機執行,廣播執行,以及子任務的分布式執行。

四、What's Next?

Spring Cloud Alibaba Cloud SLS 針對日誌類數據的一站式服務,在阿⾥巴巴集團經歷大量大數據場景錘煉⽽成。您⽆需開發就能快捷地完成日誌數據採集、消費、投遞以及查詢分析等功能,提升運維、運營效率,建立 DT 時代海量日誌處理能力。

Spring Cloud Alibaba Dubbo Dubbo 是一款流行的開源 RPC 框架,我們會把 Dubbo 整合到 Spring Cloud Alibaba 中,讓大家在開發 Dubbo 時也能享受到 Spring Cloud 帶來的便利。

致謝

Spring Cloud Alibaba 從開源建設以來,受到了很多社區同學的關注。社區的每一個 issue ,每一個 PR,都是對整個項目的幫助,都在為建設更好用的 Spring Cloud 添磚加瓦。

↓↓↓

⑹ 該怎麼解決 Redis 緩存穿透和緩存雪崩問題

緩存雪崩: 由於緩存層承載著大量請求,有效地 保護了存儲層,但是如果緩存層由於某些原因不能提供服務,比如 Redis 節點掛掉了,熱點 key 全部失效了,在這些情況下,所有的請求都會直接請求到資料庫,可能會造成資料庫宕機的情況。
預防和解決緩存雪崩問題,可以從以下三個方面進行著手:
1、使用 Redis 高可用架構:使用 Redis 集群來保證 Redis 服務不會掛掉
2、緩存時間不一致: 給緩存的失效時間,加上一個隨機值,避免集體失效
3、限流降級策略:有一定的備案,比如個性推薦服務不可用了,換成熱點數據推薦服務
緩存穿透: 緩存穿透是指查詢一個根本不存在的數據,這樣的數據肯定不在緩存中,這會導致請求全部落到資料庫上,有可能出現資料庫宕機的情況。
預防和解決緩存穿透問題,可以考慮以下兩種方法:
1、緩存空對象: 將空值緩存起來,但是這樣就有一個問題,大量無效的空值將佔用空間,非常浪費。
2、布隆過濾器攔截: 將所有可能的查詢key 先映射到布隆過濾器中,查詢時先判斷key是否存在布隆過濾器中,存在才繼續向下執行,如果不存在,則直接返回。布隆過濾器有一定的誤判,所以需要你的業務允許一定的容錯性。

⑺ 一次限流的引發思考

一個api介面 /srm/api2/disabletime 需要提供最大600qps的能力,超過600qps之後需要進行限流,返回429 http code。

現在業務節點一共20台,為了異地多活,分布在2機房gz和gz6,倆機房各十台機器上。

服務限流通常有以下倆種方式:

倆種方式各有優點和缺點

本次實驗就是使用的第一種方式,為了保證qps達到600之後能限流,所以理想情況下(也就是流量分部均勻的情況)每台機器的qps為30=600/20,但是實際情況發現,每天機器上的該api的請求量非常的不均,有的是個位數,有的達到了50多個。

可能有人有疑惑,每個節點流量不均有什麼影響呢?那我解釋下。當整個服務的流量來了600qps時,由於流量不均,有的節點流量分布是40或者50的請求量,有的節點是個位數8,5的請求量。現在每台機器的配置的qps為30,那麼大於30qps的那些節點就出觸發限流了,最終導致整個服務沒有達到600qps時就觸發限流了。

想要了解流量為什麼不均勻,需要首先搞清楚業務整體架構。

最初理解的架構如下,可以很明顯的發現,在gz和gz6倆機房的slb數量不一致,也就導致了gz的業務機器流量大於gz6的業務機器流量。
理論上 ,gz 的 slb流量會發給 gz 的 nginx,gz6 的slb流量會發給 gz6 的 nginx。騰訊雲的負載均衡,發給 slb 的流量,不區分 gz 與 gz6 ,在所有 slb 的機器里執行輪詢策略的負載均衡。
那麼gz 的 slb 機器數量比 gz6 多,那 gz機房獲得的流量就比 gz6機房多, gz 的 nignx獲得的流量就比 gz6的nginx多。

上面解釋看似合理,結論就是:由於gz和gz6倆機房的業務節點數量相同,而gz和gz6的slb的數量不同, 最終導致了倆機房的流量不均,進而導致了gz和gz6的業務節點的流量不均

下圖是對介面壓測2分鍾,每秒600qps的流量趨勢圖,可以出在gz和gz6的流量大致分部均勻,大致在300Qps左右,這也就間接的證明了上面的想法不對。

上面倆張的流量分部印證了上面的架構是不對的,通過查詢,正確的架構如下,可以看出和上面的架構圖有幾點不同

架構看起來是沒問題,流量到最終端的業務節點也是均勻的,但是事實就是流量是不均勻的,我們結合實際情況來看下,比如輪訓業務節點為a->b->c:
首先slb的輪訓 據指定url來輪訓 的,是 根據整個服務的所有請求 的來輪訓的 ,比如第一個壓測請求打到的是a業務節點,如果當前時間整個服務沒有別的請求,那麼第二個壓測請求肯定會打到b業務節點,但實際情況是,當前時間肯定有別的請求,所以第二個壓測請求時可能打到的還是a業務節點,也可能是b業務節點,可能是集群中的任何一個業務節點,我猜測就是這個原因導致了最終的流量不均勻。

為了證明這個猜想,我們可以看看 21:49:00 這個時間點所有機器上的所有請求是不是大致平均的,如果是平均的,則證明我們猜想正確。

可以看出所有機器的請求量大致基本相同,我們的猜想正確。

使用第一種方式通常有個大前提: 是各個機器流量分布必須非常均勻的,每台機器配置的qps=總qps/機器節點數量。但是由於網路總是不穩定性或者其他原因通常流量是不均勻的,所以需要每台節點配置的qps加一些Buffer,40或者50qps

如果使用第二種方式的話,我們直接在redis配置600qps即可,因為不需要關注每台機器流量的流量分布,管你節點的流量是50還8呢,只要總和大於600qps後,服務就會觸發限流了。

如果第二種方式能夠實現,建議使用第二種方式。

如何配置Kettle集群運行環境

Kettle是一款開源的ETL工具,以其高效和可擴展性而聞名於業內。其高效的一個重要原因就是其多線程和集群功能。
Kettle的多線程採用的是一種流水線並發的機制,我們在另外的文章中專門有介紹。這里主要介紹的是kettle的集群。
集群允許轉換以及轉換中的步驟在多個伺服器上並發執行。在使用kettle集群時,首先需要定義的是Cluster schema。所謂的Cluster schema就是一系列的子伺服器的集合。在一個集群中,它包含一個主伺服器(Master)和多個從屬伺服器伺服器(slave)。如下圖所示:
子伺服器(Slave servers)允許你在遠程伺服器上執行轉換。建立一個子伺服器需要你在遠程伺服器上建立一個叫做「Carte」的 web 伺服器,該伺服器可以從Spoon(遠程或者集群執行)或者轉換任務中接受輸入。
在以後的描述中,如果我們提到的是子伺服器,則包括集群中的主伺服器和從屬伺服器;否則我們會以主伺服器和從屬伺服器來進行特別指定。

選項 描述
伺服器名稱
子伺服器的名稱
主機名稱或IP地址
用作子伺服器的機器的地址
埠號
與遠程服務通信的埠號
用戶名
獲取遠程伺服器的用戶名
密碼
獲取遠程伺服器的密碼
是主伺服器嗎
在轉換以集群形式執行時,該子伺服器將作為
注意: 在集群環境下執行轉化時,你必須有一個子伺服器作為主伺服器(master server)而其餘所有的子伺服器都作從屬伺服器(slave server)

選項 描述
代理伺服器主機名
設置你要通過代理進行連接的主機名
代理伺服器埠
設置與代理進行連接時所需的埠號
Ignore proxy for hosts: regexp|separated
指定哪些伺服器不需要通過代理來進行連接。該選項支持你使用正則表達式來制定多個伺服器,多個伺服器之間以' | ' 字元來進行分割
創建cluster schema
選項 描述
Schema 名稱
集群schema的名稱
埠號
這里定義的埠號是指從哪一個埠號開始分配給子伺服器。每一個在子伺服器中執行的步驟都要消耗一埠號。注意: 確保沒有別的網路協議會使用你定義的范圍之類的埠,否則會引起問題
Sockets緩存大小
TCP內部緩存的大小
Sockets刷新間隔(rows)
當TCP的內部緩存通過網路完全發送出去並且被清空時處理的行數
Sockets數據是否壓縮
如果該選項被選中,則所有的數據都會使用Gzip壓縮演算法進行壓縮以減輕網路傳輸量
Dynamic Cluster
動態集群指的是在運行的時候才能獲知從屬伺服器的信息。這種情形適用於主機可以自動增加或者去除的情形,例如雲計算。
主伺服器的設置不變,但是它可以接受從屬伺服器的注冊。一旦接受了某個從屬伺服器的注冊,則每隔30秒去監視該從屬伺服器是否還處於有效狀態
子伺服器
這里是一個要在集群中使用的伺服器列表。這個列表中包含一個主伺服器和任意數目的從屬伺服器。
在dynamic Cluster的情況下,只需要選擇主伺服器即可

定義轉換
定義完了 cluster schema 後,下一步就是定義在集群環境下執行的轉換。我們這里展現的只是一個最簡單的例子,完全是為了演示而用。現實情況中的集群有可能非常復雜。
首先你像平時一樣創建轉換,以hop連接連個兩個步驟。然後你指定第二個步驟將在集群下執行
然後選擇需要使用的集群。轉換如圖一樣顯示在GUI中。
注意 Cx4顯示這個步驟將在集群中運行,而這個集群中有4個從屬伺服器。假設我們將計算結果再次存入到數據表中
這個轉換雖然定義了集群,但是我們同樣可以讓它在單機環境下執行,而且可以得到相同的結果。這意味著你可以使用普通的本地模式來測試它。
執行轉換
要想以集群方式來運行轉換或者作業,首先需要啟動在Cluster schema中定義的主伺服器和從屬伺服器,然後再運行轉換或者作業。
啟動子伺服器
子伺服器其實是一個嵌入式的名為 Carte 的 小web server。要進行集群轉換,首先需要啟動cluster schema中的子伺服器
腳本啟動
kettle 提供了 carte.bat 和 carte.sh ( linux )批處理腳本來啟動子伺服器,這種啟動方式分為兩種
使用主機號和埠號

Carte 127.0.0.1 8080
Carte 192.168.1.221 8081
使用配置文件

Carte /foo/bar/carte-config.xml
Carte 網站/carte-config.xml

⑼ 分布式解決方案之:限流

限流在日常生活中限流很常見,例如去有些景區玩,每天售賣的門票數是有限的,例如 2000 張,即每天最多隻有 2000 個人能進去遊玩。那在我們工程上限流是什麼呢?限制的是 「流」,在不同場景下「流」的定義不同,可以是 每秒請求數、每秒事務處理數、網路流量 等等。通常意義我們說的限流指代的是限制到達系統的並發請求數,使得系統能夠正常的處理部分用戶的請求,來保證系統的穩定性。

日常的業務上有類似秒殺活動、雙十一大促或者突發新聞等場景,用戶的流量突增,後端服務的處理能力是有限的,如果不能處理好突發流量,後端服務很容易就被打垮。另外像爬蟲之類的不正常流量,我們對外暴露的服務都要以最大惡意為前提去防備調用者。我們不清楚調用者會如何調用我們的服務,假設某個調用者開幾十個線程一天二十四小時瘋狂調用你的服務,如果不做啥處理咱服務基本也玩完了,更勝者還有ddos攻擊。

對於很多第三方開放平台來說,不僅僅要防備不正常流量,還要保證資源的公平利用,一些介面資源不可能一直都被一個客戶端占著,也需要保證其他客戶端能正常調用。

計數器限流也就是最簡單的限流演算法就是計數限流了。例如系統能同時處理 100 個請求,保存一個計數器,處理了一個請求,計數器就加一,一個請求處理完畢之後計數器減一。每次請求來的時候看看計數器的值,如果超過閾值就拒絕。計數器的值要是存內存中就算單機限流演算法,如果放在第三方存儲里(例如Redis中)集群機器訪問就算分布式限流演算法。

一般的限流都是為了限制在指定時間間隔內的訪問量,因此還有個演算法叫固定窗口。

它相比於計數限流主要是多了個時間窗口的概念,計數器每過一個時間窗口就重置。規則如下:

這種方式也會面臨一些問題,例如固定窗口臨界問題:假設系統每秒允許 100 個請求,假設第一個時間窗口是 0-1s,在第 0.55s 處一下次湧入 100 個請求,過了 1 秒的時間窗口後計數清零,此時在 1.05 s 的時候又一下次湧入100個請求。雖然窗口內的計數沒超過閾值,但是全局來看在 0.55s-1.05s 這 0.1 秒內湧入了 200 個請求,這其實對於閾值是 100/s 的系統來說是無法接受的。

為了解決這個問題,業界又提出另外一種限流演算法,即滑動窗口限流。

滑動窗口限流解決固定窗口臨界值的問題,可以保證在任意時間窗口內都不會超過閾值。相對於固定窗口,滑動窗口除了需要引入計數器之外還需要記錄時間窗口內每個請求到達的時間點,因此對內存的佔用會比較多。

規則如下,假設時間窗口為 1 秒:

但是滑動窗口和固定窗口都無法解決短時間之內集中流量的沖擊問題。 我們所想的限流場景是: 每秒限制 100 個請求。希望請求每 10ms 來一個,這樣我們的流量處理就很平滑,但是真實場景很難控制請求的頻率,因為可能就算我們設置了1s內只能有100個請求,也可能存在 5ms 內就打滿了閾值的情況。當然對於這種情況還是有變型處理的,例如設置多條限流規則。不僅限制每秒 100 個請求,再設置每 10ms 不超過 2 個,不過帶來的就是比較差的用戶體驗。

而漏桶演算法,可以解決時間窗口類的痛點,使得流量更加平滑。

如下圖所示,水滴持續滴入漏桶中,底部定速流出。如果水滴滴入的速率大於流出的速率,當存水超過桶的大小的時候就會溢出。

規則如下:

水滴對應的就是請求。

與線程池實現的方式方式如出一轍。

面對突發請求,服務的處理速度和平時是一樣的,這並非我們實際想要的。我們希望的是在突發流量時,在保證系統平穩的同時,也要盡可能提升用戶體驗,也就是能更快地處理並響應請求,而不是和正常流量一樣循規蹈矩地處理。

而令牌桶在應對突擊流量的時候,可以更加的「激進」。

令牌桶其實和漏桶的原理類似,只不過漏桶是定速地流出,而令牌桶是定速地往桶里塞入令牌,然後請求只有拿到了令牌才能通過,之後再被伺服器處理。

當然令牌桶的大小也是有限制的,假設桶里的令牌滿了之後,定速生成的令牌會丟棄。

規則:

令牌桶的原理與JUC的Semaphore 信號量很相似,信號量可控制某個資源被同時訪問的個數,其實和拿令牌思想一樣,不同的是一個是拿信號量,一個是拿令牌。信號量用完了返還,而令牌用了不歸還,因為令牌會定時再填充。

對比漏桶演算法可以看出 令牌桶更適合應對突發流量 ,假如桶內有 100 個令牌,那麼這100個令牌可以馬上被取走,而不像漏桶那樣勻速的消費。不過上面批量獲取令牌也會致使一些新的問題出現,比如導致一定范圍內的限流誤差,舉個例子你取了 10 個此時不用,等下一秒再用,那同一時刻集群機器總處理量可能會超過閾值,所以現實中使用時,可能不會去考慮redis頻繁讀取問題,轉而直接採用一次獲取一個令牌的方式,具體採用哪種策略還是要根據真實場景而定。

1、計數器 VS 固定窗口 VS 滑動窗口

2、漏桶演算法 VS 令牌桶演算法

總的來說

單機限流和分布式限流本質上的區別在於 「閾值」 存放的位置,單機限流就是「閥值」存放在單機部署的服務/內存中,但我們的服務往往是集群部署的,因此需要多台機器協同提供限流功能。像上述的計數器或者時間窗口的演算法,可以將計數器存放至 Redis 等分布式 K-V 存儲中。又如滑動窗口的每個請求的時間記錄可以利用 Redis 的 zset 存儲,利用 ZREMRANGEBYSCORE 刪除時間窗口之外的數據,再用 ZCARD 計數,

可以看到,每個限流都有個閾值,這個閾值如何定是個難點。定大了伺服器可能頂不住,定小了就「誤殺」了,沒有資源利用最大化,對用戶體驗不好。一般的做法是限流上線之後先預估個大概的閾值,然後不執行真正的限流操作,而是採取日誌記錄方式,對日誌進行分析查看限流的效果,然後調整閾值,推算出集群總的處理能力,和每台機子的處理能力(方便擴縮容)。然後將線上的流量進行重放,測試真正的限流效果,最終閾值確定,然後上線。

其實真實的業務場景很復雜,需要限流的條件和資源很多,每個資源限流要求還不一樣。

一般而言,我們不需要自己實現限流演算法來達到限流的目的,不管是接入層限流還是細粒度的介面限流,都有現成的輪子使用,其實現也是用了上述我們所說的限流演算法。

具體的使用還是很簡單的,有興趣的同學可以自行搜索,對內部實現感興趣的同學可以下個源碼看看,學習下生產級別的限流是如何實現的。

限流具體應用到工程還是有很多點需要考慮的,並且限流只是保證系統穩定性中的一個環節,還需要配合降級、熔斷等相關內容。

⑽ redis常見問題

1. 緩存擊穿

緩存擊穿是指一個請求要訪問的數據,緩存中沒有,但資料庫中有的情況。這種情況一般都是緩存過期了。

但是這時由於並發訪問這個緩存的用戶特別多,這是一個熱點 key,這么多用戶的請求同時過來,在緩存裡面沒有取到數據,所以又同時去訪問資料庫取數據,引起資料庫流量激增,壓力瞬間增大,直接崩潰給你看。

所以一個數據有緩存,每次請求都從緩存中快速的返回了數據,但是某個時間點緩存失效了,某個請求在緩存中沒有請求到數據,這時候我們就說這個請求就"擊穿"了緩存。

針對這個場景,對應的解決方案一般來說有三種。

藉助Redis setNX命令設置一個標志位就行。設置成功的放行,設置失敗的就輪詢等待。就是在更新緩存時加把鎖

後台開一個定時任務,專門主動更新過期數據

比如程序中設置 why 這個熱點 key 的時候,同時設置了過期時間為 10 分鍾,那後台程序在第 8 分鍾的時候,會去資料庫查詢數據並重新放到緩存中,同時再次設置緩存為 10 分鍾。

其實上面的後台續命思想的最終體現是也是永不過期。

只是後台續命的思想,會主動更新緩存,適用於緩存會變的場景。會出現緩存不一致的情況,取決於你的業務場景能接受多長時間的緩存不一致。


2. 緩存穿透

緩存穿透是指一個請求要訪問的數據,緩存和資料庫中都沒有,而用戶短時間、高密度的發起這樣的請求,每次都打到資料庫服務上,給資料庫造成了壓力。一般來說這樣的請求屬於惡意請求。

解決方案有兩種:

就是在資料庫即使沒有查詢到數據,我們也把這次請求當做 key 緩存起來,value 可以是 NULL。下次同樣請求就會命中這個 NULL,緩存層就處理了這個請求,不會對資料庫產生壓力。這樣實現起來簡單,開發成本很低。


3. 緩存雪崩

緩存雪崩是指緩存中大多數的數據在同一時間到達過期時間,而查詢數據量巨大,這時候,又是緩存中沒有,資料庫中有的情況了。

防止雪崩的方案簡單來說就是錯峰過期。

在設置 key 過期時間的時候,在加上一個短的隨機過期時間,這樣就能避免大量緩存在同一時間過期,引起的緩存雪崩。

如果發了雪崩,我們可以有服務降級、熔斷、限流手段來拒絕一些請求,保證服務的正常。但是,這些對用戶體驗是有一定影響的。

4. Redis 高可用架構

Redis 高可用架構,大家基本上都能想到主從、哨兵、集群這三種模式。

哨兵模式:

它主要執行三種類型的任務:

哨兵其實也是一個分布式系統,我們可以運行多個哨兵。

然後這些哨兵之間需要相互通氣,交流信息,通過投票來決定是否執行自動故障遷移,以及選擇哪個從伺服器作為新的主伺服器。

哨兵之間採用的協議是 gossip,是一種去中心化的協議,達成的是最終一致性。

選舉規則: