如何編寫一個 SendFile 服務器

# 如何編寫一個 SendFile 服務器react

# 前言nginx

以前討論零拷貝的時候,咱們知道,兩臺機器之間傳輸文件,最快的方式就是 send file,衆所周知,在 Java 中,該技術對應的則是 FileChannel 類的 transferTo 和 transferFrom 方法。git

在平時使用服務器的時候,好比 nginx ,tomcat ,都有 send file 的選項,利用此技術,可大大提升文件傳輸效能。github

另外,可能也有人談論 send file 的缺點,例如不能利用 gzip 壓縮,不能加密。這裏本文不作探討。api

紙上得來終覺淺,絕知此事要躬行。數組

那麼,如何使用這兩個 api 實現一個 send file 服務器和客戶端呢?緩存

想象一下,你寫的 send file 服務器利用 send file 技術,利用萬兆網卡,從各個 client 端 copy 海量文件,瞬間打爆你那 1TB 的磁盤和 48核的 CPU。而且,注意:只需很小的 JVM  內存就能夠實現這樣一臺強悍的服務器。爲何?若是你知道 send file 的原理,就會知道,使用 send file 技術時, 在用戶態中,是不須要多少內存的,數據都在內核態。tomcat

是否是頗有成就感?什麼?沒有?那打擾了 🤣。安全

另外,關於 send file,咱們都知道,因爲是直接從內核緩衝區進入到網卡驅動,咱們幾乎能夠稱之爲 「零拷貝」,他的性能十分強勁。性能優化

可是。

除了這個,還有其餘的嗎?答案是有的,send file 利用 DMA 的方式 copy 數據,而不是利用 CPU。注意,不利用 CPU 意味着什麼?意味着數據不會進入「緩存行」,進一步,不會進入緩存行,表明着緩存行不會由於這個被污染,再進一步,就是不須要維護緩存一致性。

還記得咱們由於這個特性搞的那些關於 「僞共享」 的各類黑科技嗎?是否是又學到了一點呢?😎

# 理念

做爲一個純粹的,高尚的,有趣的 sendFile 服務器或者客戶端,使用場景是嵌入到某個服務中,或者某個中間件中,不須要搞成誇張的容器。咱們能夠借鑑一下,客戶端能夠作成 Jedis 那樣的,若是你想搞個鏈接池也不是不能夠,但 client 自身實例,仍是單鏈接的。服務端能夠作成 sun 的 httpServer 那種輕量的,隨時啓動,隨時關閉。

同時, 支持 oneway 的高性能發送,由於,只要機器不宕機,發送到網卡就意味着發送成功,這樣能大幅提升發送速度,減小客戶端阻塞時間。

另外,也支持帶有 ack 的穩定發送,即只有返回 ack 了,才能確認數據已經寫到目標服務器磁盤了。

server 端支持海量鏈接,必須得是  reactor 網絡模型,但咱們不想在這麼小的組件裏用 netty,過重了,還容易和使用方有 jar 衝突。因此,咱們能夠利用 Java 的 selector + nio 本身實現 Reactor 模型。

# 設計

### IO 模型設計

設計圖:

