摘要:本文介紹高併發系統的度量指標,講述高併發系統的設計思路,再梳理高併發的關鍵技術,最後結合做者的經驗作一些延伸探討。
當前,數字化在給企業帶來業務創新,推進企業高速發展的同時,也給企業的IT軟件系統帶來了嚴峻的挑戰。面對流量高峯,不一樣的企業是如何經過技術手段解決高併發難題的呢?程序員
軟件系統有三個追求:高性能、高併發、高可用,俗稱三高。三者既有區別也有聯繫,門門道道不少,全面討論須要三天三夜,本篇討論高併發。面試
高併發(High Concurrency)。併發是操做系統領域的一個概念,指的是一段時間內多任務流交替執行的現象,後來這個概念被泛化,高併發用來指大流量、高請求的業務情景,好比春運搶票,電商雙十一,秒殺大促等場景。算法
不少程序員天天忙着搬磚,平時接觸不到高併發,哪天受不了跑去面試,還經常會被面試官犀利的高併發問題直接KO,其實吧,高併發系統也不高深,我保證任何一個智商在線的看過這篇文章後,都能打敗恐懼,重拾生活的信心。數據庫
本文先介紹高併發系統的度量指標,而後講述高併發系統的設計思路,再梳理高併發的關鍵技術,最後結合做者的經驗作一些延伸探討。編程
既然是高併發系統,那併發必定要高,否則就名存實亡。併發的指標通常有QPS、TPS、IOPS,這幾個指標都是可歸爲系統吞吐率,QPS越高系統能hold住的請求數越多,但光關注這幾個指標不夠,咱們還須要關注RT,即響應時間,也就是從發出request到收到response的時延,這個指標跟吞吐每每是此消彼長的,咱們追求的是必定時延下的高吞吐。segmentfault
好比有100萬次請求,99萬次請求都在10毫秒內響應,其餘次數10秒才響應,平均時延不高,但時延高的用戶受不了,因此,就有了TP90/TP99指標,這個指標不是求平均,而是把時延從小到大排序,取排名90%/99%的時延,這個指標越大,對慢請求越敏感。後端
除此以外,有時候,咱們也會關注可用性指標,這可歸到穩定性。緩存
通常而言,用戶感知友好的高併發系統,時延應該控制在250毫秒之內。安全
什麼樣的系統才能稱爲高併發?這個很差回答,由於它取決於系統或者業務的類型。不過我能夠告訴你一些衆所周知的指標,這樣能幫助你下次在跟人扯淡的時候稍微靠點兒譜,不至於貽笑大方。性能優化
一般,數據庫單機每秒也就能抗住幾千這個量級,而作邏輯處理的服務單臺每秒抗幾萬、甚至幾十萬都有可能,而消息隊列等中間件單機每秒處理個幾萬沒問題,因此咱們常常聽到每秒處理數百萬、數千萬的消息中間件集羣,而像阿某的API網關,每日百億請求也有可能。
高併發的設計思路有兩個方向:
提高單機處理能力又可分爲硬件和軟件兩個方面:
爲了解決分佈式系統的複雜性問題,通常會用到架構分層和服務拆分,經過分層作隔離,經過微服務解耦。
這個理論上沒有上限,只要作好層次和服務劃分,加機器擴容就能知足需求,但實際上並不是如此,一方面分佈式會增長系統複雜性,另外一方面集羣規模上去以後,也會引入一堆AIOps、服務發現、服務治理的新問題。
由於垂直向的限制,因此,咱們一般更關注水平擴展,高併發系統的實施也主要圍繞水平方向展開。
玩具式的網絡服務程序,用戶能夠直連服務器,甚至不須要數據庫,直接寫磁盤文件。但春運購票系統顯然不能這麼作,它確定扛不住這個壓力,那通常的高併發系統是怎麼作呢?好比某寶這樣的正經系統是怎麼處理高併發的呢?
其實大的思路都差很少,層次劃分 + 功能劃分。能夠把層次劃分理解爲水平方向的劃分,而功能劃分理解爲垂直方向的劃分。
首先,用戶不能直連服務器,要作分佈式就要解決「分」的問題,有多個服務實例就須要作負載均衡,有不一樣服務類型就須要服務發現。
負載均衡就是把負載(request)均衡分配到不一樣的服務實例,利用集羣的能力去對抗高併發,負載均衡是服務集羣化的實施要素,它分3種:
因此,完整的負載均衡鏈路是 client <-> DNS負載均衡 -> F5 -> LVS/SLB -> NGINX
無論選擇哪一種LB策略,或者組合LB策略,邏輯上,咱們均可以視爲負載均衡層,經過添加負載均衡層,咱們將負載均勻分散到了後面的服務集羣,具有基礎的高併發能力,但這只是萬里長征第一步。
前面經過負載均衡解決了無狀態服務的水平擴展問題,但咱們的系統不全是無狀態的,後面一般還有有狀態的數據庫,因此解決了前面的問題,存儲有可能成爲系統的瓶頸,咱們須要對有狀態存儲作分片路由。
數據庫的單機QPS通常不高,也就幾千,顯然知足不了高併發的要求。
因此,咱們須要作分庫分表 + 讀寫分離。
就是把一個庫分紅多個庫,部署在多個數據庫服務上,主庫承載寫請求,從庫承載讀請求。從庫能夠掛載多個,由於不少場景寫的請求遠少於讀的請求,這樣就把對單個庫的壓力降下來了。
若是寫的請求上升就繼續分庫分表,若是讀的請求上升就掛更多的從庫,但數據庫天生不是很適合高併發,並且數據庫對機器配置的要求通常很高,致使單位服務成本高,因此,這樣加機器抗壓力成本過高,還得另外想招。
緩存的理論依據是局部性原理。
通常系統的寫入請求遠少於讀請求,針對寫少讀多的場景,很適合引入緩存集羣。
在寫數據庫的時候同時寫一份數據到緩存集羣裏,而後用緩存集羣來承載大部分的讀請求,由於緩存集羣很容易作到高性能,因此,這樣的話,經過緩存集羣,就能夠用更少的機器資源承載更高的併發。
緩存的命中率通常能作到很高,並且速度很快,處理能力也強(單機很容易作到幾萬併發),是理想的解決方案。
CDN本質上就是緩存,被用戶大量訪問的靜態資源緩存在CDN中是目前的通用作法。
但緩存是針對讀,若是寫的壓力很大,怎麼辦?
同理,經過跟主庫加機器,耗費的機器資源是很大的,這個就是數據庫系統的特色所決定的。
相同的資源下,數據庫系統過重太複雜,因此併發承載能力就在幾千/s的量級,因此此時你須要引入別的一些技術。
好比說消息中間件技術,也就是MQ集羣,它是很是好的作寫請求異步化處理,實現削峯填谷的效果。
消息隊列能作解耦,在只須要最終一致性的場景下,很適合用來配合作流控。
假如說,每秒是1萬次寫請求,其中好比5千次請求是必須請求過來立馬寫入數據庫中的,可是另外5千次寫請求是能夠容許異步化等待個幾十秒,甚至幾分鐘後才落入數據庫內的。
那麼此時徹底能夠引入消息中間件集羣,把容許異步化的每秒5千次請求寫入MQ,而後基於MQ作一個削峯填谷。好比就以平穩的1000/s的速度消費出來而後落入數據庫中便可,此時就會大幅度下降數據庫的寫入壓力。
業界有不少著名的消息中間件,好比ZeroMQ,rabbitMQ,kafka等。
消息隊列自己也跟緩存系統同樣,能夠用不多的資源支撐很高的併發請求,用它來支撐部分容許異步化的高併發寫入是很合適的,比使用數據庫直接支撐那部分高併發請求要減小不少的機器使用量。
再強大的系統,也怕流量短事件內集中爆發,就像銀行怕擠兌同樣,因此,高併發另外一個必不可少的模塊就是流控。
流控的關鍵是流控算法,有4種常見的流控算法。
接入-邏輯-存儲是經典的互聯網後端分層,但隨着業務規模的提升,邏輯層的複雜度也上升了,因此,針對邏輯層的架構設計也出現不少新的技術和思路,常見的作法包括系統拆分,微服務。
除此以外,也有不少業界的優秀實踐,包括某信服務器經過協程(無侵入,已開源libco)改造,極大的提升了系統的併發度和穩定性,另外,緩存預熱,預計算,批量讀寫(減小IO),池技術等也普遍應用在實踐中,有效的提高了系統併發能力。
爲了提高併發能力,邏輯後端對請求的處理,通常會用到生產者-消費者多線程模型,即I/O線程負責網絡IO,協議編解碼,網絡字節流被解碼後產生的協議對象,會被包裝成task投入到task queue,而後worker線程會從該隊列取出task執行,有些系統會用多進程而非多線程,經過共享存儲,維護2個方向的shm queue,一個input q,一個output q,爲了提升併發度,有時候會引入協程,協程是用戶線程態的多執行流,它的切換成本更低,一般有更好的調度效率。
另外,構建漏斗型業務或者系統,從客戶端請求到接入層,到邏輯層,到DB層,層層遞減,過濾掉請求,Fail Fast(儘早發現儘早過濾),嘴大屁眼小,哈哈。
漏斗型系統不只僅是一個技術模型,它也能夠是一個產品思惟,配合產品的用戶分流,邏輯分離,能夠構建全方位的立體模型。
莫讓浮雲遮望眼,除去繁華識真顏。咱們不能掌握了大方案,吹完了牛皮,而忽視了編程最本質的東西,掌握最基本最核心的編程能力,好比數據架構和算法,設計,慣用法,培養技術的審美,也是很重要的,既要致高遠,又要盡精微。