聊聊如何設計千萬級吞吐量的.Net Core網絡通訊!

原文: 聊聊如何設計千萬級吞吐量的.Net Core網絡通訊!

聊聊如何設計千萬級吞吐量的.Net Core網絡通訊!

  • 做者:大石頭
  • 時間:2018-10-26 晚上 20:00
  • 地點:QQ羣-1600800
  • 內容:網絡通訊,
    1. 網絡庫使用方式
    2. 網絡庫設計理念,高性能要點

介紹

1.1 開始網絡編程

簡單的網絡程序示例

  • 相關使用介紹:https://www.cnblogs.com/nnhy/p/newlife_net_echo.html
  • 克隆上面的代碼,運行EchoTest項目,打開編譯的exe,打開兩次,一個選1做爲服務器,一個選2做爲客戶端

  • 在客戶端鏈接服務器和給服務端發送數據的時候,分別觸發StartOnReceive方法,鏈接以後服務端發送了Welcome 的消息,客戶端發送5次「你好」。服務端回傳收到的數據,打了一個日誌,把收到的信息轉成字符串輸出到控制檯。
  • NetServer是應用級網絡服務器,支持tcp/udp/ipv4/ipv6。上面能夠看到,同時監聽了四個端口。
  • 碼神工具也能夠鏈接上來

解釋

  • 對於網絡會話來講,最關鍵的就是客戶端連上來,以及收到數據包,這兩部分,對應上面StartOnReceive兩個方法

服務端

  • 上面是最小的網絡庫例程,簡單演示了服務端和客戶端,鏈接和收發信息。網絡應用分爲NetServer/NetSession,服務端、會話,N個客戶端鏈接服務器,就會有N個會話。來一個客戶端鏈接,服務端就new一個新的NetSession,並執行Start,收到一個數據包,就執行OnReceive,鏈接斷開,就執行OnDispose,這即是服務端的所有。
  • 客戶端鏈接剛上來的時候,沒有數據包等其它信息,因此這個時候沒有參數。客戶端發數據包過來,OnReceive函數在處理。
  • 服務端的建立,能夠是很簡單,看如下截圖。這裏爲了測試方便,開了不少Log,實際使用的時候,根據須要註釋。
  • 長鏈接、心跳第二節設計理念再講。

客戶端

  • 跟不少網絡庫不一樣,NewLife.Net除了服務端,還封裝了客戶端。客戶端的核心,也就是Send函數和Received事件,同步發送,異步接收。
  • 由於是長鏈接,因此服務端隨時能夠向客戶端發送數據包,客戶端也能夠收到。tcp在不作設置的時候,默認長鏈接2小時。
  • NetServer默認20分鐘,在沒有心跳的時候,20分鐘沒有數據包往來,服務端會幹掉這個會話。
  • 雖然上面講的NetServer和Client,都是tcp,可是換成其它協議也是能夠的。這裏的NetServer和NetUri.CreateRemote,同時支持Tcp/Udp/IPv4/IPv6等,CreateRemote內部,就是根據地址的不一樣,去new不一樣的客戶端。因此咱們寫的代碼,根本不在乎用的是tcp仍是udp,或者IPv6。有興趣的能夠看看源碼

1.2 構建可靠網絡服務

  • 相關博客
  • 要真正造成一個網絡服務,那得穩定可靠。上面例程EchoTest只是簡單演示,接下來看下一個例程EchoAgent。


安裝運行

  • 這是一個標準的Windows服務,有了這個東西,咱們就能夠妥妥的註冊到Windows裏面去。這也是目前咱們大量數據分析程序的必備。
  • 首先運行EchoAgent,按2,安裝註冊服務,用管理員身份運行。安裝成功而後能夠在服務裏面找到剛剛安裝的服務。



  • 安裝完成能夠在服務上找到,再次按2就是卸載,這個是XAgent提供的功能

  • 這時候按3,啓動服務

代碼解釋

  • 接下來看代碼,服務啓動的時候,執行StartWork。在這個時候實例化並啓動NetServer,獲得的效果就跟例程EchoTest同樣,區別是一個是控制檯一個是服務。中止服務時執行StopWork,咱們能夠在這裏關閉NetServer。詳細請看源碼
  • 必須有這個東西,你的網絡服務程序,纔有可能達到產品級。linux上直接控制檯,上nohup,固然還有不少其它辦法。之後但願這個XAgent可以支持linux吧,這樣就一勞永逸了

