短視頻上傳怎么做|寫個支持分片上傳/斷點續傳/秒傳功能的文件服務吧
前言
各位平時使用的短視頻應用,微信 & 微博等圖文社區,它們的圖文動態 & 視頻上傳的能力,都是極其核心的業務。
本質來說,這都是文件的上傳,這篇文章帶大家寫一個文件上傳服務,探究其核心原理,相信能為你帶來一些幫助。
感謝我的好友 Trembling 對本文的支持
主要包含以下能力:
- 文件上傳
- 文件下載
- 分片上傳
- 斷點續傳
- 文件秒傳
往期視頻講解 ??:B站:白澤talk,公眾號:白澤talk

FileService 主要能力
pd定義:
service FileService {
// pre sign a file url for user get it
rpc PreSignGet(PreSignGetRequest) returns (PreSignGetResponse);
// pre sign a file url for user put it
rpc PreSignPut(PreSignPutRequest) returns (PreSignPutResponse);
// report a file has been uploaded
rpc ReportUploaded(ReportUploadedRequest) returns (ReportUploadedResponse);
// pre sign a file url for user put it with slicing
rpc PreSignSlicingPut(PreSignSlicingPutRequest) returns (PreSignSlicingPutResponse);
// get upload progress rate for slicing put
rpc GetProgressRate4SlicingPut(GetProgressRate4SlicingPutRequest) returns (GetProgressRate4SlicingPutResponse);
// merge a slicing uploading file
rpc MergeFileParts(MergeFilePartsRequest) returns (MergeFilePartsResponse);
// remove a file
rpc RemoveFile(RemoveFileRequest) returns (RemoveFileResponse);
}
FileService用于向各個業務領域提供文件上傳、下載的能力。在FileService的所有接口中,都存在一個名為file context的結構體,該參數通常用于指定文件相關的信息。其結構如下:
message FileContext {
// 所屬業務領域,用于創建bucket
string domain = 1;
// 所屬業務名稱
string biz_name = 2;
// 文件id
int64 file_id = 3;
// 文件sha256 hash
string hash = 4;
// 文件類型
string file_type = 5;
// 文件大小,單位byte
int64 size = 6;
// 文件訪問鏈接的過期時間
int64 expire_seconds = 7;
// 文件名
string filename = 8;
}
在各個請求中,domain和biz_name兩個參數為必傳項,這個2個參數組合后,將使得FileService所依賴的表以業務領域為維度進行第一次分表。在此基礎上,可以為每個業務領域配置一個對應的二次分表,可以指定其分為若干張子表。
Quick Start
FileService對于不同業務領域和不同業務項,其文件數量的擴增速度是不同的,帶來的數據量很有可能天差地別。所以,要使用FileService的能力,需要在FileService中進行配置分表數量。
在config.yaml中,可以配置如下內容:
data:
db_sharding_config:
file_shortvideo_short_video:
sharding: file_shortvideo_short_video
sharding_number: 5
db_sharding_config項用于配置分表數量,其下的file_shortvideo_short_video為分表key,其中"file"為固定值,shortvideo為業務領域,short_video為業務名稱。sharding_number為分表數量。業務領域和業務名稱需要在使用FileService時傳入。如不進行配置,則默認分表數量為1。
主要鏈路
普通上傳/下載
分片上傳/斷點續傳
主要能力
普通上傳
FileService.PreSignPut提供了最基礎的上傳能力。該接口需要額外上傳的參數包括:
- hash: 文件的sha256值
- file_type: 文件類型
- size:文件大小(字節數)
- expire_seconds:文件上傳鏈接的過期時間
例如,可以向此接口傳入這樣的參數:
{
"file_context": {
"domain": "shortvideo",
"biz_name": "short_video",
"hash": "8D6BB0819A2C1E66F846031DC54AAF47",
"file_type": "pdf",
"size": 1181178,
"expire_seconds": 86400
}
}
訪問后,該接口將返回一個上傳鏈接(http),由上游服務/前端直接將文件上傳到該鏈接。一個上傳示例代碼(Python):
with open(file_path, 'rb') as file_data:
response = requests.put(
minio_url, # 接口返回的上傳鏈接
data=file_data,
headers={"Content-Type": "application/octet-stream"}
)
print(response)
return response.status_code
通過上述Python代碼的方式上傳完成后,并不能直接訪問到該文件,需要通過FileService.ReportUploaded接口來進行上傳確認,只有上傳確認后的文件,才會在數據庫中被標記為uploaded并能被訪問到。關于FileService.ReportUploaded的具體使用,將在下文中詳細介紹。
分片上傳
在一些情況下,需要上傳的文件較大,如果直接上傳,可能出現如下問題:
- 上傳較慢
- 上傳過程中如果出現問題,則需要重新上傳整個文件
所以,FileService提供了分片上傳的能力。
首先,可以通過FileService.PreSignSlicingPut預注冊一個分片上傳任務。該接口需要傳入的參數與FileService.PreSignPut相同。接口返回的主要內容包括:
- urls:數組,各個分片的上傳鏈接,且已經按照分片號排序
- upload_id:上傳任務的id
- parts:分片總數
- file_id:文件id
參數示例:
{
"file_context": {
"domain": "shortvideo",
"biz_name": "short_video",
"hash": "8D6BB0819A2C1E66F846031DC54AAF47",
"file_type": "pdf",
"size": 72246060,
"expire_seconds": 86400
}
}
此時,由上有服務/前端對文件進行分片(每一片大小為5MB),然后將各個分片進行上傳,一個文件分片的示例如下(Python):
def slicing(filename):
file_size = 5 * 1024 * 1024 # 10MB
files = list()
# 打開文件
with open(filename, 'rb') as f:
index = 0
while True:
# 定位到要讀取的位置
f.seek(index * file_size)
# 讀取數據
data = f.read(file_size)
# 如果已經讀到文件末尾,退出循環
if not data:
break
# 寫入分割后的文件
with open(f'{filename}_{index}', 'wb') as f1:
f1.write(data)
files.append(data)
# 更新位置
index += 1
return files
全部分片上傳完成后,可以通過FileService.MergeFileParts來合并分片。主要參數包括:
- file_id
- upload_id
參數示例:
{
"upload_id": "ZDNlOWI2MjktMjAzOC00NzJkLWE0ODYtOGMzZTBlZmJlODUwLmRmN2M5ZWQyLTYxMzMtNDM4NS1hNTljLWEwMzRlNTI5NWNkNHgxNzIzNzM5ODA2MTM2NzU3MzE5",
"file_context": {
"file_id": 1824123073628999680,
"domain": "shortvideo",
"biz_name": "short_video"
}
}
與不同上傳不同的是,分片上傳在上傳完成并調用FileService.MergeFileParts后,會自動進行上傳確認,無需再次調用FileService.ReportUploaded。
斷點續傳
在上述分片上傳的過程中,可以通過FileService.GetProgressRate4SlicingPut來獲取分片上傳的具體情況,主要傳入的參數包括:
- file_id
- upload_id
該接口的返回值中包含一個名為parts的map,key為分片號,value為該分片是否上傳完成,上游服務或服務端可以根據該信息來決定哪些分片需要重新上傳
示例參數:
{
"upload_id": "ZDNlOWI2MjktMjAzOC00NzJkLWE0ODYtOGMzZTBlZmJlODUwLjA3ZTgyNmY0LWE4YjQtNDQxMC04M2QzLWY4ODQ4MTRiZGM4Y3gxNzIzNzM4ODUzMTMzMjY4ODQ3",
"file_context": {
"file_id": 1824119076553756672,
"domain": "shortvideo",
"biz_name": "short_video"
}
}
返回值示例:
{
"parts": {
"1": true,
"2": true,
"3": true,
"4": true,
"5": true,
"6": true,
"7": true,
"8": true,
"9": true,
"10": true,
"11": true,
"12": true,
"13": true,
"14": true
},
"meta": {
"reason": [],
"biz_code": 0,
"message": "success",
"domain": ""
},
"progress_rate": 100
}
上傳確認
在上傳完成時(分片上傳除外,分片上傳結束時調用FileService.MergeFileParts會自動進行上傳確認),都需要調用FileService.ReportUploaded來進行上報,該接口必傳參數為:
- file_id
這一接口主要完成這樣一件事:檢查文件的hash,檢查通過后將文件標記為“上傳成功狀態”,否則文件將不可被查詢到。
參數示例:
{
"file_context": {
"domain": "shortvideo",
"biz_name": "short_video",
"file_id": "1824118603822141440"
}
}
下載文件
通過FileService.PreSignGet接口則可以獲取下載文件鏈接,該接口主要傳入的參數包括:
- file_id
- expire_seconds
參數示例:
{
"file_context": {
"domain": "shortvideo",
"biz_name": "short_video",
"file_id": "1824123073628999680",
"expire_seconds": 86400,
"filename": "data.mp4"
}
}
秒傳
在FileService.PreSignPut和FileService.PreSignSlicingPut接口中,如果傳入的hash已經存在,則會返回一個file_id,這個file_id可以用于獲取下載鏈接,從而不需要再次上傳文件。
各位平時使用的短視頻應用,微信 & 微博等圖文社區,它們的圖文動態 & 視頻上傳的能力,都是極其核心的業務。
本質來說,這都是文件的上傳,這篇文章帶大家寫一個文件上傳服務,探究其核心原理,相信能為你帶來一些幫助。
浙公網安備 33010602011771號