当前位置: 首页 > 后端技术 > Java

我用 Dubbo 传输文件,差点被开除

时间:2023-04-01 21:44:13 Java

我用Dubbo传输文件,差点被炒了。公司之前有一个Dubbo服务,封装了腾讯云的对象存储服务SDK。目的是统一管理这个第三方服务的SDK。其他系统直接调用这个对象存储服务。服务。避免因平台SDK大版本更新不兼容导致公司所有系统修改升级的问题。想法是好的,但是这种方式不适合,因为Dubbo不适合传输文件。好在这个系统上线没多久就被废弃了……虽然被系统废弃了,但是我们还是可以详细分析一下Dubbo上传文件的话题,说说为什么它不适合上传文件。Dubbo是如何传输文件的?这样直接传File可以吗?voidsendPhoto(文件照片);当然不是!Dubbo只是将对象序列化然后传输,而File对象即使序列化也无法处理文件的数据,所以只能直接发送文件内容:voidsendPhoto(byte[]photo);但是这样会导致消费端需要一次性把完整的文件内容读入内存,内存再大也应付不了这样播放。而且provider端在收到数据分析报文时,也需要一次性将byte[]读入内存,同样存在内存占用高的问题。后台管理系统+基于SpringBoot+MyBatisPlus+Vue&Element的用户小程序,支持RBAC动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。项目地址:https://github.com/YunaiV/ruo...单连接模型问题除了内存占用问题外,Dubbo(这里指的是Dubbo协议)的单连接模型不适合文件传输.Dubbo协议默认是单连接模型,即一个provider的所有请求都使用一个TCP连接。默认情况下使用Netty进行传输,Netty中为了保证Channel线程安全,写事件会排队处理。那么在单连接下,多个请求会使用同一个连接,即同一个Channel写入数据;当多个请求同时写入时,如果一条消息过大,Channel会一直发送这条消息Text,其他请求的消息写入事件会排队,长时间无法发送,数据一直没有发送了,那么其他消费者自然会处于阻塞等待响应的状态,一直无法返回。所以在单连接下,如果数据包过大,会导致Netty的写事件处理被阻塞,无法及时将数据发送到服务器,造成请求白阻塞的问题。既然单连接模型有这么大的劣势,为什么Dubbo还要用单连接呢?因为节省了资源,所以TCP连接等资源非常宝贵。如果单个连接就可以满足大部分场景,那么就没有必要为每个请求都准备一个连接。Dubbo文档中也提到了单连接设计的原因:因为服务目前的状态是服务提供者很少,通常只有几台机器,而服务消费者很多,可能整个网站都在访问服务,比如Morgan的provider,provider只有6家,consumers却有几百个,每天调用1.5亿次。如果使用常规的Hessian服务,服务提供者很容易不堪重负。通过单连接,保证单个消费者不被Provider压垮,长连接,减少连接握手验证等,并使用异步IO,多路复用线程池,防止C10K问题。虽然Dubbo协议默认是单连接模型,但是仍然可以设置多连接:但是多连接下,连接和请求都是不是一一对应的,是轮询机制。如下图所示,当配置N个连接时,会为每个Provider实例维护多个连接,并在请求执行时通过轮询机制为每个请求分配不同的连接。基于微服务思想,在B2C电商场景下搭建项目实战。核心技术栈为SpringBoot+Dubbo。未来将重组SpringCloudAlibaba。项目地址:https://github.com/YunaiV/one...为什么HTTP协议“适合”文件传输?其实这么说并不严谨,并不是说HTTP协议适合做文件传输,Dubbo也支持HTTP协议(虽然是半成品),同样不适合做文件传输。Dubbo等RPC框架必须将数据序列化为语言对象,才能满足“像调用远程方法一样调用本地方法”的需求,但是这样就无法处理File形式的对象。如果跳出Dubbo这个RPC框架特性的限制,单看HTTP协议,非常适合传输文件。因为对于客户端来说,它只需要将消息发送给服务器即可。比如要传输的文件是本地的,那么我一次只能读取文件的一个Buffer大小,然后用Socket发送这个buffer的数据。;这样,内存中同时存在的数据只会有一个Buffer大小,不会像Dubbo一样将数据全部读入内存的问题。如下图,Client每次只从1GB的文件中读取4KBuffer数据,然后通过Socket发送,直到文件完全读取并发送成功。所以这样一来,对于单次传输来说,内存总是只占用4Kbuffer大小,不会像Dubbo那样一次性读取所有的byte[]然后发送。服务器端也是如此。服务器端不需要一次将所有消息读入内存。解析完Header中的Content-Length后,直接包裹了一个InputStream,读取InputStream内部SocketBuffer的数据。是的,不会有内存占用问题(更详细的文件消息处理方法可以参考我的另一篇文章《Tomcat 中是怎么处理文件上传的?》)。那么既然HTTP协议“适合”传输文件,那SpringCloud的标准RPC客户端-Feign在传输文件上有什么问题呢?Feign是否适合传输文件Feign其实不是一个RPC框架,它只是一个HttpClient。使用Feign时,Server可以是任意的HttpServer,比如实现Servlet的Tomcat/Jetty/Undertow,或者其他语言的ApacheServer等。一般使用Feign时,都是在SpringCloud全家桶环境下,而服务器通常是默认的Tomcat。当Tomcat读取一个文件消息(form-data)时,会先将消息暂时保存到磁盘中,然后通过FileItem从磁盘中读取消息内容。所以对于服务端来说,不会一次性将完整的消息数据读入内存,不会出现内存占用过大的问题。Feign中上传文件有以下几种方式:interfaceSomeApi{//文件参数@RequestLine("POST/send_photo")@Headers("Content-Type:multipart/form-data")voidsendPhoto(@Param("is_public")BooleanisPublic,@Param("photo")文件照片);//byte[]参数@RequestLine("POST/send_photo")@Headers("Content-Type:multipart/form-data")voidsendPhoto(@Param("is_public")BooleanisPublic,@Param("photo")字节[]照片);//FormData参数@RequestLine("POST/send_photo")@Headers("Content-Type:multipart/form-data")voidsendPhoto(@Param("is_public")BooleanisPublic,@Param("photo")FormDataphoto);//MultipartFile参数@RequestLine("POST/send_photo")@Headers("Content-Type:multipart/form-data")voidsendPhoto(@RequestPart(value="photo")MultipartFilephoto);//将所有参数分组nPOJO@RequestLine("POST/send_photo")@Headers("Content-Type:multipart/form-data")voidsendPhoto(MyPojopojo);类MyPojo{@FormProperty("is_public")BooleanisPublic;档案照片;}}Feign将参数的编码/序列化抽象成一个Encoder,同时也提供了HTTP协议文件上传的feign-form模块。这个模块提供了一些FormEncoder,但是不管是哪种FormEncoder,都是Feign封装的Output。要输出的对象,但是这个Output对象并不是那种把SocketInputStream包装成中转传输的,而是直接作为数据载体,使用一个ByteArrayOutputStream来存储编码后的数据。所以不管FormEncoder怎么定义,最终的数据都会写入到这个Output的ByteArrayOutputStream中,所有的数据还是会被完整的读入内存,同样会存在内存占用高的问题。@RequiredArgsConstructor@FieldDefaults(level=PRIVATE,makeFinal=true)publicclassOutputimplementsCloseable{ByteArrayOutputStreamoutputStream=newByteArrayOutputStream();//所有数据在“编码”后仍然会写入到ByteArrayOutputStream的内存OutputStreamputwrite(byte[]bytes){outputStream.write(bytes);归还这个;}publicOutputwrite(byte[]bytes,intoffset,intlength){outputStream.write(bytes,offset,length);归还这个;publicbyte[]toByteArray(){返回outputStream.toByteArray();}}不过好在Feign只是一个HTTPClient,服务端还是“增量”读取的,所以服务端不会有这样的内存问题。综上所述,Dubbo不仅不适合做文件传输,也不适合大消息场景。Dubbo的设计更适合小型企业消息的传递(默认消息大小仅为8MB)。所以如果有文件上传的场景,尽量使用客户端直接上传的方式,既友好又节省资源!