1.3 壓測

  • 相關博客
  • 只須要記住一個兩個數字,.net應用打出來2266萬tps,流量峯值4.5Gbps
  • 兩千萬吞吐量的數字,固然,只能看不能用。由於服務端只是剛纔的Echo而已,並無帶什麼業務。實際工做中,帶着業務和數據庫,能跑到10萬已經很是很是牛逼了。
  • 咱們工做中的服務能夠跑到100萬,可是我不敢,怕它不當心就崩了。因此咱們都是按照10萬的上限來設計,不夠就堆服務器好了,達到5萬以上後,穩定性更重要node

    網絡編程的坑

  • 主要有粘包
  • 程序員中會網絡編程的少,會解決粘包的更少!linux

1.4 網絡編程的坎——粘包

  • 廣泛狀況,上萬的程序員,會寫網絡程序的不到20%,會解決粘包問題的不到1%,若是你們會寫網絡程序,而且能解決粘包,那麼至少已經達到了網絡編程的中級水平。

什麼是粘包

  • 舉個栗子:客戶端連續發了5個包,服務端就收到了一個大包。代碼就不演示了,把第一個例程的這個睡眠去掉。

  • 客戶端連續發了5個包,服務端就收到了一個大包。

緣由

  • 不少人可能都據說Tcp是流式協議,可是不多人去問,什麼叫流式吧?流式,就是它把數據像管道同樣傳輸過去。
  • 剛纔咱們發了5個 「你好」,它負責把這10個字發到對方,至於發多少次,每次發幾個字,不用咱們操心,tcp底層本身處理。tcp負責把數據一個不丟的按順序的發過去。因此,爲了性能,它通常會把相近的數據包湊到一塊兒發過去。對方收到一個大包,5個小包都粘在了一塊兒,這就是最簡單的粘包。
  • 這個特性由NoDelay設置決定。NoDelay默認是false,須要本身設置。若是設置了,就不會等待。可是不要想得那麼美好,由於對方可能合包。
  • 局域網MTU(Maximum Transmission Unit,最大傳輸單元)是1500,處於ip tcp 頭部等,大概1472多點的樣子。

更復雜的粘包及解決方法

  • A 1000 字節 B 也是 1000字節,對方可能收到兩個包,1400 + 600。對方可能收到兩個包,1400 + 600。
  • 凡是以特殊符號開頭或結尾來處理粘包的辦法,都會有這樣那樣的缺陷,最終是給本身挖坑。因此,tcp粘包,絕大部分解決方案,偏向於指定數據包長度。這其中大部分使用4字節長度,長度+數據。對方收到的時候,根據長度判斷後面數據足夠了沒有。
  • 這是粘包的處理代碼:http://git.newlifex.com/NewLife/X/Blob/master/NewLife.Core/Net/Handlers/MessageCodec.cs
  • 每次判斷長度,接收一個或多個包,若是接收不完,留下,存起來。等下一個包到來的時候,拼湊完整。
  • 雖然tcp確保數據不丟,可是不免咱們本身失手,弄丟了一點點數據。爲了不禍害後面全部包,就須要進行特殊處理了。
  • 每一個數據幀,本身把頭部長度和數據體湊一塊兒發送啊,tcp確保順序。這裏咱們把超時時間設置爲3~5秒,每次湊包,若是發現上次有殘留,而且超時了,那麼就扔了它,免得禍害後面。
  • 根據以上,粘包的關鍵解決辦法,就是設定數據格式,能夠看看咱們的SRMP協議,1字節標識,1字節序號,2字節長度
  • 若是客戶端發送太頻繁,服務端tcp緩衝區阻塞,發送窗口會逐步縮小到0,再也不接受客戶端數據。

1.5 .NetCore版RPC框架

代碼分析

  • 咱們看這部分代碼,4次調用遠程函數,成功獲取結果,包括二進制高速調用、返回複雜對象、捕獲遠程異常,沒錯,這就是一個RPC。

服務端

  • 有沒有發現,這個ApiServer跟前面的NetServer有點像?其實ApiServer內部就有一個NetServer
  • 這麼些行代碼,就幾個地方有價值,一個是註冊了兩個控制器。你能夠直接理解爲Mvc的控制器,只不過咱們沒有路由管理系統,直接手工註冊。
  • 第二個是指定編碼器爲Json,用Json傳輸參數和返回值。其實內部默認就是Json,能夠不用指定
  • 看看咱們的控制器,特別像Mvc,只不過這裏的Controller沒有基類,各個Action返回值不是ActionResult,是的,ApiServer就是一個按照Mvc風格設置的RPC框架
  • 返回複雜對象
  • 作請求預處理,甚至攔截異常
  • 像下面這樣寫RPC服務,而後把它註冊到ApiServer上,客戶端就能夠在1234端口上請求這些接口服務啦

