以下文章来源方志朋的博客,回复”666“获面试宝典

公司之前有一个 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"/>

不过多连接下,连接和请求并不是一一对应的,而是一个轮询的机制。如下图所示,当配置了N个连接时,对于每一个 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 的数据即可,一样不会有内存占用问题(更详细的文件报文处理方式可以参考我的另一篇文章《Tomcat 中是怎么处理文件上传的?》)。

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

Feign 适合传输文件吗

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

而一般用 Feign 时,都是在 Spring Cloud 全家桶环境下,服务端往往是默认的 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)。

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

来源 | https://juejin.cn/post/6963642641506369566

在这里,我为大家准备了一份2021年最新最全的《史上最简单的java面试题》,这套电子书涵盖了诸多java技术栈的面试知识题,是作者面试BAT大厂的前的总结,作者顺利拿下AT的offer,相信可以帮助大家在最短的时间内复习Java后端的大多数面试题,从而拿到自己心仪的offer。截了张图,大家可以仔细查看左边的菜单栏,覆盖的知识面真的很广,而且质量都很不错。资料获取方法扫描下方二维码
后台回复关键词:BAT
明天见(。・ω・。)

用 Dubbo 传输文件?被老板一顿揍相关推荐

  1. 同事使用 Dubbo 传输文件,被点名批评!

    你知道的越多,不知道的就越多,业余的像一棵小草! 成功路上并不拥挤,因为坚持的人不多. 编辑:业余草 juejin.cn/post/6963642641506369566 推荐:https://www ...

  2. 基于python的文件加密传输系统 毕业论文_20183411 李丞灏 2020-2021 《python程序设计》 实验三 加密传输文件 实验报告...

    20183411 李丞灏 2020-2021 <python程序设计> 实验三 加密传输文件 实验报告 课程:<Python程序设计> 班级: 1834 姓名: 李丞灏 学号: ...

  3. 2022年5月8日 解决手机连接电脑无法选择“传输文件”

    起因 昨天,2022年5月7日,下午手机空间提示不足,本来准备今天清理,但是昨天晚上突然无法立刻唤醒屏幕,则知道肯定是空间不足导致的. 过了一段时间,屏幕唤醒了,但是桌面延迟了很长时间才显示,而且时不 ...

  4. Linux系统管理必备知识之利用ssh传输文件

    在使用SSH时候,有时我们需要传输文件,这就需要用到命令scp. 从服务器上下载文件 scp username@servername:/path/filename /local_dir(本地目录) e ...

  5. tftp:timeout问题解决 - 从Windows传输文件到开发板

    通过串口工具ping一下主机,确定是否能ping通,确保通信无问题,如下 ping通后,确保PC tftp软件打开, 检查防火墙是否关闭,专用网络是家庭网络,允许同网段下的数据传输,无需关闭,因此只需 ...

  6. 使用nc传输文件和目录【转】

    方法1,传输文件演示(先启动接收命令) 使用nc传输文件还是比较方便的,因为不用scp和rsync那种输入密码的操作了 把A机器上的一个rpm文件发送到B机器上 需注意操作次序,receiver先侦听 ...

  7. linux cp sync,通过SSH使用Rsync传输文件,复制和同步文件及目录

    在本文中,我们将解释如何通过SSH使用rsync复制文件.当涉及在网络上的系统之间传输文件时,Linux和Unix用户可以使用许多工具,最流行的数据传输协议是SSH和FTP,虽然FTP很受欢迎,但总是 ...

  8. 使用C++的Socket实现从客户端到服务端,服务端到客户端传输文件

    使用: (1)首先运行服务端,待服务端运行起来: (2)最后运行客户端,输入要传输文件到哪个目标机器的IP地址: (3)输入传输文件的路径及文件(完成的路径),其中包含文件的类型,也就是后缀需要包含( ...

  9. 使用C++实现Socket编程传输文件

    使用: (1)首先运行服务端,待服务端运行起来: (2)最后运行客户端,输入要传输文件到哪个目标机器的IP地址: (3)输入传输文件的路径及文件(完成的路径),其中包含文件的类型,也就是后缀需要包含( ...

最新文章

  1. nicstat命令安装与分析
  2. 实施ERP系统要先进行思考
  3. 巴特沃斯带通滤波器matlab程序_带通带阻滤波器频率计算方法如何算
  4. docker仓库harbor搭建
  5. IDA Pro的patch插件 KeyPatch
  6. C++ 解决enum redeclaration的冲突
  7. JAVA开发需求分析套路_JAVA并发工具常用设计套路示例代码
  8. python抽取指定url页面的title_Python使用scrapy爬虫,爬取今日头条首页推荐新闻
  9. 中兴手机官宣吴京代言 以科技为勇敢者助力
  10. 防止自建控件与页面间重复引入客户端js脚本的方法
  11. EF-DbUpdateException--实体类和数据库列不对应的解决方案
  12. java中如何表示圆周率
  13. Intel600P三星sm951pm961nvme等固态硬盘安装WIN7教程
  14. PostgreSQL update多张表关联查询更新
  15. 物理机通过Xshell连接不上虚拟机的解决方案
  16. 从个人邮箱登录页面进入后,邮箱如何撤回邮件?
  17. 考题篇(6.2) 02 ❀ FortiGate ❀ Fortinet 网络安全专家 NSE 4
  18. 深入浅出--何为多线程(引用自大神Kyrie lrving)
  19. 小米重新上锁[BL]
  20. Flash Builder 4.6.1的安装与破解

热门文章

  1. libcudart.so.6.5 cannot open shared object file: no such file or directory
  2. Hadoop虚拟机的jdk版本和本地eclipse的版本不一致怎么办
  3. Laravel 上使用 phpexcel的两种方式
  4. 安装mayavi和VTK库的血泪史
  5. 使用SVG中的Symbol元素制作Icon
  6. 【BZOJ-3712】Fiolki LCA + 倍增 (idea题)
  7. 程序员眼中的英文单词是这样的
  8. cucumber 文件目录结构和执行顺序
  9. 【青少年编程】【四级】词语接龙
  10. 如何利用遗传算法进行自变量降维