背景
最近因为工作业务需要做一个本地数据上传到服务器的项目和一个RK3576 OTA升级的项目,一个是上传,一个是下载。我过去接触的数据上传下载,也就只有SCP协议或者ADB协议,显然这只适合调试而非生产环境。对于生产环境中的数据上传和下载, 我认为它至少要满足以下要求:
- 使用一个成熟的基于TCP/IP协议的数据传输协议
- 可以做到断点续传,可以实时获取传输进度和速率
- 小文件压缩,提高IO速率
- 传输过程中给文件加密
- 需要和服务器有握手和身份认证服务
- 需要有文件校验功能
为此,我需要学习一下如果根据上面的要求来设计一个数据上传和下载服务。语言和生态使用Rust。
1. 需要维护哪几种数据?
最直接的想法,如果我要做数据上传和下载,那么我就只关心需要上传/下载的数据就行了,但是这样的话,系统无法得知有哪些数据需要上传/下载和上传/下载的状态如何。所以,一般系统会包含三个部分:
- 数据平面(Data Plane):负责数据的传输
- 控制平面(Control Plane):负责权限、任务和状态
- 元数据平面(Metadata):记录文件信息、Chunk信息、版本
如果从数据类型的角度分,需要维护:
- Chunk
- Manifest
2. 数据传输以Chunk为单位
什么是Chunk?
Chunk就是文件切片,把所有数据分为固定大小的块,传输时以Chunk为单位,而不是以实际的单个文件为单位。Chunk主要用于数据上传/下载时的断点续传和流式传输(边下边播)。
每个Chunk都带有一个头部,说明当前Chunk的偏移量和附带校验码。
在传输的时候,把所有文件分为固定大小的Chunk:
file │ │ ├── chunk0 ├── chunk1 ├── chunk2 ├── chunk3 └── chunkN如果上传过程中断了,可以接着上一个Chunk开始传输,而不是从头开始。
Chunk需要包含哪些元信息?
Chunk需要的元信息例如:
chunk { file_id # chunk id chunk_index # 索引 offset # 偏移量,一般由chunk长度和索引决定 length # chunk长度,就是每个chunk的大小,一般是所有chunk固定大小 sha256 # sha256校验信息}Chunk传输的流程
接收端收到某个Chunk后,先获取id,再校验sha256,再确认收到(ACK),和发送端同步状态(是为了防止丢包:发送端需要确认发送端以收到,否则应重复发送)。
此外,接收端发送ACK时,还应该包含期望下一次收到的id的信息,例如当前接收到了17,下一次期望接收18,避免乱序。
还应该有相应的错误处理机制,例如如果sha256码错误了怎么办、收到的chunk的id不符合期望怎么办、接收端一直不ACK怎么办等等。值得庆幸的是,Rust强大的错误处理机制将在这里得到体现。
3. Manifest 清单文件
Manifest有什么用
Manifest就是清单文件,这个清单文件在数据传输过程中非常重要,根据我的理解,有以下功能需要依托manifest来实现:
-
维护单次传输时数据的元信息:
知道这次要传哪些文件、chunk有多少、sha256信息
-
发送端和接收端之间同步数据信息和进度
- 让接收端知道本次传输有哪些数据
- 让接收端有信息来做数据校验
- 让接收端记录当前传输进度
- 断点续传时知道从哪里开始
据此可以看出,对于单次数据传输来说,逻辑上一共有一份manifest,但是发送端和接收端都需要单独维护一个manifest,一个是静态的,一个是动态的(用来记录进度等信息),同时,这个传输过程还不是单向的,发送端要随时从接收端哪里同步状态,manifest也起到一个握手协议的作用。
依托于Manifest的传输流程
按照设想:发送端在发送开始时,把manfest发送给接收端,接收端存入MySQL/Redis 后,后续所有的 ACK 通信里,只携带 chunk_index,不再重复传输整个 Manifest。
4. 传输协议选择什么
最适合的传输协议是HTTP/HTTPS,没啥好说的,功能完备,Rust生态也成熟。
5. 压缩与加密
一般情况下,如果细碎小文件太多,会影响传输的IO速率,显著影响传输速率,应该把细碎小文件压缩为大文件来传输。而上述流程中,文件传输是以Chunk为单位的,所以大于单个Chunk的文件不用压缩,压缩的大小也以Chunk的大小为基准。