小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

干貨 | Python后臺開發(fā)的高并發(fā)場景優(yōu)化解決方案

 風聲之家 2019-07-10

嘉賓 | 黃思涵

來源 | AI科技大本營在線公開課

互聯(lián)網發(fā)展到今天,規(guī)模變得越來越大,也對所有的后端服務提出了更高的要求。在平時的工作中,我們或多或少都遇到過服務器壓力過大問題。針對該問題,本次公開課邀請到了金山辦公AI平臺研發(fā)工程師黃思涵,他分享的主題是《Python后臺開發(fā)的高并發(fā)場景優(yōu)化解決方案》,為大家講解不同業(yè)務場景下這類問題的解決思路和方案。本文是直播公開課的速記版,視頻回放請見↓↓↓

1 背景

我們看一下今天的課程內容。首先我們來看一組數(shù)據。第一個數(shù)據是春晚紅包,相信大家在今年春晚,大家已經用過這個功能;互動游戲數(shù)據是互動次數(shù)達到了208億次。在晚上的短短四個小時之內,去完成了這樣的請求量,說明這個互聯(lián)網的規(guī)模是非常大的。把它平攤到14.8億人的頭上,平均每個人也是點擊了14.8次,這當中還沒有算上這些非網民的次數(shù),所以說,整個互聯(lián)網的規(guī)模是非常龐大的。

再接下來,我們再看一個雙十一的一個數(shù)據,這個雙十一的訂單量,在去年數(shù)據是13.52億訂單量,我們再看一下,在這個屏幕,左邊這里有一個圖,從2014年,它大概是1點多億件,到2015年會有一個增長,到了四點多億件,到2016年,這邊是逐漸的增長,直到2018年增長到了一個13億次的一個訂單量,那么有了這樣的訂單量,大家可以看到互聯(lián)網規(guī)模的迅速發(fā)展,它是逐年在增加的,說明發(fā)展速度是非常快。

接下來,我們再看一個數(shù)據,那么就是微信,也是平時大家用的比較多的一個軟件。微信的日活可以達到十個億,相應的一個請求量,可能比這個更多。那么看這些數(shù)據,它有一個什么意義?這樣的一些數(shù)據,它就說明了,我們的互聯(lián)網的一個規(guī)模是非常龐大的。而且它的增長也是非??斓?。這就引出了今天的一個問題,那么也許大家平時在一些開發(fā)過程當中,或者在大家自己的遇到的一個業(yè)務場景當中,沒有前面那么多比較恐怖的數(shù)據,但是,大家知道,公司要去增長,我們的業(yè)務要增長,業(yè)務的增長,就意味著用戶量會去增加,用戶量增加就導致了一個請求量的增加。請求量的增加對我們開發(fā)人員來講,或者對我們后臺的服務器來講的最直接的反映,就是服務器壓力的增加。那么壓力的增加就意味著對服務器來講,它的一個請求量到達了一個比較高的水平,或者說它的壓力呢,已經不能夠繼續(xù)往上增加了,那么請求量已經到達了一個瓶頸。

接下來,我們看一下,它會導致的直接的問題是什么?大家可以看到這樣一個簡單的模型。左邊是一個客戶端,右邊是一個服務端??蛻舳讼蚍斩税l(fā)出請求,當服務端壓力過大的時候,對服務器來說,那么就是請求太多,來不及處理,甚至是這樣的請求呢,它還會被丟棄掉。但是,對于這個客戶端來講,或者是使用這個客戶端的一個用戶來講,它可能面臨的一個問題,就是等待時間過長,或者是出現(xiàn)錯誤,對客戶來講,客戶端來講,他并不知道,你這個后臺是面臨著這樣大的一個壓力,或者是你能處理的請求是多大的一個數(shù)量,他只能直觀感受到,我這個請求時間長,或者我這個請求有問題。這在我們的業(yè)務場景當中都是不能接受的。

所以,今天我就將跟大家去探討一下,如何盡可能多的去處理這樣的一個客戶端的請求,對于我們來講,這個服務端的壓力大,最簡單的來講就是資源不足。我們可能第一印象就是這個資源不足,那么資源不足呢,非常簡單,就是去提升硬件配置,比如說這里CPU,或者是內存,直接去升級硬件就可以了,這樣是一個簡單的處理方式。但大家有沒有想過,還會有一種問題,這種場景下面,就是當我資源充足的情況下,我的一個服務器的請求依然不能夠進行提升,或者請求還依然維持在一個比較低的水平,不能夠將我所有的資源進行一個很好的利用。這就存在著一個浪費的問題,它的性價比呢,就是不高的。

2 優(yōu)化思路和方案

接下來,我們今天呢,主要就是針對下面的這種情況,會去給大家分享我們優(yōu)化的思路和方案。說到服務端的壓力大,最簡單的可能想到的一個原因,就是CPU。畢竟CPU在計算機當中處于一個非常核心的位置。

查看CPU使用情況

我們先來看一下CPU。我使用TOP命令在這個主機上,去查看CPU的使用情況。在這里,我截了一個圖,這個圖中,CPU使用率是接近于百分之百,說明CPU,已經是完全被代碼,被測試程序是完全占滿了。所以它就導致了請求量無法去再進行一個提升。它也就是說明我這個資源不足了。如何解決呢?那么就是去增加資源,去升級這樣的一個CPU,這就是我講的一個比較簡單的一個場景。

大家有沒有想過另外一種情況,假如說我的CPU它的利用率是在非常低的水平,比如這里,它只有這樣一個大概1/10的一個利用率,但是在這個情況下,我的一個請求數(shù)量依然不能夠增加,我的服務器的壓力依然很大。如果在這種情況下,要去給老板說,我這個請求無法提升,我需要去增加配置,這樣的話,老板看到這樣的情況肯定不會很滿意,因為你資源明顯沒有占滿,你還要去申請這樣的一個機器。我們繼續(xù)去查看它的詳情,在這里我使用這個TOP命令,在這個之后繼續(xù)去按一個一,就可以展開四個CPU的一個詳情,這里呢,我的CPU是四核心的,看到這里有四個CPU的核心數(shù)在這里運行,大家可以看到在這里,第一個確實是占滿了第一個核心,但是呢,后面的三個是在這里,空閑的,這說明了一個什么問題呢?就是在前面的這個程序當中,實際上它對整個CPU的一個利用,只是用了它的一部分,而沒有全部用,沒有全部去用上,那么這種情況,解決方案就變得比較簡單了。大家可以想一下,如何去解決呢?很顯然,就是要將這樣的一個CPU,其他的核心數(shù),全部用起來。另外在現(xiàn)在的CPU幾乎都是多核心的,這里我們要盡可能的多的使用我們已有的資源。這里有同學說的多進程,所以我們就可以考慮使用多進程,將所有的CPU去給它利用起來,盡可能的多的使用我們已有的資源。

多進程

        

接下來,我使用Python去給大家講解一下,如何去使用多進程,來利用這樣的一個資源。這里我貼了一段代碼,這個代碼是Python去實現(xiàn)的一個多進程,然后來利用多核CPU完成任務。

        

這里我大概給大家講一下。在Python當中,我們使用multiprocessing這個庫去完成這樣的一個多進程,這里有一個-1大家要注意一下,通常我們會去留下這么一些核心數(shù),去用作其他的一些任務,防止整個所有的核心數(shù)被占滿導致其他的任務不能夠正常運行。這里算出一個核心的CPU數(shù)。下面我是用了一個進程池,進程池大概是一個什么概念呢?這里用了Pool函數(shù),這個函數(shù),會創(chuàng)建出這樣一個進程池。創(chuàng)建出了這樣的進程池之后,下面它會在當中去加入這樣的一些任務,這個Task它實際上是寫的一個函數(shù)。在這里,就將它加入到這樣的一個進程池當中。

