<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 傳輸文件?被老板一頓揍

          共 5956字,需瀏覽 12分鐘

           ·

          2021-06-18 02:43

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享 
          關(guān)注


          閱讀本文大概需要 5.5 分鐘。

          來自:https://juejin.cn/post/6963642641506369566

          公司之前有一個 Dubbo 服務(wù),其內(nèi)部封裝了騰訊云的對象存儲服務(wù) SDK,目的是統(tǒng)一管理這種三方服務(wù)的SDK,其他系統(tǒng)直接調(diào)用這個對象存儲的 Dubbo 服務(wù)。這樣可以避免因平臺 SDK 出現(xiàn)不兼容的大版本更新,從而導(dǎo)致公司所有系統(tǒng)修改跟著升級的問題。
          想法是好的,不過這種做法并不合適,因?yàn)?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)行排隊(duì)處理。那么在單連接下,多個請求都會使用同一個連接,也就是同一個 Channel 進(jìn)行寫入數(shù)據(jù);當(dāng)多個請求同時寫入時,如果某個報文過大,會導(dǎo)致 Channel 一直在發(fā)送這個報文,其他請求的報文寫入事件會進(jìn)行排隊(duì),遲遲無法發(fā)送,數(shù)據(jù)都沒有發(fā)送過去,那么其他的 consumer 也自然會處于阻塞等待響應(yīng)的狀態(tài)中,一直無法返回了。
          所以在單連接下,如果報文過大,會導(dǎo)致 Netty 的寫入事件處理阻塞,無法及時的將數(shù)據(jù)發(fā)送至服務(wù)端,從而造成請求白白阻塞的問題。
          那既然單連接模型有這么大的缺點(diǎn),為什么 Dubbo 還要采用單連接呢?
          因?yàn)槭≠Y源啊,TCP 連接這種資源可是很寶貴的,如果單連接可以滿足絕大多數(shù)場景,那么完全不需要為每個請求準(zhǔn)備一個連接。
          Dubbo 文檔中也提到了單連接設(shè)計(jì)的原因:
          因?yàn)榉?wù)的現(xiàn)狀大都是服務(wù)提供者少,通常只有幾臺機(jī)器,而服務(wù)的消費(fèi)者多,可能整個網(wǎng)站都在訪問該服務(wù),比如 Morgan 的提供者只有 6 臺提供者,卻有上百臺消費(fèi)者,每天有 1.5 億次調(diào)用,如果采用常規(guī)的 hessian 服務(wù),服務(wù)提供者很容易就被壓跨,通過單一連接,保證單一消費(fèi)者不會壓死提供者,長連接,減少連接握手驗(yàn)證等,并使用異步 IO,復(fù)用線程池,防止 C10K 問題。
          雖然 Dubbo 協(xié)議默認(rèn)單連接模型,但還是可以設(shè)置多連接的:

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


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

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

          其實(shí)這么說并不嚴(yán)謹(jǐn),并不是 HTTP 協(xié)議適合傳文件,Dubbo 還支持 HTTP 協(xié)議呢(雖然是半殘品),一樣不適合傳文件。
          Dubbo 這類 RPC 框架為了滿足“調(diào)用本地方法像調(diào)用遠(yuǎn)程一樣”,必須將數(shù)據(jù)序列化成語言里的對象,但這樣一來就導(dǎo)致無法處理 File 這種形式的對象了。
          如果跳出 Dubbo 這種 RPC 框架特性的限制,單獨(dú)看 HTTP 協(xié)議的話,是很適合傳輸文件的。因?yàn)閷τ?Client 來說,只需要將報文發(fā)送至 Server,比如要傳輸?shù)奈募诒镜氐脑?,那我完全可以每次只讀取文件的一個 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)存占用問題(更詳細(xì)的文件報文處理方式可以參考我的另一篇文章《Tomcat 中是怎么處理文件上傳的?》)。
          那既然 HTTP 協(xié)議“適合”傳輸文件,Spring Cloud 的標(biāo)配 RPC 客戶端 - Feign 在傳輸文件上又會有什么問題呢?

          Feign 適合傳輸文件嗎

          Feign 其實(shí)并不能算一套 RPC 框架,它只是一個 Http Client 而已。在使用 Feign 時,Server 可以是任意的 Http Server,比如實(shí)現(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é)

          其實(shí) Dubbo 不光是不適合傳輸文件,大報文場景下都不太合適,Dubbo 的設(shè)計(jì)更適合小業(yè)務(wù)報文的傳輸(默認(rèn)報文大小只有8MB)。
          所以如果有文件上傳的場景,盡可能的用客戶端直傳的方式吧,友好又節(jié)省資源!
          <END>

          推薦閱讀:

          黑客是如何攻破一個網(wǎng)站的?

          在Spring Boot中實(shí)現(xiàn)通用Auth認(rèn)證的幾種方式

          最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)個「在看」,點(diǎn)擊上方小卡片,進(jìn)入公眾號后回復(fù)「面試題」領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          朕已閱 

          瀏覽 38
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(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>
                  国语对白av | 日韩黄色在线观看视频 | 好吊爽一区二区三区免费 | 音彰先锋成人无码视频 | 国产性在线电影 |