背景

之前公司有一个 Dubbo 服务,内部封装了腾讯云的对象存储服务 SDK,是为了统一管理这种三方服务的SDK,其他系统直接调用这个对象存储的 Dubbo 服务。用来避免因平台 SDK 出现不兼容的大版本更新,导致公司所有系统修改跟着升级的问题。

然而因为 Dubbo 并不适合传输大包,所以虽然想法不错,但这种做法还是并不合适,于是这个系统在上线不久就遭废弃没人用了……

不过系统虽然废弃了,但是我们可以顺着 Dubbo 上传文件的主题来详细分析下,说说看它究竟是因为什么不适合传文件。

Dubbo 怎么传文件?

难道直接这样传 File 吗?

void sendPhoto(File photo);

自然是不可以的!Dubbo 只是将对象进行序列化然后传输,可 File 对象就算序列化也无法处理文件的数据,所以只能直接发送文件内容:

void sendPhoto(byte[] photo);

但这样会导致 consumer 端需要一次性读取完整的文件内容至内存中,这样玩的话,再大的内存也都遭不住。

并且 provider 端在接受数据解析报文时, byte[] 也需要一次性读取至内存中,内存占用过高的问题同样存在。

单连接模型问题

除了内存占用问题之外,Dubbo(这里指 Dubbo 协议)的单连接模型也不适合文件传输。

Dubbo 协议默认是单连接的模型,也就是一个 provider 的所有请求都是用一个 TCP 连接。默认使用 Netty 来进行传输,而 Netty 中为了保证 Channel 线程安全,会将写入事件进行排队处理。那么在单连接下,多个请求都会使用同一个连接,也就是同一个 Channel 进行写入数据;当多个请求同时写入时,如果某个报文过大,会导致 Channel 一直在发送这个报文,其他请求的报文写入事件会进行排队,迟迟无法发送,连数据都没有发送过去,那么其他的 consumer 也自然会处于阻塞等待响应的状态中,一直无法返回了。

所以在单连接下,如果报文过大,将会导致 Netty 地写入事件处理阻塞,数据将无法及时发送至服务端,从而造成请求白白阻塞的问题。

那有的朋友可能会问,单连接模型缺点都这么大了, Dubbo 为什么还要采用单连接呢?

很简单,因为省资源,TCP 连接这样的资源可是很宝贵的,如果单连接可以满足绝大多数场景,那么也就完全没必要为每个请求准备一个连接。

在Dubbo 文档中也提到了单连接设计的原因:

因为服务的现状大都是服务提供者少,通常只有几台机器,而服务的消费者多,可能整个网站都在访问该服务,比如 Morgan 的提供者只有 6 台提供者,却有上百台消费者,每天有 1.5 亿次调用,如果采用常规的 hessian 服务,服务提供者很容易就被压垮,通过单一连接,保证单一消费者不会压死提供者,长连接,减少连接握手验证等,并使用异步 IO,复用线程池,防止 C10K 问题。

虽然 Dubbo 协议默认单连接模型,但还是可以设置多连接的:

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

不过多连接下,连接和请求是一个轮询的机制,并不是一一对应的。

如下图,当配置了数个连接时,对于每一个 Provider 实例都会维护多个连接,在执行请求时会通过轮询的机制,为每次请求分配不同的连接

为什么 HTTP 协议“适合”传文件?

这么说其实不严谨,并不是 HTTP 协议适合传文件,Dubbo 还支持 HTTP 协议呢(虽然是半残品),一样不适合传文件。

Dubbo 这类 RPC 框架为了满足“调用本地方法像调用远程一样”,必须将数据序列化成语言里的对象,但这样一来就导致无法处理 File 这种形式的对象了。

如果跳出 Dubbo 这种 RPC 框架特性的限制,单独看 HTTP 协议的话,是很适合传输文件的。因为对于 Client 来说,只需要将报文发送至 Server,比如要传输的文件在本地的话,那我完全可以每次只读取文件的一个 Buffer 大小,然后将这个 Buffer 的数据使用 Socket 发送即可;在这种方式下,同时存在于内存中的数据,只会有一个 Buffer 大小,不会有 Dubbo 那样将全部数据读取至内存的问题。

