<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          同事使用 Dubbo 傳輸文件,被點名批評!

          共 6131字,需瀏覽 13分鐘

           ·

          2021-06-22 12:27

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          成功路上并不擁擠,因為堅持的人不多。

          編輯:業(yè)余草

          juejin.cn/post/6963642641506369566

          推薦:https://www.xttblog.com/?p=5221

          公司之前有一個 Dubbo 服務(wù),其內(nèi)部封裝了騰訊云的對象存儲服務(wù) SDK,目的是統(tǒng)一管理這種三方服務(wù)的SDK,其他系統(tǒng)直接調(diào)用這個對象存儲的 Dubbo 服務(wù)。這樣可以避免因平臺 SDK 出現(xiàn)不兼容的大版本更新,從而導(dǎo)致公司所有系統(tǒng)修改跟著升級的問題。

          想法是好的,不過這種做法并不合適,因為 Dubbo 并不適合傳輸文件。好在這個系統(tǒng)在上線不久就沒人用廢棄了……

          雖然系統(tǒng)廢棄了,不過就這個 Dubbo 上傳文件的主題還是可以詳細(xì)分析下,聊聊它到底為什么不適合傳文件。

          Dubbo 怎么傳文件?

          難道這樣直接傳 File 嗎?

          void sendPhoto(File photo);  

          當(dāng)然不行!Dubbo 只是將對象進(jìn)行序列化然后傳輸,而 File 對象就算序列化也無法處理文件的數(shù)據(jù),所以只能直接發(fā)送文件內(nèi)容:

          void sendPhoto(byte[] photo);  

          但這樣就會導(dǎo)致 consumer 端需要一次性讀取完整的文件內(nèi)容至內(nèi)存中,再大的內(nèi)存也扛不住這樣玩。而且 provider 端在接受數(shù)據(jù)解析報文時,也需要一次性將 byte[] 讀取至內(nèi)存中,也是一樣有內(nèi)存占用過高問題。

          單連接模型問題

          除了內(nèi)存占用問題之外,Dubbo(這里指 Dubbo 協(xié)議)的單連接模型也不適合文件傳輸。

          Dubbo 協(xié)議默認(rèn)是單連接的模型,即一個 provider 的所有請求都是用一個 TCP 連接。默認(rèn)使用 Netty 來進(jìn)行傳輸,而 Netty 中為了保證 Channel 線程安全,會將寫入事件進(jìn)行排隊處理。那么在單連接下,多個請求都會使用同一個連接,也就是同一個 Channel 進(jìn)行寫入數(shù)據(jù);當(dāng)多個請求同時寫入時,如果某個報文過大,會導(dǎo)致 Channel 一直在發(fā)送這個報文,其他請求的報文寫入事件會進(jìn)行排隊,遲遲無法發(fā)送,數(shù)據(jù)都沒有發(fā)送過去,那么其他的 consumer 也自然會處于阻塞等待響應(yīng)的狀態(tài)中,一直無法返回了。

          所以在單連接下,如果報文過大,會導(dǎo)致 Netty 的寫入事件處理阻塞,無法及時的將數(shù)據(jù)發(fā)送至服務(wù)端,從而造成請求白白阻塞的問題。

          那既然單連接模型有這么大的缺點,為什么 Dubbo 還要采用單連接呢?

          因為省資源啊,TCP 連接這種資源可是很寶貴的,如果單連接可以滿足絕大多數(shù)場景,那么完全不需要為每個請求準(zhǔn)備一個連接。

          Dubbo 文檔中也提到了單連接設(shè)計的原因:

          ?

          因為服務(wù)的現(xiàn)狀大都是服務(wù)提供者少,通常只有幾臺機(jī)器,而服務(wù)的消費者多,可能整個網(wǎng)站都在訪問該服務(wù),比如 Morgan 的提供者只有 6 臺提供者,卻有上百臺消費者,每天有 1.5 億次調(diào)用,如果采用常規(guī)的 hessian 服務(wù),服務(wù)提供者很容易就被壓跨,通過單一連接,保證單一消費者不會壓死提供者,長連接,減少連接握手驗證等,并使用異步 IO,復(fù)用線程池,防止 C10K 問題。

          ?

          雖然 Dubbo 協(xié)議默認(rèn)單連接模型,但還是可以設(shè)置多連接的:

          <dubbo:service connections\="1"/>  
          <dubbo:reference connections\="1"/>

          不過多連接下,連接和請求并不是一一對應(yīng)的,而是一個輪詢的機(jī)制。如下圖所示,當(dāng)配置了N個連接時,對于每一個 Provider 實例都會維護(hù)多個連接,在執(zhí)行請求時會通過輪詢的機(jī)制,為每次請求分配不同的連接

          為什么 HTTP 協(xié)議“適合”傳文件?

          其實這么說并不嚴(yán)謹(jǐn),并不是 HTTP 協(xié)議適合傳文件,Dubbo 還支持 HTTP 協(xié)議呢(雖然是半殘品),一樣不適合傳文件。

          Dubbo 這類 RPC 框架為了滿足“調(diào)用本地方法像調(diào)用遠(yuǎn)程一樣”,必須將數(shù)據(jù)序列化成語言里的對象,但這樣一來就導(dǎo)致無法處理 File 這種形式的對象了。

          如果跳出 Dubbo 這種 RPC 框架特性的限制,單獨看 HTTP 協(xié)議的話,是很適合傳輸文件的。因為對于 Client 來說,只需要將報文發(fā)送至 Server,比如要傳輸?shù)奈募诒镜氐脑挘俏彝耆梢悦看沃蛔x取文件的一個 Buffer 大小,然后將這個 Buffer 的數(shù)據(jù)使用 Socket 發(fā)送即可;在這種方式下,同時存在于內(nèi)存中的數(shù)據(jù),只會有一個 Buffer 大小,不會有 Dubbo 那樣將全部數(shù)據(jù)讀取至內(nèi)存的問題。

          如下圖所示,Client 每次只從1GB 文件中讀取 4K 大小的 Buffer 數(shù)據(jù),然后用 Socket 發(fā)送,直至將文件完全讀取并發(fā)送成功。那么這種方式下對于單次傳輸來說,內(nèi)存始終都是只有 4K buffer 大小的占用,并不會像 Dubbo 那樣一次性全部讀取為 byte[] 再發(fā)送。

          對于 Server 端也是一樣,Server 端也并不用一次性將所有報文讀取至內(nèi)存中,在解析 Header 中的 Content-Length 后,直接包裝一個 InputStream,在這個 InputStream 內(nèi)部進(jìn)行讀取 Socket Buffer 的數(shù)據(jù)即可,一樣不會有內(nèi)存占用問題。

          那既然 HTTP 協(xié)議“適合”傳輸文件,Spring Cloud 的標(biāo)配 RPC 客戶端 - Feign 在傳輸文件上又會有什么問題呢?

          Feign 適合傳輸文件嗎

          Feign 其實并不能算一套 RPC 框架,它只是一個 Http Client 而已。在使用 Feign 時,Server 可以是任意的 Http Server,比如實現(xiàn) Servlet 的 Tomcat/Jetty/Undertow,或者是其他語言的 Apache Server 等等。

          而一般用 Feign 時,都是在 Spring Cloud 全家桶環(huán)境下,服務(wù)端往往是默認(rèn)的 Tomcat。而 Tomcat 在讀取文件報文(form-data)時,會先將報文暫存至磁盤,然后通過 FileItem 讀取磁盤中的報文內(nèi)容。所以在對于 Server 端來說,不會一次性將完整的報文數(shù)據(jù)讀取至內(nèi)存中,也就不會有內(nèi)存占用過高的問題。

          Feign 中上傳文件有以下幾種方式:

          interface SomeApi {

            // File parameter  
            @RequestLine("POST send_photo")  
            @Headers("Content-Type: multipart/form-data")  
            void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") File photo);

            // byte[] parameter  
            @RequestLine("POST send_photo")  
            @Headers("Content-Type: multipart/form-data")  
            void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") byte[] photo);

            // FormData parameter  
            @RequestLine("POST send_photo")  
            @Headers("Content-Type: multipart/form-data")  
            void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") FormData photo);

            // MultipartFile parameter  
            @RequestLine("POST send_photo")  
            @Headers("Content-Type: multipart/form-data")  
            void sendPhoto(@RequestPart(value = "photo") MultipartFile photo);

            // Group all parameters within a POJO  
            @RequestLine("POST send_photo")  
            @Headers("Content-Type: multipart/form-data")  
            void sendPhoto (MyPojo pojo);

            class MyPojo {

              @FormProperty("is_public")  
              Boolean isPublic;

              File photo;  
            }  
          }

          Feign 中將參數(shù)的編碼/序列化抽象為一個 Encoder,對于 HTTP 協(xié)議的文件上傳也提供了一個 feign-form 模塊,該模塊中提供了一些 FormEncoder??蔁o論哪種 FormEncoder 最后都是通過 Feign 封裝的 Output 對象進(jìn)行輸出,不過這個 Output 對象卻不是那種包裝 Socket InputStream 作為中轉(zhuǎn)發(fā)送,而是直接作為一個數(shù)據(jù)的載體,用一個 ByteArrayOutputStream 來存儲編碼完成的數(shù)據(jù)。

          所以無論怎么定義 FormEncoder,最后數(shù)據(jù)都會寫入到這個 Output 的 ByteArrayOutputStream 中,仍然會將所有數(shù)據(jù)完整的讀取至內(nèi)存中,一樣會有內(nèi)存占用高的問題。

          @RequiredArgsConstructor  
          @FieldDefaults(level = PRIVATE, makeFinal = true)  
          public class Output implements Closeable {

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            //所有的數(shù)據(jù)在“編碼”之后,仍然會寫入到 ByteArrayOutputStream 這個內(nèi)存 OutputStream 中  
            public Output write (byte[] bytes) {  
              outputStream.write(bytes);  
              return this;  
            }

            public Output write (byte[] bytes, int offset, int length) {  
              outputStream.write(bytes, offset, length);  
              return this;  
            }

            public byte[] toByteArray () {  
              return outputStream.toByteArray();  
            }

          }

          但好在 Feign 只是個 HTTP Client,Server 端還是“增量”讀取的,對于 Server 端來說不會有這個內(nèi)存問題。

          總結(jié)

          其實 Dubbo 不光是不適合傳輸文件,大報文場景下都不太合適,Dubbo 的設(shè)計更適合小業(yè)務(wù)報文的傳輸(默認(rèn)報文大小只有8MB)。

          所以如果有文件上傳的場景,盡可能的用客戶端直傳的方式吧,友好又節(jié)省資源!

          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本久久一区 | 精品国产一区二区三区久久久狼,91精品一 | 日韩1234区| 欧美成人电影在线观看 | 三级网站在线播放 |