實際上,它是異步加入的,會有一個隊列,這些任務都會被在非常短的時間內加載到下面的這樣的一個隊列當中,由于這上面已經有了進程池,這個multiprocessing幫我們實現(xiàn)好了。它會去創(chuàng)建好一些進程,創(chuàng)建好之后,它會自動去把這些加入進來的任務呢,放到這個進程當中去進行調度,然后執(zhí)行。當前面的一些執(zhí)行完了之后,后面的議程會再次被調度進去,這個進程池它相對于普通的直接去創(chuàng)建進程的方式有什么好處呢?如果我們直接去創(chuàng)建這樣進程,比如有一個任務,我就創(chuàng)建一個進程,然后當這個任務執(zhí)行結束之后呢,我再銷毀這個進程,這樣創(chuàng)建和一個刪除的過程都會有這樣一個開銷,這里使有進程池,它就會很好的去解決這樣一個問題。接下來,Close,就是去當你不需要加入的時候,就對它進行一個Close掉,Join在這里是進行一個阻塞等待,這里給大家講了這一段代碼,大概做了一個什么事情。

       

下面,我就給大家看一下我是如何去測試這樣的一個多進程,如何去進行一個對比的。我執(zhí)行的測試流程,是這樣的一個流程:首先我會創(chuàng)建了一個隊列,這個隊列是一個全局的一個隊列,而且它在這些進程當中是進行共享,當然它是用的multiprocessing當中的一個隊列去實現(xiàn)的。然后,下面會有一個循環(huán),當然這個是單進程的時候,下面會有一個循環(huán),然后循環(huán)每次從這個隊列當中,這個隊列當中去拿一個操作號,這個隊列它是從0到9這樣的一個數(shù)組,從0、1、2、3、4一直到9,這樣的一個序列。后面,我在這里會拿到了一個執(zhí)行號之后,然后再去執(zhí)行的一個函數(shù),執(zhí)行了這個函數(shù);下一次循環(huán),就是再次從這個隊列當中去再拿出一個執(zhí)行號,拿出執(zhí)行號之后,再執(zhí)行一下這樣的一個函數(shù),最終執(zhí)行結束,當隊列為空的時候這里就執(zhí)行結束了。

這里我再給大家說一下,這個函數(shù)是一個什么函數(shù)呢?是這邊定義的一個計算斐波那契數(shù)列的一個函數(shù),這個函數(shù)使用遞推實現(xiàn)的。說到這個斐波那契數(shù)列可以給大家提一下,斐波那契數(shù)列大概是長這個樣子,寫出來可能大家就知道了,第一,1、2、3、5,然后是8,這樣的一個數(shù)列。就是前兩個數(shù)相加就等于后一個數(shù),這樣的一個數(shù)列。我實現(xiàn)它的方式是實現(xiàn)遞推實現(xiàn)的,為什么使用這樣一個函數(shù)在這里呢?比較簡單的一個理由,就是它比較耗時,我沒有進行優(yōu)化。耗時的話,我這里就可以去清楚地來對比單進程和多進程它們之間的一個優(yōu)化的一個那個結果,那么多進程的時候,同樣是有一個全局的隊列。全局的隊列之后呢,下面會有多個進程,同時在這個隊列當中去拿這個操作號,依然是0到9操作號。拿了操作號之后,每一個進程獨立的去拿到進程號之后,獨立的去執(zhí)行斐波那契數(shù)列,直到這個執(zhí)行為空,這個隊列為空的時候,就算作是整體執(zhí)行結束。

這里講了我的—個測試腳本的流程之后,我們來看一下它的結果。在單核的時候,它也就是前面我執(zhí)行這個單進程的時候,它依然是把一個核心代碼,這樣我是展開來看的,一個核心代碼。后面使用多核的時候,大家可以看到,它將所有的核心數(shù)幾乎都是再一個比較高的使用水平上面,這里呢,我的代碼呢,它并沒有去留下一個核心數(shù)來作為預留,因為我為了去測試它的效果,讓它看起來更明顯,我直接將四個核心都用上了,這里大家也可以看到它的執(zhí)行時間的結果,當使用單線程的時候是15秒,使用多進程的時候是在4.8秒。它看起來大致是在一個4倍的一個左右的時間,當中要考慮這個進程的開銷,它這個4倍是一個理論值。

這里講到多進程,大家可能有些同學也會有有想,既然多進程可以,那么我們是否可以用多線程實現(xiàn)呢?是的,多線程也能完成這樣的一個工作。

多線程

接下來我們看一下多線程如何去執(zhí)行,首先在這個進程的使用進程的時候,它的進程時間的通信是比較不方便的,因為大家知道這個進程是系統(tǒng)分配資源的一個最小單位,所以說呢,它這個一些變量,包括你要在進程之間去進行共享數(shù)據,這樣的時候,就會變得不太方便。面臨一個問題,就是創(chuàng)建或撤銷的時候,它的系統(tǒng)開銷是比較大的,所以說因為它的開銷大,你不可能去創(chuàng)建大量的一個進程在這個地方去執(zhí)行這樣的任務。還有一個,就是進程的切換,它的耗時是比較大的,當然它這個耗時,這里指的耗時是跟與多線程比較來講的。

              

那么多線程相對于進程來講,它就會有如下三個特點。第一個,就是通信簡單,可以在這個多個線程之間去共享數(shù)據,會比進程之間來的比較簡單,而且它的開銷小,就意味著,可以去使用更多的線程,同時呢,它的切換也是比這個進程更輕量,更快的。

       

那么接下來,我們看一下,如何使用這個多線程去改寫當前的一個測試的一個代碼,那么這里是多線程的一部分關鍵代碼,這里呢,大家注意到,隊列queue 它實際上是一個全局變量,這樣的全局變量在這里就并不是使用了一個Multiprocessing里面隊列來實現(xiàn)的,而是簡單的,直接使用了這個Python當中的列表去實現(xiàn)的,同樣的,它依然是去拿到操作號之后,執(zhí)行的斐波那契的序列,這里去起動線程的時候,在Python當中,這里我使用了Thread這個庫,然后去創(chuàng)建線程.所以同樣的,大家看到右邊這個流程圖,它和我們多進程的一個流程圖類似。在這里,因為它也是有一個全局的一個隊列,在這個隊列,實際上在這里,它是一個List,這個List,當然下面所有的線程都依次的從這個列表當中去拿到了這樣一個操作號,然后再去執(zhí)行斐波那契數(shù)列,最終呢,直到這個List為空的時候,所有的程序執(zhí)行結束。

       

我們看一下它的執(zhí)行結果,同樣的是單線程的時候,依然是一個單核CPU被使用的,利用率非常高,那么多線程的時候,大家注意一下這樣的一個結果,它所有的核心這里看起來,都使用起來的,而且它處在一個非常低的一個使用水平,這里是我們程序被優(yōu)化了嗎?大家想一下這個問題,看起來是,就如果直觀的從這樣的一個結果來看,好像是被優(yōu)化了,而且它的一個利用率還比較低,但是我們來看一下它的一個結果,在單線程的時候,它是14.52秒,在這個多線程的時候有一個,時間相對于這個單線程它還增加了,這是一個什么問題呢?這里有同學說,數(shù)量越大,效果就能體現(xiàn)出來。

全局解釋器鎖(GIL)

            

