摘要:協作方式在高并發場景中,必須要讓服務器同時維護大量請求連接,可能是一個服務進程創建另一個進程,也可能是一個服務線程去創建另一個線程,但連接結束后進程或線程就銷毀了,這是一個巨大的浪費一個自然的想法就是通過創建一個進程線程池從而達到資源復用,
協作方式
在高并發場景中,必須要讓服務器同時維護大量請求連接,可能是一個服務進程創建另一個進程,也可能是一個服務線程去創建另一個線程,但連接結束后進程或線程就銷毀了,這是一個巨大的浪費
一個自然的想法就是通過創建一個進程/線程池從而達到資源復用,一個進程/線程可以處理多個連接
那么如何處理多個連接?
同步阻塞
一個請求占用一個進程處理,先等待數據準備好,然后從內核向進程復制數據,最后處理完數據后返回
如果一個進程處理一個請求,再來請求再開進程,雖然會有CPU在等待IO時的浪費和進程數量限制,但還是可以做到一定的高性能。如果一個進程處理多個連接,那么其他連接會在第一個連接導致的IO操作時被阻塞,這樣無法做到高性能,所以不會選擇該模式實現高性能
同步非阻塞
進程先將一個套接字在內核中設置成非阻塞再等待數據準備好,在這個過程中反復輪詢內核數據是否準備好,準備好之后最后處理數據返回
一個進程處理一個請求不太實際,一個進程處理多個請求的性能上限會更高,所以簡單的處理同步阻塞中的阻塞問題的方式就是一個進程輪詢多個連接,但輪詢是有CPU開銷的,且如果一個進程有成千上萬的連接時效率很低,也不會選擇該模式實現高性能
I/O多路復用
相當于對同步非阻塞的優化版本,區別在于I/O多路復用阻塞在select,epoll這樣的系統調用之上,而沒有阻塞在真正的I/O系統調用如recvfrom之上。換句話說,輪詢機制被優化成通知機制,多個連接公用一個阻塞對象,進程只需要在一個阻塞對象上等待,無需再輪詢所有連接
當某條連接有新的數據可以處理時,操作系統會通知進程,進程從阻塞狀態返回,開始處理業務,這是高性能的基礎,但仍不算高效,因為讓一個進程/線程進行select是不夠的,還需要某種機制來分配進程/線程去負責監聽、處理數據這個兩個過程才能實現高性能
Reactor
I/O多路復用結合線程池就是Reactor
Reactor的核心包括Reactor(監聽和分配事件)和處理資源池(負責處理事件),具體實現可以多變,體現在:
Reactor的數量可以變化
處理資源池的數量可以變化,可以是單個進程/線程,也可以是多個進程/線程
單Reactor單進程/線程
Reactor對象通過select監控連接事件,收到事件后通過dispatch分發
如果是建立連接,交給Acceptor處理,通過accept接收連接,創建一個Handler來處理連接后續的事件
如果是不是建立連接事件,交給之前建立連接階段創建的對應的Handler處理
優點是簡單,沒有進程間通信、競爭,缺點是只有一個進程,無法發揮多核CPU性能,且Handler上處理某個連接的業務時,整個進程無法處理任何其他事件
所以適用場景不多,適合于業務處理非常快的場景,如Redis
單Reactor多線程
與單Reactor單進程/線程在于Handler只負責響應事件,業務處理交給Processor,且Processor會在獨立的子線程中處理,然后將結果發給主進程的Handler處理
優點是充分發揮了多核CPU的能力,缺點是多線程數據共享復雜,且Reactor承擔所有事件的監聽和響應,高并發會成為瓶頸
多Reactor多進程/線程
為了解決單Reactor多線程的問題,這個模式的區別:
父進程的select監聽到連接建立事件后通過Acceptor將新的連接分配給子進程
子進程的Reactor將新的連接加入自己的連接隊列進行監聽,并創建一個Handler用于處理連接的事件
當有新的事件發生,子Reactor會調用連接的Handler
Handler完成read->業務處理->send的業務流程
看起來比單Reactor多線程更復雜,但實現更簡單,因為:
父進程只負責接收并建立新連接,子進程只負責業務處理
父子進程之間的交互只有父進程把連接交給子進程,子進程不需要把結果返回給父進程
Nginx、Memcache、Netty使用的就是該模式
Proactor
Reactor是同步非阻塞的網絡模型,因為真正的read和send這樣的IO操作都需要用戶進程同步操作,如果把IO操作改為異步就能進一步提升性能,這就是Proactor
初始化器Initiator負責創建通知組件Proactor和處理器Handler,并且都注冊到內核
內核負責處理注冊請求,并完成IO操作
內核完成IO操作后通知Proactor
Proactor回調到Handler
Handler完成業務處理,Handler也可以注冊新的Handler到內核
理論上Proactor的效率高于Reactor,讓IO操作與計算重疊,但要實現真正的異步IO,需要操作系統支持,Windows支持而Linux不完善
實踐方式
以上是操作系統或Nginx或高性能服務器軟件已經幫我們解決了,我們在編碼的時候除非達到了代碼的性能極限,一般不需要擔心這方面
所以下面談到的是一些作為開發人員,為了提升單體服務的性能而需要注意的地方
高性能的代碼
性能
選用高性能的框架。比如Java方面考慮用Netty,Go方面考慮用Gin
代碼細節。這塊是與我們最息息相關的了,如何寫出高性能代碼,每種語言都有自己的最佳實踐,反而這里沒辦法講到,需要日常學習積累。比如字符串拼接效率如何最高?哪個數據結構適合在某個業務場景使用?
IO細節。由于磁盤的讀寫速度遠低于CPU、內存,所以對磁盤的讀寫往往會嚴重拖慢性能,比如寫日志,不注意的話可能本地寫了一份日志文件,控制臺也在輸出日志信息,另外一個文件上傳流也在寫入信息,那么log會成倍地拖慢速度,所以需要統一日志輸出方式,比如只往日志收集流中寫入到EFK系統中查看
單體服務器壓測
寫出了自認為高性能的代碼?趕緊來壓測試一遍,壓測就一個目的:尋找瓶頸
在接近于生產環境下的機器做壓測才是最真實的,還需要使用專門的壓測機來避免環境的影響,最簡單的方式是通過ab工具測試QPS是多少,同時檢測CPU、內存、網絡流量是否達到了瓶頸,然后再根據瓶頸,尋找解決方案,這就是大體壓測以及優化的思路,單體應用的壓測還挺簡單,至于集群的壓測就需要考慮更多,日后再說
最近我對一個服務進行了壓測,QPS是1200,并且是跑在3臺虛擬機上的,瓶頸在于CPU,所以很明顯單體服務的性能太低或者是總路由出現了轉發問題,這里不考慮后者,我們先分析這個服務的接口是拿來干什么的,這個接口僅僅做了一件事,從Redis獲取數據,轉發給前端,這里也不考慮Redis的性能問題,那么就可能是在處理數據的時候性能太低。所以同事將返回的json壓縮了一下,從40kb壓縮到了20kb,QPS直接提升到2500。這就是一個簡單的壓測后調優的例子,還可以參考這里。
合適的服務器
規格
如果你用過云服務,那么肯定會在啟動實例的時候被強迫去選擇一個規格的實例,如下
那么請根據你的服務是哪種性能需要,選擇對應的服務器呢,當然還要考慮你滴錢包夠不夠
配置
在Linux平臺上,在進行高并發TCP連接處理時,最高的并發數量都要受到系統對用戶單一進程同時可打開文件數量的限制(這是因為系統為每個TCP連接都要創建一個socket句柄,每個socket句柄同時也是一個文件句柄)。可使用ulimit命令查看系統允許當前用戶進程打開的文件數限制
類似的,對Linux系統配置也會影響到性能的參數需要格外注意,但也需要遵從一個方式:按需調整
感謝您耐心看完的文章
順便給大家推薦一個Java技術交流群:710373545里面會分享一些資深架構師錄制的視頻資料:有Spring,MyBatis,Netty源碼分析,高并發、高性能、分布式、微服務架構的原理,JVM性能優化、分布式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74694.html
摘要:作為面試官,我是如何甄別應聘者的包裝程度語言和等其他語言的對比分析和主從復制的原理詳解和持久化的原理是什么面試中經常被問到的持久化與恢復實現故障恢復自動化詳解哨兵技術查漏補缺最易錯過的技術要點大掃盲意外宕機不難解決,但你真的懂數據恢復嗎每秒 作為面試官,我是如何甄別應聘者的包裝程度Go語言和Java、python等其他語言的對比分析 Redis和MySQL Redis:主從復制的原理詳...
摘要:作為面試官,我是如何甄別應聘者的包裝程度語言和等其他語言的對比分析和主從復制的原理詳解和持久化的原理是什么面試中經常被問到的持久化與恢復實現故障恢復自動化詳解哨兵技術查漏補缺最易錯過的技術要點大掃盲意外宕機不難解決,但你真的懂數據恢復嗎每秒 作為面試官,我是如何甄別應聘者的包裝程度Go語言和Java、python等其他語言的對比分析 Redis和MySQL Redis:主從復制的原理詳...
閱讀 2856·2021-10-14 09:42
閱讀 3174·2019-08-30 15:52
閱讀 3240·2019-08-30 14:02
閱讀 1102·2019-08-29 15:42
閱讀 529·2019-08-29 13:20
閱讀 1157·2019-08-29 12:24
閱讀 469·2019-08-26 10:20
閱讀 680·2019-08-23 18:31