用 Dubbo 传输文件?被老板一顿揍

沙海 2021年6月18日12:24:28Java评论25字数 4911阅读16分22秒阅读模式
摘要

智能摘要

智能摘要文章源自JAVA秀-https://www.javaxiu.com/33488.html

这样就会导致consumer端需要一次性读取完整的文件内容至内存中,再大的内存也扛不住这样玩。除了内存占用问题之外,Dubbo(这里指Dubbo协议)的单连接模型也不适合文件传输。RPC框架特性的限制,单独看HTTP协议的话,是很适合传输文件的。的数据即可,一样不会有内存占用问题(更详细的文件报文处理方式可以参考我的另一篇文章《Tomcat中是怎么处理文件上传的?其实Dubbo不光是不适合传输文件,大报文场景下都不太合适,Dubbo的设计更适合小业务报文的传输(默认报文大小只有8MB)。文章源自JAVA秀-https://www.javaxiu.com/33488.html

原文约 2599 | 图片 4 | 建议阅读 6 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/33488.html

用 Dubbo 传输文件?被老板一顿揍

戳一戳→ 程序员的成长之路 文章源自JAVA秀-https://www.javaxiu.com/33488.html

用 Dubbo 传输文件?被老板一顿揍文章源自JAVA秀-https://www.javaxiu.com/33488.html

程序员的成长之路文章源自JAVA秀-https://www.javaxiu.com/33488.html

互联网/程序员/技术/资料共享 文章源自JAVA秀-https://www.javaxiu.com/33488.html

关注文章源自JAVA秀-https://www.javaxiu.com/33488.html

阅读本文大概需要 5.5 分钟。文章源自JAVA秀-https://www.javaxiu.com/33488.html

来自:https://juejin.cn/post/6963642641506369566文章源自JAVA秀-https://www.javaxiu.com/33488.html

公司之前有一个 Dubbo 服务,其内部封装了腾讯云的对象存储服务 SDK,目的是统一管理这种三方服务的SDK,其他系统直接调用这个对象存储的 Dubbo 服务。这样可以避免因平台 SDK 出现不兼容的大版本更新,从而导致公司所有系统修改跟着升级的问题。文章源自JAVA秀-https://www.javaxiu.com/33488.html

想法是好的,不过这种做法并不合适,因为 Dubbo 并不适合传输文件。好在这个系统在上线不久就没人用废弃了……文章源自JAVA秀-https://www.javaxiu.com/33488.html

虽然系统废弃了,不过就这个 Dubbo 上传文件的主题还是可以详细分析下,聊聊它到底为什么不适合传文件。文章源自JAVA秀-https://www.javaxiu.com/33488.html

Dubbo 怎么传文件?

难道这样直接传 File 吗?文章源自JAVA秀-https://www.javaxiu.com/33488.html

void sendPhoto(File photo);

当然不行!Dubbo 只是将对象进行序列化然后传输,而 File 对象就算序列化也无法处理文件的数据,所以只能直接发送文件内容:文章源自JAVA秀-https://www.javaxiu.com/33488.html

void sendPhoto(byte[] photo);

但这样就会导致 consumer 端需要一次性读取完整的文件内容至内存中,再大的内存也扛不住这样玩。而且 provider 端在接受数据解析报文时,也需要一次性将 byte[] 读取至内存中,也是一样有内存占用过高问题。文章源自JAVA秀-https://www.javaxiu.com/33488.html

单连接模型问题

除了内存占用问题之外,Dubbo(这里指 Dubbo 协议)的单连接模型也不适合文件传输。文章源自JAVA秀-https://www.javaxiu.com/33488.html

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

所以在单连接下,如果报文过大,会导致 Netty 的写入事件处理阻塞,无法及时的将数据发送至服务端,从而造成请求白白阻塞的问题。文章源自JAVA秀-https://www.javaxiu.com/33488.html

那既然单连接模型有这么大的缺点,为什么 Dubbo 还要采用单连接呢?文章源自JAVA秀-https://www.javaxiu.com/33488.html

因为省资源啊,TCP 连接这种资源可是很宝贵的,如果单连接可以满足绝大多数场景,那么完全不需要为每个请求准备一个连接。文章源自JAVA秀-https://www.javaxiu.com/33488.html

Dubbo 文档中也提到了单连接设计的原因:文章源自JAVA秀-https://www.javaxiu.com/33488.html

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

虽然 Dubbo 协议默认单连接模型,但还是可以设置多连接的:文章源自JAVA秀-https://www.javaxiu.com/33488.html

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

不过多连接下,连接和请求并不是一一对应的,而是一个轮询的机制。如下图所示,当配置了N个连接时,对于每一个 Provider 实例都会维护多个连接,在执行请求时会通过轮询的机制,为每次请求分配不同的连接文章源自JAVA秀-https://www.javaxiu.com/33488.html

用 Dubbo 传输文件?被老板一顿揍文章源自JAVA秀-https://www.javaxiu.com/33488.html

为什么 HTTP 协议“适合”传文件?文章源自JAVA秀-https://www.javaxiu.com/33488.html

其实这么说并不严谨,并不是 HTTP 协议适合传文件,Dubbo 还支持 HTTP 协议呢(虽然是半残品),一样不适合传文件。文章源自JAVA秀-https://www.javaxiu.com/33488.html

Dubbo 这类 RPC 框架为了满足“调用本地方法像调用远程一样”,必须将数据序列化成语言里的对象,但这样一来就导致无法处理 File 这种形式的对象了。文章源自JAVA秀-https://www.javaxiu.com/33488.html

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

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

用 Dubbo 传输文件?被老板一顿揍文章源自JAVA秀-https://www.javaxiu.com/33488.html

对于 Server 端也是一样,Server 端也并不用一次性将所有报文读取至内存中,在解析 Header 中的 Content-Length 后,直接包装一个 InputStream,在这个 InputStream 内部进行读取 Socket Buffer 的数据即可,一样不会有内存占用问题(更详细的文件报文处理方式可以参考我的另一篇文章《Tomcat 中是怎么处理文件上传的?》)。文章源自JAVA秀-https://www.javaxiu.com/33488.html

那既然 HTTP 协议“适合”传输文件,Spring Cloud 的标配 RPC 客户端 - Feign 在传输文件上又会有什么问题呢?文章源自JAVA秀-https://www.javaxiu.com/33488.html

Feign 适合传输文件吗

Feign 其实并不能算一套 RPC 框架,它只是一个 Http Client 而已。在使用 Feign 时,Server 可以是任意的 Http Server,比如实现 Servlet 的 Tomcat/Jetty/Undertow,或者是其他语言的 Apache Server 等等。文章源自JAVA秀-https://www.javaxiu.com/33488.html

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

Feign 中上传文件有以下几种方式:文章源自JAVA秀-https://www.javaxiu.com/33488.html

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 来存储编码完成的数据。文章源自JAVA秀-https://www.javaxiu.com/33488.html

所以无论怎么定义 FormEncoder,最后数据都会写入到这个 Output 的 ByteArrayOutputStream 中,仍然会将所有数据完整的读取至内存中,一样会有内存占用高的问题。文章源自JAVA秀-https://www.javaxiu.com/33488.html

@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 端来说不会有这个内存问题。文章源自JAVA秀-https://www.javaxiu.com/33488.html

总结

其实 Dubbo 不光是不适合传输文件,大报文场景下都不太合适,Dubbo 的设计更适合小业务报文的传输(默认报文大小只有8MB)。文章源自JAVA秀-https://www.javaxiu.com/33488.html

所以如果有文件上传的场景,尽可能的用客户端直传的方式吧,友好又节省资源!文章源自JAVA秀-https://www.javaxiu.com/33488.html

<END>文章源自JAVA秀-https://www.javaxiu.com/33488.html

推荐阅读:文章源自JAVA秀-https://www.javaxiu.com/33488.html

黑客是如何攻破一个网站的?文章源自JAVA秀-https://www.javaxiu.com/33488.html

在Spring Boot中实现通用Auth认证的几种方式文章源自JAVA秀-https://www.javaxiu.com/33488.html

最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

文章源自JAVA秀-https://www.javaxiu.com/33488.html

获取方式:点个「在看」,点击上方小卡片,进入公众号后回复「面试题」领取,更多内容陆续奉上。文章源自JAVA秀-https://www.javaxiu.com/33488.html

朕已阅 用 Dubbo 传输文件?被老板一顿揍文章源自JAVA秀-https://www.javaxiu.com/33488.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定