接下來,我給大家解釋一下,大家可能就會明白了,要解釋這樣的,為什么多線程反而在這里會比單線程的時間長?首先我們要去知道Python當中的一個非常著名的問題,就是全局解釋器鎖這樣的一個問題。為什么Python當中會有這樣的一個東西呢?這個東西叫GIL(Global Interpreter Lock的縮寫),是在很早之前,并發(fā)編程出現(xiàn)的時候,就會涉及到數(shù)據一致性的問題。Python社區(qū)最開始為了解決這個問題,提出了全局鎖。全局鎖控制線程之間,或者是并行的任務之間的數(shù)據一致性的問題。全局鎖確實很好地解決了這個問題,不過它就會帶來一些問題,就是使得任何一個時刻,只會有一個線程在執(zhí)行。這樣也就會帶來一些性能上的問題,我們經常會聽到的Python的并發(fā)性不好這樣的一些說法,后面我給大家講一下,大家可能知道這樣的一個原因所在,是為什么,就清楚了。

接下來,我們先看一下這個數(shù)據一致性是什么樣的問題?假設我這里有兩個線程,一個是線程一,一個是線程二,如果這兩個任務我們同時是執(zhí)行的了一個同樣的任務,有一個A是全局的變量,它是線程一和線程二都能夠同時訪問的一個變量。線程一,它要執(zhí)行的任務,就是先給這個A賦上一個值,比如說賦上一個1,再第二步它再去,給這個值進行一個自加,即A等于A加一;線程二,它也是執(zhí)行的這個功能,那么也是A,然后它是等于二,然后它是進行一個自加,即A等于A加二。這里,如果兩個線程單獨執(zhí)行,那第一個線程執(zhí)行的一個結果就是A等于1,A=1+1,那它執(zhí)行的一個結果就是A就等于2;下面這個,如果是單獨去執(zhí)行的話,它執(zhí)行的一個結果呢,就等于4。這樣是沒有問題的。

但是呢,如果這兩個線程,同時去運行,而且不去控制順序的話,那么有可能,我先執(zhí)行了一個A=1,然后再執(zhí)行了A=2,這個時候,A=2;這個時候,大家去在這個線的位置,假如說它們是有一個交叉的,在執(zhí)行的上面這個,A=A+1,那么這個最后呢,在線程一執(zhí)行出來A的結果,它就等于3了。在上面這個執(zhí)行完了以后,再去執(zhí)行下面這一個線程二的結果的時候,它就變成了A=5。大家注意沒有,這里它會有兩個結果,對于一個程序來說它的一個運行時機居然決定了它的一個結果,這種方式是大家不能接受的。也就是說如果有可能它運行的線程,第一個,先運行,第二個,后運行;如果是在這個地方,它的結果是第一個,如果是這種重疊運行的時候,結果是第二個,那么這就會有一個歧義。

在這個地方,為了解決這樣一個一致性的問題,就提出了一個全局鎖的概念,這個全局鎖,那么它是如何去解決這樣的一個問題的呢?接下來,我們看一下,在下面這個位置,同樣的,先是這個線程一開始執(zhí)行,執(zhí)行到這個地方的時候,假如它碰上了一個I/O,碰到了一個I/O的時候呢,它就會進行一個釋放鎖的動作,釋放鎖的動作之后,會被第二個線程去獲取鎖,這里有個獲取鎖的動作,它再進行執(zhí)行,也就是說我每一個線程要去執(zhí)行的時候,我就必須先要去有個獲取鎖的動作,獲取鎖之后,再進行執(zhí)行。這里呢,假如說線程二,它執(zhí)行的時間比較長,這里我就沒有用這個I/O來進行舉例了,還有一種情況,Python,這個解釋器它內部會去計算它所執(zhí)行的一個微代碼的一個數(shù)量,當它微代碼的數(shù)量執(zhí)行的足夠多的時候,它會進行一個釋放,將這樣一個線程強制釋放鎖,強制釋放鎖之后,下一次,比如說這里,就是線程4,它獲取到了這樣一個鎖,他再進行一個執(zhí)行。這個地方,微代碼的數(shù)量大家可以簡單的去理解它就是一個執(zhí)行時間,當它執(zhí)行時間長了以后,就系統(tǒng)不可能讓這樣一個線程一直在這里,帶著CPU去執(zhí)行,它就會去對它進行強制釋放,當然大家可以簡單的這樣去理解。當這個線程4,拿到了這樣一個鎖之后,它又會執(zhí)行,所以這些線程就在這樣的切換當中,依次的去進行一個切換,輪流著來執(zhí)行,而這里呢,也就解釋了,下面這一句話,就是全局解釋器鎖,使得在任意一個時刻,僅有一個線程在執(zhí)行,這樣的處理方式也就解決了我們上面這樣的一個計算的問題,也就是數(shù)據一致性的問題,它保證同一時刻只有一個線程去執(zhí)行。那么同一時刻,也就只有一個線程去對這樣的一個數(shù)據進行修改,也就不會出現(xiàn)一個不一致的情況。

這里講了全局解釋器鎖,我們再回過頭來看一下前面的那個問題,為什么我們的一個多線程,反而比這個單線程的一個執(zhí)行時間會長。大家看到,當多線程它進行一個計算的時候,因為大家知道,這里它會進行切換,綠色的呢,表示是這個線程正在計算,那么它會不停的去進行切換,因為我們前面的這個程序呢,它是一個計算密集型的,它的主要的任務就是進行計算,而且它在這個當中切換了,就相當于是當它執(zhí)行的時間足夠長之后,被這個系統(tǒng)強制進行釋放。

比如第二個CPU,拿到的這樣一個鎖之后,它進一個執(zhí)行,這里再進行一次釋放,然后被CPU0拿到了進行執(zhí)行,所以它這里會進行非常多的切換,這樣的切換,它當中會是有代價的。它這樣切換的代價帶來的結果,最終執(zhí)行的時間會是比原來單線程執(zhí)行的要高的,這里就是一個原因。

CPU密集型

這里,那么看起來,多線程它會不會是沒用呢?其實多線程它也有自己的一個使用場景,那么我們這里已經提到了一個CPU密集型,也就是說計算密集型,它也稱之為是CPU密集型,簡單的去理解,就是在程序當中,大部分時間花在計算上面的,比如說,前面我講的這個事例,它計算了一個斐波那契數(shù)列,它的所有的時間都是耗在計算上面的。并且這樣的一個程序呢,沒有去進行使用遞推沒有進行優(yōu)化的話,計算量會非常的大。這個大家下來自己去嘗試一下,去計算這樣的一個數(shù)列,然后去比較一下它的時間,就知道它的計算量。由于它的計算量都在計算上面,所以說呢,它比較適用于這個多進程,多進程它就可以讓真正的讓所有的CPU都去參與到這個計算當中。

I/O密集型

            

好,那么還有一種計算的類型,就是這個I/O密集型,所謂I/O密集型,就是程序當中,大部分的時間是花在這個數(shù)據傳輸上面的。比如我們的要在程序當中去讀入一個比較大的一個圖片,或者是讀入一些比較大的一些支點數(shù)據,或者從數(shù)據庫讀一些比較大的數(shù)據出來,那么要讀取很大的數(shù)據,它就會稱之為是I/O密集型。當然大部分時間,也是相對于這個CPU的計算時間來講的。同學們再回憶一下,前面我講的一個多線程切換的時候,當遇到I/O的時候,它就會進行切換,所以當它進行一個I/O的時候,它切換到其他的線程進行一個執(zhí)行,那么多線程是可以對這種場景下的一個程序進行優(yōu)化的,這里呢,就給大家講,解釋講了兩種,一個是多進程,一個是多線程。

 協(xié)程

        

