本篇介绍如何设计一个文件上传/传输功能的dubbo接口及遇到的问题;

背景

1. 在B端业务下,上传文件服务未做好收口,由于历史原因,代码中有各种调用文件服务Client的场景,并且针对不同的业务场景或文件类型,写了不同的方法逻辑;

2. 文件上传,入口一般是Controller参数里面的MultipartFile,涉及流的读取和传输,需要做好流的关闭,否则容易导致内存泄漏OOM;

3. 现存的各种文件上传的Service,使用了不同系统的文件上传client(client初始化时需要跟系统的appkey和appSecret绑定),可能是为了拿到各系统CDN域名的URL;不同的appkey和appSecret难以区分和维护;

准备方案

使用内部core服务的dubbo接口,将文件上传的服务给收口,兼容不同文件类型、不同场景的文件上传,对内部其他服务屏蔽上传实现的细节;

解决过程

先说结论——dubbo不适合用来做上传接口;

更好的方案——使用client方式打成内部二方包,在各个应用中配置原上传服务的client和当前二方包收口的上传服务的bean,在代码中调用即可;

在实现过程中尝试过做一个dubbo接口来实现上传服务,过程和问题记录如下;

1. 接口定义

因为接口参数需要包含传输文件,任意文件都是二进制流,因此使用byte[ ]来传输文件数据;

public interface UploadFileFacade {

    /**
     * 上传文件 返回url 注意:上传接口存在cdn缓存 短时间内重复调用返回的文件url 下载的文件可能相同
     * 注意:dubbo的单次传输数据包大小默认8M 可通过修改dubbo.protocol.payload参数调整但此请求满足的数据包大小
     *
     * @param reqDTO
     * @return
     */
    FacadeDefaultDTO<UploadRespDTO> upload(@Valid UploadReqDTO reqDTO);

}

入参除了文件字节流,其他的参数有文件名、文件类型;

@Data
public class UploadReqDTO implements Serializable {

    /**
     * fileBytes
     *
     * @see File2BytesTemplate
     */
    @NotNull
    private byte[] bytes;

    /**
     * fileName without file extend name
     *
     * @see UploadReqDTO#fileType
     */
    @NotNull
    private String fileName;

    /**
     * fileName with file extend name
     *
     * @see com.vivo.internet.cp.resource.center.facade.inner.enums.FileTypeEnum
     */
    @NotNull
    private FileTypeEnum fileType;

}

返参目前仅包含文件上传后的CDN下载地址,后序可以扩展字段,如文件的有效期、大小等;

@Data
public class UploadRespDTO implements Serializable {

    /**
     * downloadUrl using CDN domain
     */
    private String downloadUrl;

}

2. 遇到的的问题

(1)性能分析

首先dubbo不适合传输大文件数据;

dubbo协议采用单一长连接,举例:如果每次dubbo请求的数据包大小为500KByte,假设:

(1) 网络为千兆网卡(1024Mbit/8=128MByte)
(2) 每条连接最大7MByte(不同的环境可能不一样,供参考)

则:

1. 单个服务提供者的TPS(每秒处理事务数)最大为:128MByte / 500KByte = 262。
2. 单个消费者,调用单个服务提供者的TPS(每秒处理事务数)最大为:7MByte / 500KByte = 14

如果能接受,可以考虑使用,否则网络将成为瓶颈;

(2)dubbo数据包大小限制

dubbo每次传输数据包大小默认是8Mb,也就是8388608bit;(8 * 1024 * 1024 = 8388608)

单次dubbo请求的数据包超过这个大小,会报错:

cause: Data length too large: 36304917, max payload: 8388608, channel: NettyChannel

注意这里说的"数据包大小"针对的是整体参数大小,而不是单个请求参数的大小;

这个"数据包大小"可以通过修改dubbo的配置参数修改,有以下方式;

方式1:通过修改全局配置,这个配置在protocol下;可以在 application.properties 中添加以下配置来修改

#dubbo protocol相关配置

#可不配置;如果未设置,默认端口为-1,随机端口
dubbo.protocol.port=13300

#可不配置;如果未设置,默认为dubbo
=dubbo

#dubbo服务限制大数据传输,最大限制为8M,这里设置为50Mb
dubbo.protocol.payload=52428800

吐槽一下,部分搜到的帖子里说的配置是 dubbo.protocol.dubbo.payload=11557050,试了是不行,我的dubbo是2.7.12,难道是老版本这样配置?其实最科学的办法还是去官网dubbo:protocol | Apache Dubbo找答案,拼接出来的参数也就是 dubbo.protocol.payload;

方式2:修改单个指定provider的protocol(这个方法还没有验证);在下注册provider的地方加上dubbo:protocol属性

<dubbo:service interface="com.AA.facade.SelfOperateFacade" ref="selfOperateFacade" version="1.0.0" timeout="3000">
        <dubbo:parameter key="payload" value="52428800"/>
    </dubbo:service>

(3)修改dubbo配置后启动遇到的"Caused by: java.lang.IllegalStateException: Invalid name="org.apache.dubbo.config.ProtocolConfig#0" contains illegal character, only digit, letter, '-', '_' or '.' is legal."

dubbo 传递 租户 dubbo如何传输文件_文件上传

解决方案:application.properties 中配置,重启服务,问题解决;

=dubbo

(4)调试时IDEA弹窗报错"There is not enough memory to perform the requested operation"

dubbo 传递 租户 dubbo如何传输文件_文件上传_02

参考了这篇文章IDEA报错There is not enough memory to perform the requested operation,直接在弹窗修改是不行的;于是进入"Help->Edit Custom VM Options"修改,重启IDEA,问题解决;

dubbo 传递 租户 dubbo如何传输文件_dubbo_03

(5)调试时dubbo报错"Invoke remote method timeout"

org.apache.dubbo.rpc.RpcException: Failfast invoke providers XX 
RandomLoadBalance select from all providers (count: 1) for service com.AA.facade.inner.UploadFileFacade method upload on consumer XX use dubbo version 2.7.8.13-VIVO, but no luck to perform the invocation. 
Last error is: Invoke remote method timeout. ...

顾名思义,就是dubbo的server响应超时了,想想也是,文件上传场景非常见的快速调用,接口响应时间应该放宽,设置timeout=10000(10秒),重启调试,问题解决;

@DubboService(version = "1.0.0", timeout = 10000)
public class UploadFileFacadeImpl implements UploadFileFacade {...}

参考:

官方中文文档 | Apache Dubbo