如下图所示,Client 每次只从1GB 文件中读取 4K 大小的 Buffer 数据,然后用 Socket 发送,直至将文件完全读取并发送成功。那么这种方式下对于单次传输来说,内存始终都是只有 4K buffer 大小的占用,并不会像 Dubbo 那样一次性全部读取为 byte[] 再发送。

对于 Server 端也是一样,Server 端也并不用一次性将所有报文读取至内存中,在解析 Header 中的 Content-Length 后,直接包装一个 InputStream,在这个 InputStream 内部进行读取 Socket Buffer 的数据即可,一样不会有内存占用问题

那既然 HTTP 协议“适合”传输文件,Spring Cloud 的标配 RPC 客户端 - Feign 在传输文件上又会有什么问题呢?

Feign 适合传输文件吗

Feign 其实并不能算一套 RPC 框架,它只是一个 Http Client 而已。在使用 Feign 时,Server 可以是任意的 Http Server,比如实现 Servlet 的 Tomcat/Jetty/Undertow,或者是其他语言的 Apache Server 等等。

而一般都是在 Spring Cloud 全家桶环境下用 Feign,服务端往往是默认的 Tomcat。而 Tomcat 在读取文件报文(form-data)时,会先将报文暂存至磁盘,然后通过 FileItem 读取磁盘中的报文内容。所以在对于 Server 端来说,不会一次性将完整的报文数据读取至内存中,也就不会有内存占用过高的问题。

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 中将参数的编码/序列化抽象为一个 Encoder,对于 HTTP 协议的文件上传也提供了一个 feign-form 模块,该模块中提供了一些 FormEncoder。可无论哪种 FormEncoder 最后都是通过 Feign 封装的 Output 对象进行输出,不过这个 Output 对象却不是那种包装 Socket InputStream 作为中转发送,而是直接作为一个数据的载体,用一个 ByteArrayOutputStream 来存储编码完成的数据。

所以无论怎么定义 FormEncoder,最后数据都会写入到这个 Output 的 ByteArrayOutputStream 中,仍然会将所有数据完整的读取至内存中,一样会有内存占用高的问题。

@RequiredArgsConstructor
@FieldDefaults(level = PRIVATE, makeFinal = true)
public class Output implements Closeable {ByteArrayOutputStream outputStream = new ByteArrayOutputStream();//所有的数据在“编码”之后,仍然会写入到 ByteArrayOutputStream 这个内存 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 端来说不会有这个内存问题。

总结

其实 Dubbo 不光是不适合传输文件,大报文场景下都不太合适,Dubbo 的设计更适合小业务报文的传输(默认报文大小只有8MB)。

所以如果有文件上传的场景,尽可能地用客户端直传的方式吧,节省资源又友好!

Dubbo支持的协议

dubbo(默认):单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议 TCP,异步,Hessian 序列化。

http:基于 Http 表单提交的远程调用协议,使用Spring的HttpInvoke 实现。多个短连接,传输协议 HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器 JS 调用。

hessian:集成Hessian 服务,基于HTTP通讯,采用Servlet暴露服务,Dubbo 内嵌 Jetty 作为服务器时默认实现,提供与Hession服务互操作。多个短连接,同步 HTTP 传输,Hessian 序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件。

memcache:基于memcached实现的RPC协议。传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。

rmi:采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口,使用java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议 TCP。多个短连接,TCP 协议传输,同步传输,适用常规的远程服务调用和 rmi 互操作。在依赖低版本的 Common-Collections包,java 序列化存在安全漏洞。

webservice:基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,基于 HTTP 传输,同步传输,适用系统集成和跨语言调用。

redis:基于redis实现的RPC协议。

为什么都说Dubbo不适合传输大文件?Dubbo支持的协议相关推荐

  1. 分享:一款快速传输大文件、支持英文版的工具

    最近朋友小A向我求助:因为工作需要常常需要往国外发送数百兆甚至接近1G的超大文件.试了好几种方式都不满意,国内的可以发送超大附件的网站(如QQ邮箱,各种网盘)不支持英文界面,外国朋友看不懂.而WeTr ...