大家在平時的Python編程當中,還會聽到一個值,叫協(xié)程。那么協(xié)程是什么?這里我再補充給大家說一下。協(xié)程它就是一個用戶太輕量級的這樣一個線程,它有自己的一個寄存器,而且上下文切換,比線程還要更輕量,這樣說起來可能比較抽象,所以我在這里用代碼,再給大家去演示一下協(xié)程是如何工作的。這里我有三個工作的一個函數(shù),第一個函數(shù),task0,當中我使用了這樣的一個Sleep,去模擬I/O,Task一我又Sleep 3秒,Task2,就去Sleep  0秒,然后呢,在這個下面這個地位Join去把所有的任務加到這個協(xié)程當中,然后去來進行一個計算,它最終執(zhí)行的流程,是從task0開始,當它執(zhí)行到task0的時候,遇到了這樣一個I/O,5秒這個位置,遇到I/O之后,它就會進行切換,切換到下一個開始執(zhí)行;切換到task1的時候,它也發(fā)現(xiàn),這里也有一個I/O,那么它會繼續(xù)切換,切換到Task2的時候,這里它也會發(fā)現(xiàn),這里有一個sleep0,大家注意在這個,這個協(xié)程當中呢,它遇到了這樣的一個sleep0的時候,雖然這里Sleep0,但它依然會觸發(fā)一次切換,所以它最后還是會切換到Task0;之后,它會繼續(xù)進行下一輪的一個執(zhí)行,當然這里看起來會有這么多流程。

實際上呢,它的一個切換的速度是非??斓?,它的執(zhí)行速度非???。所以看起來,這樣的一個程序最終的一個結果看起來會是一個并發(fā)進行的,所以這里最終執(zhí)行的一個結果呢,大家看一下,如果直接去串行執(zhí)行這三個任務的話,它就會是一個8點多秒,大概是在5和3的加起來的一個值。如果是協(xié)程來計算,大概在5秒的樣子,那么5秒的樣子,它也就是5、3和0,這三個取得一個最大值,因為它這里會不停地進行切換了。最終它的值會處在,就以這個最大的值是它的一個結果,來講它也是適用于I/O密集型的程序。實際上在這個工程開發(fā)當中,協(xié)程用的多的是在這個非阻塞,異步并發(fā)的情況下,用的是比較多的,那么比如這個有一個Web框架,它就是用協(xié)程去做的,這個服務器的壓力依然很大,就是對我們來講,這個服務器的請求的數(shù)量依然不能夠有一個提升。

評估磁盤I/O

那么接下來,我們還要從哪些方面去考慮呢?接下來就是磁盤。大家想一想,磁盤有沒有可能成為阻礙我們這個請求提升的一個因素?

要去查看一個磁盤目前的狀態(tài),這里我提供兩點思路。一種,就是使用TOP命令,可以去看到磁盤信息,這里有大家注意到,磁盤信息,這邊有一個WA,這個詞它就表示的是等待輸入輸出的一個時間一個比例,當然這個值是我模擬出來的,它當時處在一個非常高的水平。這就說明,目前的一個I/O讀寫的一個能力,是在一個比較有壓力的位置。還有一種情況,就是去直接查看業(yè)務代碼,大家對自己的一個業(yè)務代碼相信是非常熟悉的,那么要去查,是否對磁盤的處理能力有要求,或者是否磁盤處理的能力壓力大的情況下,就查看業(yè)務代碼也可以,這也是比較方便的一種做法。大家知道在這個業(yè)務代碼當中,比如說這里有一種讀文件的Read函數(shù),這里我寫的是偽代碼,假如大家在這個平常的一個業(yè)務代碼當中會發(fā)現(xiàn)有這種場景,就是去讀一些大文件,或者最常見的是讀一些圖片,或者是呢,讀一些比較大的二進制文件,或者是一些其他什么,比如一些AI訓練當中有一些模型訓練當中一些文件,這個對這個磁盤的要求非常高。

好,接下來,我們就希望去提升這磁盤的讀寫能力,我們接下來就會去針對這個磁盤進行優(yōu)化。當然說到優(yōu)化,依然是從硬件開始。這里我有一個程序,這個程序它是在讀取磁盤。對它進行優(yōu)化最簡單,直接就將這個磁盤換為SID,從機械盤換成一個固態(tài)盤。方式雖然簡單,但是對于企業(yè)來講,或者對于大家的業(yè)務來講,這是一個成本非常高的一個選擇,大家知道這個固態(tài)盤它是非常貴的,相對于機械盤來說,它是非常不劃算的,那么我們有沒有一種方式,能夠用很多的這樣的一些機械盤,比較廉價的機械盤,把它拼起來成一個大的盤,或者讓它并行的去進行一些處理?這樣我們的速度就能夠進行一個提升,這樣就優(yōu)化了我們的思路。

但是大家看一下,如果我放了這么多盤在這里,大家看到中間這么一個圖,我放了很多盤在這里,那么對程序來講,我到底是訪問第一個盤呢,還是訪問第二個盤呢,還是訪問第三個盤呢?這對程序來講,又會是一個很大的難題,那么這里,我就想,如果我有這么一個工具,假如在這里有一個工具,它能夠幫我把上面的這些磁盤全部屏蔽掉,讓我不知道下面有這些磁盤,它來幫我做這樣的一些事情;而這個程序,只需要去讀這樣一個工具就好了,并且我希望讀這個工具,跟我直接讀一塊盤是一樣的,那么就更好了,那么有沒有這樣的一個工具幫我們去做這樣的事情呢?

磁盤陣列

            

接下來,就是我要給大家講的一個磁盤陣列,這個磁盤陣列技術(RAID),那么這里呢,會有幾種RAID,實際上RAID對讀操作來講,它本身是一個硬件,是一個RAID卡,這個卡可以插在這個主板上面,或者是有些主板上面它直接就帶有一種RAID卡。RAID卡的作用就是將這些磁盤去連接起來,可以認為這上面它就是一個RAID卡,把這個磁盤進行一個連接,它當中會去做一些事情,來把這些磁盤組織起來,具體如何去讀來一塊數(shù)據,都是由它來進行組織的。而對于我們的一個計算機來講,直接去讀這個RAID卡就可以了。

            

這里講的RAID也會有幾種類型,它對磁盤的管理,比如說這里有個RAID0,第一個,我們給大家講一下,那么RAID0,它就是使用兩個以上的磁盤并聯(lián),它的速度是在所有型號當中最快的。這是一個什么含義呢?就是比如這里我用一個D0和D1,這是兩塊盤那么就將它并起來,這就是我們前面所講到的我們所希望完成的一個功能,就是把這兩塊磁盤并列起來,把它的數(shù)據由這個RAID卡自己去決定,這一份數(shù)據是寫在第0塊盤上,或者是第一塊盤上,這個都是由RAID自己去決定的。

但是這里大家會發(fā)現(xiàn)一個問題,它的是沒有容錯能力的,因為我的一份數(shù)據,要么寫在第0號上面,要么寫在第1號上面,如果這個數(shù)據丟失了,那么它就沒了,就跟我們使用一塊盤的時候是一樣的,而且大家知道這個隨著我的這個盤,比如這里我再接上一塊盤,隨著盤的增加,它出錯的一個概率,或者說數(shù)據丟失概率也會增加。那么我們就想一下,有沒有一種方式它更快,能夠滿足我們快速的需求,而且它還能夠幫我們進行一些備份,或者進行一些容錯?這個就是要引出今天要講的RAID1。RAID1是如何做得呢?它就是兩組以上的磁塊互做進項,比如我有一個D0和D1,那么有了這兩塊盤之后,它就是對于這個RAID來講,來了一份數(shù)據之后,是第一塊盤上存儲一份,第二塊盤上存一份,那么這樣的一個數(shù)據呢,相當于,它就做了一個備份,而且它的讀寫速度也幾乎是等于這個RAID1的一個讀寫速度,幾乎是等于RAID0的,它們兩個讀寫速度幾乎一樣。