客戶端

  • 客戶端是ApiClient,這裏的MyClient繼承自ApiClient

  • 這些就是咱們剛纔客戶端遠程調用的stub代碼啦,固然,咱們沒有自動生成stub,也沒有要求客戶端跟服務端共用接口之類。實際上,咱們認爲徹底沒有必要作接口約束,大部分項目的服務接口不多,而且要求靈活多變
  • stub就是相似於,剛纔那個MyController實現IAbc接口,而後客戶端根據服務端元數據自動在內存裏面生成一個stub類並編譯,這個類實現了IAbc接口。
    客戶端直接操做接口,還覺得在調用服務端 的函數呢
  • 其實stub代碼內部,就是封裝了 這裏的InvokeAsync這些代碼,等同於自動生成這些代碼,包括gRPC、Thrift等都是這麼幹的

框架解析

  • 這個RPC框架,封包協議就是剛纔的SRMP,負載數據也就是協議是json
  • 當須要高速傳輸的時候,參數用Byte[],它就會直接傳輸,不經json序列化,這是多年經驗獲得的靈活性與性能的最佳結合點

2.1 人人都有一個本身的高性能網絡庫

  • 網絡庫核心代碼:http://git.newlifex.com/NewLife/X/Tree/master/NewLife.Core/Net
  • 咱們一開始就是讓Tcp/Udp能夠混合使用,網絡庫設計於2005年,應該要比現現在絕大部分網絡框架要老。服務端清一色採用 Server+Session 的方式。
  • 網絡庫的幾個精髓文件
  • 其中比較重要的一個,裏面實現了 Open/Close/Send/Receive 系列封裝,Tcp/Udp略有不一樣,重載就行了。打開關閉比較簡單,就不講了
  • 全部對象,無論客戶端服務端,都實現ISocket。而後客戶端Client,服務端Server+Session。tcp+udp同時支持並不難,由於它們都基於Socket。
  • 目前無狀態無會話的通訊架構,作不到高性能。咱們就是依靠長鏈接以及合併小包,實現超高吞吐量
  • 通常靈活性和高性能都是互相矛盾的

2.2 高性能設計要點

  • 第一要點:同步發送,由於要作發送隊列、拆分、合併,等等,異步發送大大增長了複雜度。你們若是未來遇到詭異的40ms延遲,很是可能就是tcp的nodelay做怪,能夠設爲true解決
  • 第二要點:IOCP,高吞吐率的服務端,必定是異步接收,而不是多線程同步。固然,能夠指定若干個線程去select,也就是Linux裏面常見的poll,那個不在這裏討論,Windows極少人這麼幹,大量資料代表,iocp更厲害。

    • SAEA是.net/.netcore當下最流行的網絡架構,咱們能夠通俗理解爲,把這個緩衝區送給操做系統內核,待會有數據到來的時候,直接放在裏面,這樣子就減小了一次內核態到用戶態的拷貝過程。
    • 咱們測試4.5Gbps,除以8,大概是 540M字節,這個拷貝成本極高
  • 第三要點:零拷貝ZeroCopy,這也是netty的核心優點。iocp是爲了減小內核態到用戶態的拷貝,zerocopy進一步把這個數據交給用戶層,不用拷貝了。
    • 數據處理,咱們採用了鏈式管道,
    • 這些都是管道的編碼器
  • 第四要點:合併小包,NoDelay=false,容許tcp合併小包,MTU=1500,除了頭部,通常是1472
  • 第五要點:二進制序列化,消息報文儘量短小,每一個包1k,對於100Mbps,也就12M,理論上最多12000包,因此大量Json協議或者字符串協議,吞吐量都在1萬上下
    • SRMP頭部4字節,ApiServer的消息報文,通常二三十個字節,甚至十幾個字節
  • 第五要點:批量操做User FindByID(int id); User[] FindByIDs(int[] ids);

最後

  • 整理不全,你們湊合着看。中途錄屏,語音啥的還掉了,準備得不是很好,下週再來一次吧,選Redis
相關文章
相關標籤/搜索