  2. 局域网只看到少数电脑_利用局域网高速传输大文件的两种方法

    点击蓝字,关注我们 说到传输文件,我们可能很容易想到使用微信或者QQ发送文件,如果没有连接网络的话,可以考虑使用U盘或数据线,当然也可以使用第三方的共享文件,比如说茄子快传.以上方法的缺点自然是很明显 ...

  3. JAVA实现服务器间拷贝文件,寻找在Java服务器之间传输大文件的好方法

    在这个项目中有一个主要的数据库服务器,其他安装在不同地方的服务器维护着自己的本地数据库.我们必须允许每个系统将其本地数据库更新为主要数据库上的任何版本.所有的服务器都运行Java环境.寻找在Java服 ...

  4. 四种企业传输大文件的方法

    如今,发送数GB大小的文件变得越来越普遍.例如,在我们的日常办公中,接收超过100MB的日志数据的zip文件是正常的.虽然看起来这只是一个高度专业化的案例,但在科技行业中却很常见. 在影视行业中,单个 ...

  5. Android使用usb线传输大文件笔记

    使用usb线传输大文件 参考资料: 使用USB数据线连接PC端和Android端进行数据的交互 安卓设备通过USB接口实现与pc端的简单数据通信 Socket TCP/IP协议数据传输过程中的粘包和分 ...

  6. 浅析C#UDP传输大文件

    1.前言 众所周知,UDP通信是允许丢包的,这个通信方式本身就是"不太靠谱的",针对的是即便数据丢了几包也无所谓的情景,如果你非要用这个传输大文件(如一个视频),我只能说你和我一样 ...

  7. NodeJS使用socket传输大文件

    NodeJS的net模块为我们提供了socket相关API,介于此我们可以进行相关的网络编程.JavaScript 语言自身只有字符串数据类型,没有二进制数据类型,需要通过Buffer对象来处理.在这 ...

  8. 如何快速传输大文件,介绍大文件快速方法

    现在,企业比以往任何时候都面临着一个重大挑战:需要一个快速共享文件的解决方案.但是,并非所有快速文件传输解决方案都以相同的速度传输文件.文件大小.端点位置.路径.设备.防火墙.网络系统和加密需求都会限 ...

  9. 几种快速传输大文件的方式

    随着科学技术的发展,图片或视频等文件的质量越来越高,同时也意味着,文件也变得越来越大,那么快速传输大文件需求越来越明显. 在日常生活中,经常遇到需要与他人共享文件或传输到另一台电脑上的情况.传统的大文 ...

最新文章

  1. hdu1978 简单记忆化搜索
  2. 笨办法学Python——学习笔记1
  3. 加载vue文件步骤_vue中.vue文件解析步骤详解
  4. poj 1077 Eight(A*)
  5. 蚂蚁金服开源自动化测试框架 SOFAACTS
  6. Condition.signal
  7. 百度地图POI数据爬取,突破百度地图API爬取数目“400条“的限制11。
  8. Python中的计数(词频)
  9. 避免“被忽略” 如何向上司“喜传捷报”?
  10. 传说中人生必去的50个地方
  11. quartus+modelsim仿真教程
  12. kindle电子书使用calibre进行解除DRM导出本地
  13. 修改.class文件内容
  14. Sqlmap中文手册
  15. sessionStorage 、localStorage 和 cookie
  16. opencv------绘制文本
  17. 自媒体短视频怎么玩?0粉丝也可以变现,5种变现模式
  18. GlobalSign的旗下的SSL证书产品
  19. 如何将多行和多列转换为行和行Excel
  20. PYTHON文件操作(读/写文件)

热门文章

  1. Pandas 数据分析-第八章(排序sort_index())
  2. js 操作字符串,
  3. 华为虚拟机服务器怎么使用教程,虚拟机装服务器教程
  4. emoji表情符号编码大全
  5. 女人长的漂亮不如活的漂亮(Top60)
  6. Java程序员的职业生涯规划建议
  7. 函数中的arguments
  8. 如何做好项目管理任务分配
  9. 在黑暗中哭泣的众筹,黎明的曙光还未到来
  10. NLU(Natural Language Understanding)太难了