但是呢,大家知道,它的缺點是什么呢?雖然它有一個容錯能力,比如說我第一塊盤丟失了,那么第二塊盤依然存在一個完整的數(shù)據,并不影響我們上層的邏輯的一個功能。但是這里,同學們注意一下,它的每一份數(shù)據都是存了兩份的,而且如果這里還有第三塊盤的時候,那么相當于第三塊盤,它也會把樣的數(shù)據再拷貝一份到這來,這樣的RAID1對磁盤無疑是造成了一種浪費,那么這個也是一種不經濟的行為,就是比較浪費的。

對我們來說,這兩個方案各有優(yōu)缺點,第一個沒有容錯能力,但是它一個對磁盤的空間的利用率是比較高的。如果是第二個方案,它雖然是有容錯能力,但是它對磁盤的利用率不高,現(xiàn)在有沒有一種比較折中的方案,讓我們能夠去把兩種方案進行一個結合,如果有這種方案,這種對磁盤的一個陣列的一個解決呢,就算是比較完美的。

接下來,我再給大家講一下這個RAID5,RAID5就屬于一個這種的方案,那么這個折中的方案是如何實現(xiàn)的呢?它的一個原理是什么,至少需要3個盤,這個是必不可少的。這里我是用四塊盤來作為演示的,數(shù)據存到上面,它是不是直接存的,就是對于這種備份據它也不是直接備份的,然后,它呢,是將這些數(shù)據進行一個奇偶校驗,校驗之后,然后存到了另外的一塊盤上,比如說這里,我對A1,A2,A3這樣三個數(shù)據,對它進行計算一個奇偶校驗,計算出的結果我放在了第四塊盤上,對于B1、B2、B3進行一個計算,放到了中間這塊盤上面。這樣做有什么好處?假如現(xiàn)在我的一個磁盤,比如最后一個磁盤已經壞掉了,那么這個磁盤壞掉了之后,我就依然可以對它數(shù)據進行一個重建?,F(xiàn)在壞掉了之后,當我插上一塊新的盤之后,這個RAID它會對這個數(shù)據進遷移,遷移之后,比如這里,我要去計算這個AP的時候,重新根據這個A1,A2,A3去進行計算,就能夠得到這個AP,那么對于這個B3它依然可以根據前面已有的三個數(shù)據去計算出這樣的一個A3的數(shù)據,所以最終得到的一個結果,就是這個第四塊盤可以被完整的一個重建出來。這個RAID5,它也有速度快,而且呢,它也可以去進行一定的容錯,所以那么這個方案算是比較折中的一個方案,這里就給大家介紹了三個RAID的一個型號。

這里大家還會想一個問題,就是說這個磁盤雖然它的速度比較快,但是我們還能不能去更快的訪問呢?這里就涉及到一個問題,就是大家想一下,在這個計算機當中,我們除了能夠在磁盤上面去存儲數(shù)據,還能夠在哪里去存儲這樣的一些數(shù)據呢?也是說在計算機當中,我們的存儲設備還可以有哪些呢?這里有同學說到了這個內存。

內存

那么接下來,我們就想一下,能不能把這樣的數(shù)據直接存儲內存里?因為內存的數(shù)據相對于這個磁盤來講,它是一個數(shù)量級上的提升。

              

這里就給大家講到了一個優(yōu)化的點,那么就是去運用緩存。比如我們這里有兩個場景,一個是外賣,一個是電商。假如說外賣,我們去點這個外賣信息的時候,或者查看這樣的一些商家信息的時候,每個人都去查找,而且它返回的數(shù)據都是同一份,都會需要去從這個數(shù)據庫當中去查找這樣的一份數(shù)據,而且這樣的數(shù)據,它變更并不是非常的頻繁,對于電商來講,他也是,電商來講,他給我們去看一個商品的信息,那么這個商品的信息它也變換的不是非常頻繁,而且信息都是相同的,而且每個用戶去訪問得到這樣的信息也都是相同的。如果放在數(shù)據庫當中,或者放在這種磁盤上面去訪問的時候,勢必效率會非常低,這里我們就考慮把它放在內存當中。

這里給大家介紹Redis。它是一個內存數(shù)據庫,那么用Redis來做一個緩存,像這樣不是經常更新的數(shù)據,而且不是這樣經常更改的數(shù)據,將它放在這個內村當中,當這個程序去訪問想要訪問數(shù)據庫的時候,先要查這個緩存,通過緩存當中拿到數(shù)據,這樣它的效率就會進一步的提升,我這里呢,給大家講一下,如何去通過這樣的一個緩存去讀寫數(shù)據,大家可以看到,第一個,如果去獲取數(shù)據的時候,上面我們是直接讀的數(shù)據庫,而下面呢,我是先去讀這個緩存,如果緩存當中有,那么直接就返回,就得到了一個這樣的數(shù)據,如果緩存當中沒有,我就去讀這個數(shù)據庫,讀了數(shù)據庫之后,大家一定記住,下一步是要去把這個讀到的數(shù)據寫到緩存里面,方便下一次讀。下一次讀取的時候,我就知道這個緩存當中有這個數(shù)據,就不用再去查到這個數(shù)據庫,這個就是一個讀取數(shù)據庫,如果要寫入數(shù)據的時候,要如何去做呢?寫入數(shù)據的時候,就是直接寫數(shù)據庫,同時寫的時候,依然要記住去更新一下這個緩存當中的一些數(shù)據。

軟件存儲優(yōu)化

            

這里就是緩存的一個最簡單的一個讀寫的模型,就是右邊這種形式。那么如果大家想一下在這種情況下面,它有沒有一些問題?或者有沒有一些容易出錯的地方?這里給大家提示一下,第一種呢,假如我這個數(shù)據庫,這個程序,假如我這個程序是訪問這個緩存的時候,把緩存當中沒有數(shù)據,那么它是不是要繼續(xù)去訪問這樣的一個數(shù)據庫?訪問這個數(shù)據庫,它依然是沒有數(shù)據庫,那么相當于,我這個緩存并沒有起作用,而且它還增加了這樣的兩次訪問,這是一個問題,還有一個問題,假如我這樣的一個緩存宕機了,那么宕機了之后,緩存崩潰,宕機了之后,所有的請求都會走到后面的這樣一個數(shù)據庫去,那么這里也會存在一個問題。

接下來給大家講一下這兩個問題。它的一個比較常見的解決方式,第一個就是緩存穿透的問題,就是剛才給大家講到的,假如說我在這個緩存當中去訪問,并沒有找到這個數(shù)據,而且在這個數(shù)據庫當中也沒有找到,那么這種情況呢,相當于是繞過了這樣的一個緩存,如果解決的話呢,第一個去給這個查詢的時候,在數(shù)據庫當中查詢?yōu)榭盏臅r候,那么也去給這個緩存當中寫上這樣一份緩存,那么下次獲取的時候,但是我知道這個數(shù)據是不存在的,而且數(shù)據庫當中也沒有,那么我就不會再去訪問這個數(shù)據庫了;還有可以去增加一些過濾器,去給它提前進行過濾。

那么還有一個,剛剛有同學提到了,是一個緩存失效,緩存崩潰的問題,緩存崩潰,或者是緩存失效,因為緩存我們會有更新時間,假如你設置了一個同樣的更新時間,在這個統(tǒng)一時間內,這個緩存都失效了,那么他們就會大量的一個重復更新的一些請求,需要從數(shù)據庫更新到這個緩存當中,這個請求對數(shù)據庫來講也是一個比較有壓力的實行,我們設置緩存失效時間的時候,就可以設置成一個隨機的時間?;蛘咴谶@個數(shù)據庫讀寫的時候,我們要去控制它的一個讀寫的線程,那么這里呢,就是提到緩存可能會存在的兩個問題,講到這里呢,幾乎我們已經涉及到了一個對CPU的優(yōu)化,和對這個存儲方面的優(yōu)化。