![](https://tva1.sinaimg.cn/large/006y8mN6ly1g8esdrp8fhj30zr0u00u2.jpg)

如上圖,Server 端支持海量客戶端鏈接。

server 端含有 多個處理器,其中包括 accept 處理器,read 處理器 group, write 處理器 group。

accept 處理器將 serverSocketChannel 做爲 key 註冊到一個單獨的 selector 上。專門用於監聽 accept 事件。相似 netty 的 boss 線程。

當 accept 處理器成功鏈接了一個 socket 時,會隨機將其交給一個 readProcessor(netty worker 線程?) 處理器,readProcessor 又會將其註冊到 readSelector 上,當發生 read 事件時,readProcessor 將接受數據。

能夠看到,readProcessor 能夠認爲是一個多路複用的線程,利用 selector 的能力,他高效的管理着多個 socket。

readProcessor 在讀到數據後,會將其寫入到磁盤中(DMA 的方式,性能炸裂)。

而後,若是 client 在 RPC 協議中聲明「須要回覆(id 不爲 -1)」 時,那就將結果發送到 Reply Queue 中,反之沒必要。

當結果發送到  Reply Queue 後,writer 組中的 寫線程,則會從 Queue 中拉取回復包,而後將結果按照 RPC 協議,寫回到 client socket 中。

client socket 也會監聽着 read 事件,注意:client 是不須要 select 的,由於不必,selector 只是性能優化的一種方式——即一個線程管理海量鏈接,若是沒有 select, 應用層沒法用較低的成本處理海量鏈接,注意,不是不能處理,只是不能高效處理。

回過來,當 client socket  獲得 server 的數據包,會進行解碼反序列化,並喚醒阻塞在客戶端的線程。從而完成一次調用。

### 線程模型

設計圖:

![image-20191029093524267](https://tva1.sinaimg.cn/large/006y8mN6ly1g8etjmkuejj30vv0u0wg2.jpg)

如上圖所示。

#### 在 client 端:

 每一個 Client 實例,維護一個 TCP 鏈接。該 Client 的寫入方法是線程安全的。

當用戶併發寫入時,可併發寫的同時併發回覆,由於寫和回覆是異步的(此時可能會出現,線程 A 先 send ,線程 B 後 send,但因爲網絡延遲,B 先返回)。

#### 在 server 端:

server 端維護着一個 ServerSocketChannel 實例,該實例的做用就是接收 accep 事件,且由一個線程維護這個 accept selector 。

當有新的 client 鏈接事件時,accept selector 就將這個鏈接「交給「 read 線程(默認 server 有 4 個 read 線程)。

#### 什麼是「交給」?

注意:每一個 read 線程都維護着一個單獨的 selector。 4 個 read 線程,就維護了 4 個 selector。

當 accept 獲得新的客戶端鏈接時,先從 4 個read 線程組裏 get 一個線程,而後將這個 客戶端鏈接 做爲 key 註冊到這個線程所對應的 read selector 上。從而將這個 Socket 「交給」 read 線程。

而這個 read 線程則使用這個 selector 輪詢事件,若是 socket 可讀,那麼就進行讀,讀完以後,利用 DMA 寫進磁盤。

# RPC 協議

#### Server RPC 回覆包協議

| 字段名稱  | 字段長度(byte) | 字段做用                          |
| --------- | -------------- | --------------------------------- |
| magic_num | 4              | 魔數校驗,fast fail               |
| version   | 1              | rpc 協議版本                      |
| id        | 8              | Request id, TCP 多路複用 id      |
| length    | 8              | rpc 實際消息內容的長度            |
| Content   | length         | rpc 實際消息內容(JSON 序列化協議) |

#### Client RPC 發送包協議

| 字段名稱    | 字段長度(byte) | 字段做用                                          |
| ----------- | -------------- | ------------------------------------------------- |
| magic_num   | 4              | 魔數校驗,fast fail                               |
| id          | 8              | Request id, TCP 多路複用 id, 默認 -1,表示不回覆 |
| nameContent | 2              | Request id, TCP 多路複用 id                      |
| bodyLength  | 8              | rpc 實際消息內容的長度                            |
| nameContent | bodyLength     | 文件名 UTF-8 數組                                 |

爲何 發送包和返回包協議不一樣?爲了高效。

# 總結

注意:這是一個能用的,性能不錯的,輕量的 SendFile 服務器實現,本地測試時, IO寫盤達到  824MB/S,4c 4.2g inter i7 CPU 滿載。

![image-20191029120446781](https://tva1.sinaimg.cn/large/006y8mN6ly1g8exv1xxa5j30gg0bk0ux.jpg)

代碼地址:https://github.com/stateIs0/send_file

同時,歡迎你們 star, pr,issue。我來改進。

相關文章
相關標籤/搜索