想抗住千萬級流量?你應(yīng)該這樣做!
老板讓你抗住千萬級流量,如何做架構(gòu)設(shè)計?首先,要在我們架構(gòu)設(shè)計的時候建立一些原則。
1實現(xiàn)高并發(fā)
服務(wù)拆分: 將整個項目拆分成多個子項目或者模塊,分而治之,將項目進行水平擴展。
服務(wù)化: 解決服務(wù)調(diào)用復(fù)雜之后的服務(wù)的注冊發(fā)現(xiàn)問題。消息隊列: 解耦,異步處理緩存: 各種緩存帶來的并發(fā)
2實現(xiàn)高可用
集群、限流、降級
3業(yè)務(wù)設(shè)計
冪等: 就是用戶對于同一操作發(fā)起的一次請求或者多次請求的結(jié)果是一致的,不會因為多次點擊而產(chǎn)生了副作用,就像數(shù)學(xué)里的數(shù)字1,多少次冪的結(jié)果都是1。舉個最簡單的例子,那就是支付,用戶購買商品后支付,支付扣款成功,但是返回結(jié)果的時候網(wǎng)絡(luò)異常,此時錢已經(jīng)扣了,用戶再次點擊按鈕,此時會進行第二次扣款,返回結(jié)果成功,用戶查詢余額發(fā)現(xiàn)多扣錢了,流水記錄也變成了兩條。
防重: 防止同樣的數(shù)據(jù)同時提交
除了在業(yè)務(wù)方向判斷和按鈕點擊之后不能繼續(xù)點擊的限制以外,在服務(wù)器端也可以做到防重:
在服務(wù)器端生成一個唯一的隨機標(biāo)識號(Token<令牌>)同事在當(dāng)前用戶的Session域中保存這個令牌,然后將令牌發(fā)送到客戶端的form表單中,在form表單中使用隱藏域來存儲這個Token,表單提交的時候聯(lián)通這個Token一起提交到服務(wù)器,然后在服務(wù)器端判斷客戶提交上來的Token與服務(wù)器端生成的Token是否一致,如果不一致,那就重復(fù)提交了,此時服務(wù)器端就可以不處理重復(fù)提交的表單,如果相同則處理表單,處理完后清楚當(dāng)前用戶的Session域中存儲的標(biāo)識號。高可用高并發(fā)架構(gòu)參考:高可用高并發(fā)的 9 種技術(shù)架構(gòu)。
在下列情況中,服務(wù)器程序?qū)⒕芙^處理用戶提交的表單請求: 1)存儲Session域中的Token與表單提交的Token不一致 2)當(dāng)前用戶的Session中不存在Token 3)用戶提交的表單數(shù)據(jù)中沒有Token。
狀態(tài)機
軟件設(shè)計中的狀態(tài)機概念,一般是指有限狀態(tài)機(英語:finite-state machine,縮寫:FSM)又稱有限狀態(tài)自動機,簡稱狀態(tài)機,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。
這里著重講一下限流的概念和例子
限流的目的限流的目的是通過對并發(fā)訪問/請求進行限速或者一個時間窗口內(nèi)的請求進行限速來保護系統(tǒng)的可用性,一旦達(dá)到限制速率就可以拒絕服務(wù)。就像手機預(yù)售一樣,假如要賣出3萬臺,只需要接收3萬用戶的請求就可以,其他的用戶請求可以選擇過濾,可以提示"當(dāng)前服務(wù)器過忙,請稍后再試"的提示。推薦大家看這篇文章:接口限流算法:漏桶算法&令牌桶算法。
限流方式:
1. 限制瞬時并發(fā)數(shù) :比如在入口層(nginx添加nginxhttplimitconnmodule)來限制同一個ip來源的連接數(shù),防止惡意攻擊訪問的情況。
2. 限制總并發(fā)數(shù):通過配置數(shù)據(jù)庫連接池、線程池大小來約束總并發(fā)數(shù)
3. 限制時間窗口內(nèi)的平均速率:在接口層面,通過限制訪問速率來控制接口的并發(fā)請求。
4. 其他方式:限制遠(yuǎn)程接口的調(diào)用速率、限制MQ的消費速率。
常用限流算法
1. 滑動窗口協(xié)議:一種常見的流量控制技術(shù),用來改善吞吐量的技術(shù)。
滑動窗口協(xié)議的由來:
滑動窗口(sliding window)是一種流量控制技術(shù)。早期的網(wǎng)絡(luò)通訊中,通信雙方不會考慮網(wǎng)絡(luò)的擁擠情況直接發(fā)送數(shù)據(jù)。由于大家不知道網(wǎng)絡(luò)擁塞狀況,同時發(fā)送數(shù)據(jù),導(dǎo)致中間節(jié)點阻塞掉包,誰也發(fā)送不了數(shù)據(jù),所以就有了滑動窗口機制來解決此問題。 發(fā)送和接收方都會維護一個數(shù)據(jù)幀的序列,這個序列被稱為窗口。
定義:滑動窗口協(xié)議(Sliding Window Protocol),屬于TCP協(xié)議的一種應(yīng)用,用于網(wǎng)絡(luò)數(shù)據(jù)傳輸時的流量控制,以避免擁塞的發(fā)生。該協(xié)議允許發(fā)送方在停止并等待確認(rèn)前發(fā)送多個數(shù)據(jù)分組。由于發(fā)送方不必每發(fā)一個分組就停下來等待確認(rèn),因此該協(xié)議可以加速數(shù)據(jù)的傳輸,提高網(wǎng)絡(luò)吞吐量。
發(fā)送窗口:就是發(fā)送端允許連續(xù)發(fā)送的幀的序號表。發(fā)送端可以不等待應(yīng)答而連續(xù)發(fā)送數(shù)據(jù)(可以通過設(shè)置窗口的尺寸來控制)
接收窗口:接收方允許接收的幀的序列表,凡是落在接收窗口內(nèi)的幀,接收方都必須處理,落在接收窗口外的幀將被丟棄。接收方每次允許接收的幀數(shù)稱為接收窗口的尺寸
演示地址:https://media.pearsoncmg.com/aw/ecskurosecompnetwork_7/cw/content/interactiveanimations/selective-repeat-protocol/index.html
2. 漏桶:漏桶算法能強行限制數(shù)據(jù)的傳輸速率。
漏桶算法思路很簡單,請求先進入到漏桶里,漏桶以一定的速度出水。當(dāng)水請求過大會直接溢出,可以看出漏桶算法能強行限制數(shù)據(jù)的傳輸速率。進入端無需考慮出水端的速率,就像mq消息隊列一樣,provider只需要將消息傳入隊列中,而不需要關(guān)心Consumer是否接收到了消息。
對于溢出的水,就是被過濾的數(shù)據(jù),可以直接被丟棄,也可以通過某種方式暫時保存,如加入隊列之中,像線程池里對溢出數(shù)據(jù)的4種處理機制一樣
3. 令牌桶:屬于控制速率類型的限流算法。
對于很多應(yīng)用場景來說,除了要求能夠限制數(shù)據(jù)的平均傳輸速率外,還要求允許某種程度的突發(fā)傳輸。這時候漏桶算法可能就不合適了,令牌桶算法更為適合。令牌桶算法的原理是系統(tǒng)會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當(dāng)桶里沒有令牌可取時,則拒絕服務(wù)。
設(shè)置 Rate = 2 :每秒放入令牌的個數(shù)
桶的大?。?00
這里用一個小demo來實現(xiàn)一下令牌桶
- public class TokenDemo {
- //qps:每秒鐘處理完請求的次數(shù);tps:每秒鐘處理完的事務(wù)次數(shù)
- //代表qps是10;
- RateLimiter rateLimiter = RateLimiter.create(10);
- public void doSomething(){
- if (rateLimiter.tryAcquire()){
- //嘗試獲得令牌.為true則獲取令牌成功
- System.out.println("正常處理");
- }else{
- System.out.println("處理失敗");
- }
- }
- public static void main(String args[]) throwsIOException{
- /*
- * CountDownLatch是通過一個計數(shù)器來實現(xiàn)的,計數(shù)器的初始值為線程的數(shù)量,此值是線程將要等待的操作數(shù)(線程的數(shù)量)。
- * 當(dāng)某個線程為了想要執(zhí)行這些操作而等待時, 它要使用 await()方法。
- * 此方法讓線程進入休眠直到操作完成。
- * 當(dāng)某個操作結(jié)束,它使用countDown() 方法來減少CountDownLatch類的內(nèi)部計數(shù)器,計數(shù)器的值就會減1。
- * 當(dāng)計數(shù)器到達(dá)0時,它表示所有的線程已經(jīng)完成了任務(wù),這個類會喚醒全部使用await() 方法休眠的線程們恢復(fù)執(zhí)行任務(wù)。
- *
- * */
- CountDownLatch latch = new CountDownLatch(1);
- Random random = new Random(10);
- TokenDemo tokenDemo = new TokenDemo();
- for (int i=0;i<20;i++){
- new Thread(()->{
- try {
- latch.await();
- Thread.sleep(random.nextInt(1000));
- tokenDemo.doSomething();
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }).start();
- }
- latch.countDown();
- System.in.read();
- }
- }
執(zhí)行結(jié)果:
- 正常處理
- 正常處理
- 正常處理
- 正常處理
- 正常處理
- 處理失敗
- 正常處理
- 處理失敗
- 處理失敗
- 處理失敗
- 正常處理
- 處理失敗
- 正常處理
- 處理失敗
- 正常處理
- 正常處理
- 正常處理
- 正常處理
- 處理失敗
- 處理失敗
由此可見,當(dāng)令牌不足時,會獲取令牌失敗,達(dá)到限流的效果。