網絡優(yōu)化

那么接下來,大家想一下,還有可能是哪一方面?可能會去進行限制請求數(shù)量的增長。那么就是網絡問題。

首先要去了解網絡問題,我們可以先去查看一些,最基礎的可以查看一些硬件信息,比如說查看網絡帶寬,或者去查看一下程序在運行的時候,我們在后臺查看一下網絡當前的流量是否已經到達了非常高的值,這里給大家提一下,兩個點,這個使用的命令是IFTOP命令,如果沒有軟件的話,可以直接去裝上這樣的一個軟件,就可以了,那么下面還介紹一下這個TX和RX,這兩個呢,分別是發(fā)送和接收的一個流量,就是發(fā)送和接收的流量,大家通過這兩個值也可以看到當前網絡的一個情況。

針對網絡的問題,依然我們最簡單的能夠想到,一個硬件問題,那么硬件問題呢,就比較好解決了,那么就去選取網卡,如果是自建機房,就比如用千兆網卡換成萬兆網卡,或者是變口換成端口,如果是云平臺,大家就需要在這個控制臺上去升級帶寬。

            

當然這樣的方式是比較簡單的。還有一種呢,就是DNS域名解析的問題,大家知道這個我們訪問域名的時候,實際上是通過這個域名解析的一個服務器,當中去拿到了這樣的一個實際的IP,并且通過這個IP去進行訪問,那么如果說這個域名服務器它解析的速度非常的慢,這個就會導致我們的服務器,整個請求鏈時間也會變得慢,那么這個解決方法也是比較簡單的,就是會去更換域名服務器,這些域名服務器的提供商,我們也可以去選擇一些更優(yōu)質的一些服務提供商去來進行服務。

內核參數(shù)優(yōu)化

接下來,網絡優(yōu)化呢,接下來給大家講一個稍微復雜一點的優(yōu)化,那么這個呢,就是一個內核參數(shù)的一個優(yōu)化。

大家裝系統(tǒng)的時候,默認了系統(tǒng)內核的一些參數(shù),它不一定適合你當前的業(yè)務場景,包括你的一些硬件配置,這些內核參數(shù),可以去對它進行一些調整。

這里要講這個內核參數(shù)的優(yōu)化。我就先給大家去講一下這個關于握手連接,給大家回憶一下。在這個連接過程當中,首先是服務器會去監(jiān)聽你的端口,綁定一個地址,監(jiān)聽你的端口;然后,由客戶端去發(fā)起一個請求,發(fā)起了請求之后,發(fā)送一個SYN包到服務端,服務端接收了線包之后,他會回一個SYN加ACK,最后客戶端接收到之后,它會再回一個ACK到服務端,這三步完成之后,整個連接就建立起來了.基于這樣的一個思路,那我們在這個系統(tǒng)當中,實際上它會維護兩個隊列,一個隊列是半連接隊列,第一個隊列它維護的是,里面存的是一個什么樣的一些東西,存的就是這個客戶端,過來請求的時候,它一些來不及處理的這樣的一些連接,那么它會放到這樣一個隊列里,大家知道,如果說這個連接是一個一個過來,是沒有問題的,當如果是大量的這樣的請求過來,服務器不能在瞬間去處理好這些請求的時候,它就會需要放到這樣的一個隊列里面,去進行一個處理,接下來一個全連接隊列,就是這樣已經建立好的一些連接,就會放到這個全連接隊列當中。這里假設兩個隊列的原因是什么呢?因為大家想一下,這兩個隊列的長度就決定了,我們這個系統(tǒng)當前能夠接收到的一個請求的一個上限。

              

這里我給大家講幾個比較關鍵的參數(shù),比如第一個參數(shù),當然這個參數(shù)比較長,后續(xù)大家拿著課件之后,可以再仔細的去研究。第一個參數(shù)呢,是來控制這個SYN半連接隊列長度的,后面的1024是它的一個默認的一個值,也就是裝好系統(tǒng)之后,默認的是1024,當然這個值對系統(tǒng)來講是一個比較小的一個值,我們可以通過去修改這樣的一個值,讓它的把這樣的半連接隊列去變得更長,我能夠允許這樣一個名詞一個連接的長度可以變得更長。

那么下面還有一個就是這個全連接隊列長度,就是這個程序控制的,當然這個值,這個地方是128。它控制的就是這個全連接的長度,它的長度也就決定了,我這個系統(tǒng)當前能夠維護多長的一個監(jiān)聽的一個隊列,這里還要給大家提一點就是,我的半連接長度,實際上受制于這個全連接的長度,也就是說,我半連接長度的這個值,哪怕這個地方是1024,但是它實際上能夠起效的一個值是128,它會選這兩個值當中最小的一個值,去進行一個生效,這里講了兩個連接隊列。

最后再給大家講一個,這個是一個Time_Wait,而維持Time_Wait最大數(shù)量的一個參數(shù),那么它們是為什么是在Search,Watch斷開當中會出現(xiàn)的一個狀態(tài)?那么這個Time_Wait狀態(tài)如果過多的話,也有可能去把這個系統(tǒng)就拖死,所以呢,設置這樣的一個值也可以去幫我們進行優(yōu)化,這里講呢,這幾個值要如何去配置呢?也稍微提一下,要配置的時候,這里先講一個sysctl-a,去查看目前已經生效的一個參數(shù),通過sysctl-a可以去查看,但是如果直接去使用了時候,它的一個會顯示出來的值會非常的長。

所以呢,這里用Grep去過濾一下,這里我過濾了一個值。要修改也是比較簡單的,就直接去修改這樣的ETC下面的配置文件,然后使用Sysctl-p去進行一個生效就可以了,當然這里需要給大家提醒一下,需要去注意的,修改內核參數(shù)的時候,需要對系統(tǒng)有充分的了解,然后再去修改,而且這個值也并不是越大越好,需要經過一系列的測試。根據你當前系統(tǒng)的一個運行配置,然后來測試,還有你的業(yè)務才能決定,到底什么樣的值是比較符合這樣的一個系統(tǒng)的。

              

負載均衡

那么我們接下來再看一下,因為我們已經講到了CPU,也講了存儲,還講了這樣的一些網絡上的優(yōu)化。如果我還想,這個系統(tǒng)能夠再進一步的去提升它對外服務的一個能力,還有哪些方面可以去進行一個優(yōu)化。

在目前的業(yè)務當中。單機是比較難去滿足這樣的一些場景的,通常我們就會去進行一個橫向擴展,橫向擴展之后,它可以去優(yōu)化整體的服務能力,因為對用戶來講,或者對客戶端來講,它只要能夠訪問到這樣的一個服務,它并不關心后面是一個服務器,還是一個集群對它進服務的。但是這里也會帶來一個問題,那么就是集群會帶來一個你的程序,或者是你后端的一些設計復雜度提升。

            

這里講到了個集群。那么對用戶來講,我要如何知道,我去訪問后面,到底訪問哪一臺機器呢?那么這里就會去講到一個負載均衡的問題,對用戶來講我期望去訪問到中間這臺服務器,那么我期望有這樣一個東西在這里,讓我去訪問它,而它幫我去做一些事情,它來由中間這個服務器來決定,最終這個請求是到了那一個后端,那么這樣呢,對客戶端來講,那就是比較友好的。所以這里呢,中間這樣的一個服務器就承擔了任務分發(fā)的一個角色,那么這個中間這個一個抽象的服務器,它可以是硬件,它也可以是單獨的一個服務器,上面裝了這樣一些軟件,硬件這里我列了兩家,一個是F5,一個是A10  Networks,這兩個公司他們都是非常好的硬件提供商,他們的硬件能夠很好的去解決這樣一個業(yè)務分發(fā)的問題。

但是在這樣的硬件雖然好,它的價格是非常昂貴的。比如像這個F5的話,這樣的設備應該都是在百萬級別的,那么這樣的一些昂貴的設備,可能在一些業(yè)務,對一些業(yè)務來講,它的一個收益并不是那么的明顯,或者說對它來說性價比不是很高,這里我們就可以去用這種軟件的一個負載均衡,那么軟件的負載均衡呢,這里也給大家提供兩個思路,一個是LVS,這個是現(xiàn)在已經被納入了這個Linux內核的一個技術,它是基于OSI網絡模型當中第四層(傳輸層)來做得,那么這個nginx是基于應用層(7層)來做的,當然這兩個,他們也有各自的一個應用場景,也并不是說這個層數(shù)越低越好,或者層數(shù)越高的越好。大家需要注意一下。

       

這里講到了負載均衡,它中間這個服務器會進行任務分發(fā)。那么它是如何知道,我要把這個任務發(fā)給誰呢?這里是隨便發(fā)都可以嗎?還是說有一定的規(guī)則?這里就給大家講一下它的一個常見的這個算法,那么當然隨便發(fā)我這里是可以是一種叫隨機的算法,最常見的有一種算法,叫輪詢算法。輪詢算法的含義就是它追求的就是一個絕對的均衡,就比如這個地方我有一個請求,要發(fā)送,這個請求發(fā)送,它是一個中間抽象的這個任務分發(fā)的一個主機,那么就是將請求比如來了一個請求,發(fā)給第一個機器,那么第二個機器我發(fā)給第二個機器。第三個請求就發(fā)給第三個機器,那么這樣就是一個絕對的均衡算法,它就是進行一個輪詢。

             

還有一個,就是最小連接數(shù)算法。它這里會,就計算,去計算每一個服務器的一個當前的一個連接數(shù),然后呢,它會把這個請求轉發(fā)到一個最小的一個,當前連接數(shù)最小的一個服務器上面,這個連接算法,雖然它會比上面的這種RR算法要好,但是這個LC算法實現(xiàn)難度,也會去相對來更高一點。

還有一種就是源地址HASH,這個它能夠保證我客戶端的一個IP始終訪問某一臺服務器,它的大致的思路呢,就是首先對這個客戶端的IP進行HASH,對服務器的一個列表進行一個取模,取模之后,最后指定到某一臺服務器上面。這樣就能保證,我同一個IP過來的請求能夠到達某一個指定的服務器上面,這里就是一個常見的負載均衡算法。

3 峰值場景優(yōu)化

當然前面,這邊已經給大家講了這么多種優(yōu)化的場景,其實這些場景當中,沒有考慮到一個問題,那么就是請求波動很大的情況,那么這種情況是比較難處理的,我單獨去把它拎出來說。

              

在請求這個波動很大的就是我們所遇到的一些峰值場景,峰值場景下,比如,我們來看一下,第一個,它在一個時間上分布不均,假如說是一天的一個時間,這個數(shù)軸它是一個請求的次數(shù),單位是百萬次,大家可以看到,這里呢,平常的請求都是維持在一個很低的水平,但是到了某一個時間點,它會到達一個比較高的一個峰值,那么到達這個頂尖之后,也就對服務器產生的要求很大,這種場景比如說是外賣平臺,它到了這個中午的時候,可能大家點外賣的這個請求量就集中的去爆發(fā)了;或者票務網站,比如看演唱會的門票,到達某一時間放票的時候,或者到了某一個流量的明星,他的一個票務的時候,那么這個對網站來說,它的請求量就會是一個有明顯的峰值,那么針對這樣的一個場景,要如何去進行優(yōu)化?或者能不能去進行優(yōu)化呢?

              

我們看一下,這種情況也會分為兩種,第一種就是有規(guī)律的一個場景,比如說外賣網站,每天我知道到了中午就會有一個小高峰,每天中午都會有個小高峰,我就可以去運用這種容器技術,或者多機集群的方式,來進行一個定時的擴容,每天到了中午的時候,比如這里到了預測到達的這個高峰之前,我就可以在這個點提前的對它進行擴容,讓這個整體的一個服務能力有一個提升,然后能夠很好地去處理這樣一個峰值情況。然后渡過了高峰之后,比如在這個地方,然后對這樣的一些容器進行銷毀,可以去節(jié)約資源。

容器(Docker)技術

那么接下來,我給大家再提一下,一個當前比較流行的一個容器技術,以Docker為例來講一下。

它的優(yōu)勢,是在于它的一個機器的輕量,它雖然說名字叫容器,但是它只打造了一些代碼和代碼相關的一些運行環(huán)境,而且它的部署也是非??斓模斎徊糠值囊恍┤萜魃踔量梢宰龅胶撩爰?。這個也是與具體的業(yè)務場景相關的,平常有一些是秒級,當然有些也可能時間更長一點,這就是它的一個部署的方便性。還有呢,它就是一個移植性非常好,鏡像打包,它打包了這個代碼和運行環(huán)境,那么它在其他平臺上去運行也是非常方便的。而不需要去依賴這個環(huán)境上面的本身的這個數(shù)字機,身的一個這些環(huán)境配置的依賴。

還有一個好處是彈性伸縮,基于Docker,基于這個技術現(xiàn)在有很多的調度平臺的一些解決方案。比如Kubernetes,這樣的一個調度平臺。它是非常強大的,而且是背靠Google的,這樣的一個調度平臺它可以讓這個Docker的生命力變得更加的頑強。

Docker應用

那么接下來,我給大家看一下如何去使用Docker的一個流程,將它和傳統(tǒng)方式進行一個對比。

              

這里我已啟動Flask作為Web服務器。例如,當傳統(tǒng)方式,我要去啟動一個Flask來作為Web服務器的時候,把這里可能首先就要有一臺主機,我要去配置運行環(huán)境,配置運行環(huán)境之后呢,我需要在當中去裝上一些工具或者軟件,比如Python,首先要有Python,而且,它還要有Flask這樣的一個軟件,使用PIP去安裝上。或者我還需要一個Request這樣的一個庫,如果有一些其他功能,我甚至還需要去安裝更多的一些庫。

那么最后呢,就是啟動這樣的一個程序進行運行。如果是使用容器的方式,我要怎么操作呢?可能在最開始,我就只需要打包好這樣的一個容器,有了這樣一個Image(鏡像)之后呢,我只需要這樣一個宿主機上面有這樣一個Docker  Engine這樣的容器引擎,然后通過這個容器引擎下載了這個鏡像之后就對它進行啟動。

這里就容器的啟動方式,可能這里大家的感覺,兩種方式都是3步,而且看起來是差不多的。實際上,這里容器在這里它會有兩個優(yōu)勢,一個優(yōu)勢就是它比如有非常復雜的代碼,在于它的一個可移植性是非常方便的,因為我只要求,這樣的一個數(shù)據集上面有一個Docker  Engine我就可以啟動了?;蛘呤?,我需要進行多機擴展的時候,比如我現(xiàn)在要再創(chuàng)建一臺虛擬機,我不需要去裝其他的依賴的運行環(huán)境,只需要去有這樣一個Docker  Engine在這里,那么我就可以去運行這樣的一個容器,或者運行一些其他的容器都可以。

這里,就是給大家講了一個關于規(guī)則流量下的一個處理方式。最后大家也許會問,如果實在有這樣不規(guī)則的請求,那要怎樣去處理呢?

無規(guī)律峰值

接下來我們就講一個無規(guī)律峰值的情況,那么這種無規(guī)律峰值的情況,首先當然服務是要允許可異步的服務,就可以對這個流量進行一個削峰,將流量進行分攤。比如說我到這個地方有了一個流量的突增,突增之后對于這個服務器來講,它肯定是處理不了的,比如對這個地方對服務器來講,它肯定是處理不了這樣突增的請求,那么對如果時間是這樣,它的一個請求可能被丟棄,或者被延遲處理。

            

在這里,我們中間增加一個這樣的消息隊列,對它進行一個緩沖,當有了這樣一個流量洪峰過來之后,我用這個消息隊列把這樣一個流量給它接住,接住之后,然后再這邊再平緩的去給它把這些流量平滑的去放給它的下端,這里就類似于我們的那個防洪大壩這樣的一個概念,它可以先把這樣的洪水接住,然后再平滑的去放給下游,這樣對下游的一個壓力比較小的。

流量削峰

如何去處理這樣的一個隊列呢?這里就是削峰的一個場景。

              

假如這里我的一個客戶端,它的一個發(fā)送的能力是150萬次每秒,而對于消息隊列呢,它能夠去承載的是一個一千萬的一個請求,那么對于這個服務端呢,它是600次每秒的一個處理能力,如果這樣的150萬的能力請求,直接到后端,是沒有問題的,但是呢,我們這個請求如果到達了一個雪山的高峰,比如說這里,到達了八百萬,或者九百萬次,對這個服務端的壓力是非常大,那么它處理不了的請求可能就會丟失了。如果中間有了這樣的一個消息隊列之后,消息隊列就可以將這樣上面這樣一些多余的流量先將它接住,接住之后,由這個消息隊列通過一個平緩的方式將這個流量再發(fā)送給服務端去進行一個處理,那么這樣呢,所有的請求就會被分攤。大家看一下這個紅色的線是六百萬次,當請求把這些上面的高峰都處理掉之后,大概就成了下面這個樣子,它引入這種又高又尖的這樣一個流量請求,最終會變得平緩。

4 小結

好,那么今天到這里,差不多的一個優(yōu)化的一個方案大概就講到這里,差不多結束了,我給大家稍微總給一下今天的主要內容。

              

那么前面先給大家講了請求壓力的來源,我們要先分析是資源不足,還是資源不合理,如果不足的話,那么就去增加資源,這樣就是沒有其他更好的辦法了;如果是資源不合理,這個就是我們今天主要講的要去面對的一個問題,資源不合理的時候,就需要去,先去判斷它是如何不合理的,當不合理當中,我們還會有一個沒有峰值的場景呢,就會去從計算上面去優(yōu)化,從存儲上面去優(yōu)化,或者從網絡上面去優(yōu)化,或者從集群上面去優(yōu)化,分為這幾個方面去進行優(yōu)化。

對于有峰值這樣比較難處理的請求,我們依然會有兩種方案去解決,也會分為規(guī)律或者是不規(guī)律的,如果是規(guī)律的呢,就使用這種容器化定時擴容的思路;如果是可以去異步的服務呢,我們可以去使用隊列,進行消息的削峰。

Q&A

(1) 如何不重啟服務,把一臺服務器加到Nginx負載下?

那么這個NGINX這個加到負載下,Nginx它本身是可以去進行動態(tài)加載的,這個使用命令去配置好之后,使用命令去動態(tài)加載就可以了。

(2) 這里還有緩存不存在,緩存不存在的鍵如何避免查詢,不存在的鍵攻擊?

這個問題呢,就需要去從這個緩存前面,大家可能去從前面去進行一些處理,緩存本身這樣去做處理這樣的一個問題可能比較難,大家可以考慮在緩存前面增加一些過濾,這樣的一些方法,可以去達到這樣的一個目的,防止進行一個惡意攻擊。這里還有,我們看一下還有什么。

(3) 云用的是什么方式存儲數(shù)據的?

這個云用的,現(xiàn)在公有云的存儲方式,這種大型的存儲用的多的一般是用對象存儲,比如像這種開源的一個存儲方案,像Ceph,這樣的一個存儲方案,大家可以去了解一下,在屏幕上寫一下。Ceph,這樣Ceph是一個現(xiàn)在非常強大的一個對象存儲,還有一個呢,是Swift 這樣的一個,也是一個對象存儲方案,這兩個都是開源的,這個Swift 還是基于Python去實現(xiàn)的。這里是兩個開源的對象存儲,那么像公有云用的多的,像亞馬遜的ES3,或者像我們金山云也有 KS3 這樣的,它也是基于這樣對象存儲去實現(xiàn)的。

(4) 內核參數(shù)擁抱半連接,全連接?

半連接,如果熟悉三次握手的話,你就會比較方便的去理解這樣的一個東西,這里我大概畫一下,在第一次握手的時候它會發(fā)送一個SYN的一個號給這個服務端,發(fā)送給這個服務端,發(fā)送給服務端之后,大家知道,這個發(fā)送過來,如果是一個,那當然沒有問題,服務端會立馬處理這樣的一個連接。如果是發(fā)送了很多,比如說這里是成千上萬,或者是幾十萬這樣的一個請求過來之后,服務端它不能夠立即去處理這樣的一個請求,所以它本身在這個內核當中,它會有這樣的一個隊列,它會有這樣的一個隊列當中,它會去存放來的這樣一些請求的SYN的一些數(shù)據,那么它存放之后,對于這個內核來講,它處理的實際上是從這個隊列當中出來的一些數(shù)據,一些出來的一些SYN,它就從這個隊列當中去拿到這樣的一些結果,那么這個就是一個我們所說的一個半連接的一個隊列,那么全連接呢,就是三次握手結束之后,對于這個客戶端和服務端已經建立好了一個連接,比如說這里已經建立好了連接,建立好了連接之后,這樣的一些連接的請求,它也會存放在一個隊列當中,這個隊列就稱之為是一個全連接的隊列,不知道這樣說,這位同學可清楚嗎?

(5) 流量削峰是系統(tǒng)默認嗎?

這個是需要你本身從這個業(yè)務層面去實現(xiàn)的這樣一個東西,就是你需要去在你的一個代碼層面去處理你的一個連接。

(6) 典型的I/O密集場景是什么?

協(xié)程不是只適用于這個I/O密集場景,協(xié)程在工程當中,實際上協(xié)程用的多是這種非阻塞,異步并發(fā)的場景用的比較多。典型的I/O密集型場景,典型的IO密集型場景就是和計算相關的。I/O密集型場景就是去讀取數(shù)據比較多的情況,就是在你的這個程序當中,你要去讀取數(shù)據,比如說我現(xiàn)在要去對一張圖片進行處理,如果我只是對圖片進行一個讀取,并且保存,這樣的一個簡單請求,那么這個,它就可能是一個I/O密集型的,或者是我要讀入一個一些比較大的一些,比如說我要去讀取一個字典,比如一個字典數(shù)據,這些數(shù)據去讀到這個內存當中,那么這都是一些I/O密集型的。

(7) 三次握手,SYN,ACK時候是什么連接?

這里可能這位同學沒明白剛剛說的半連接和全連接,半連接和全連接,它應該是這個在為了方便去理解這個系統(tǒng)的時候,給出的一個定義,它定義的呢,就是在這個SYN,最開始發(fā)送到服務端的時候,那么這個我再說一下這里,可能有點不清楚,SYN發(fā)過來的時候,由于服務端它來不及處理,它這里會有一個隊列,去對這個進行一個保存。這個隊列就在系統(tǒng)層面,它取了一個名字就叫半連接隊列,它跟這個本身的一個三次握手連接的一些定義是沒有關系的。

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多