github.com/qiniu/go-sdk/v7@v7.27.0/storage/resume_uploader_base.go (about) 1 package storage 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "io" 8 "strconv" 9 "sync" 10 11 api "github.com/qiniu/go-sdk/v7" 12 "github.com/qiniu/go-sdk/v7/storagev2/uplog" 13 ) 14 15 // 分片上传过程中可能遇到的错误 16 var ErrUnmatchedChecksum = errors.New("unmatched checksum") 17 18 const ( 19 // 获取下一个分片Reader失败 20 ErrNextReader = "ErrNextReader" 21 // 超过了最大的重试上传次数 22 ErrMaxUpRetry = "ErrMaxUpRetry" 23 ) 24 25 const ( 26 blockBits = 22 27 blockMask = (1 << blockBits) - 1 28 blockSize = 1 << blockBits 29 30 uploadRecordVersion = "1.0.0" 31 ) 32 33 // Settings 为分片上传设置 34 type Settings struct { 35 TaskQsize int // 可选。任务队列大小。为 0 表示取 Workers * 4。 36 Workers int // 并行 Goroutine 数目。 37 ChunkSize int // 默认的Chunk大小,不设定则为4M(仅在分片上传 v1 中使用) 38 PartSize int64 // 默认的Part大小,不设定则为4M(仅在分片上传 v2 中使用) 39 TryTimes int // 默认的尝试次数,不设定则为3 40 } 41 42 // 分片上传默认参数设置 43 const ( 44 defaultWorkers = 4 // 默认的并发上传的块数量 45 defaultChunkSize = 4 * 1024 * 1024 // 默认的分片大小,4MB(仅在分片上传 v1 中使用) 46 defaultPartSize = 4 * 1024 * 1024 // 默认的分片大小,4MB(仅在分片上传 v2 中使用) 47 defaultTryTimes = 3 // mkblk / bput / uploadParts 失败重试次数 48 ) 49 50 // 分片上传的默认设置 51 var settings = Settings{ 52 TaskQsize: defaultWorkers * 4, 53 Workers: defaultWorkers, 54 ChunkSize: defaultChunkSize, 55 PartSize: defaultPartSize, 56 TryTimes: defaultTryTimes, 57 } 58 59 // SetSettings 可以用来设置分片上传参数 60 func SetSettings(v *Settings) { 61 settings = *v 62 if settings.Workers == 0 { 63 settings.Workers = defaultWorkers 64 } 65 if settings.TaskQsize == 0 { 66 settings.TaskQsize = settings.Workers * 4 67 } 68 if settings.ChunkSize == 0 { 69 settings.ChunkSize = defaultChunkSize 70 } 71 if settings.PartSize == 0 { 72 settings.PartSize = defaultPartSize 73 } 74 if settings.TryTimes == 0 { 75 settings.TryTimes = defaultTryTimes 76 } 77 } 78 79 var ( 80 tasks chan func() 81 once sync.Once 82 ) 83 84 func worker(tasks chan func()) { 85 for task := range tasks { 86 task() 87 } 88 } 89 90 func _initWorkers() { 91 tasks = make(chan func(), settings.TaskQsize) 92 for i := 0; i < settings.Workers; i++ { 93 go worker(tasks) 94 } 95 } 96 97 func initWorkers() { 98 once.Do(_initWorkers) 99 } 100 101 // 代表一块分片的基本信息 102 type ( 103 chunk struct { 104 id int64 105 offset int64 106 size int64 107 reader io.ReaderAt 108 } 109 110 // 通用分片上传接口,同时适用于分片上传 v1 和 v2 接口 111 resumeUploaderBase interface { 112 // 开始上传前调用一次用于初始化,在 v1 中该接口不做任何事情,而在 v2 中该接口对应 initParts 113 initUploader(context.Context) ([]int64, int64, error) 114 // 上传实际的分片数据,允许并发上传。在 v1 中该接口对应 mkblk 和 bput 组合,而在 v2 中该接口对应 uploadParts 115 uploadChunk(context.Context, chunk) error 116 // 上传所有分片后调用一次用于结束上传,在 v1 中该接口对应 mkfile,而在 v2 中该接口对应 completeParts 117 final(context.Context) error 118 // 上传接口版本 119 version() uplog.UpApiVersion 120 // 获取上传凭证 121 getUpToken() string 122 // 获取存储空间 123 getBucket() string 124 // 获取对象名称 125 getKey() (string, bool) 126 } 127 128 // 将已知数据流大小的情况和未知数据流大小的情况抽象成一个接口 129 chunkReader interface { 130 readChunks([]int64, func(chunkID, off, size int64, reader io.ReaderAt) error) error 131 expectedSize() int64 132 } 133 134 // 已知数据流大小的情况下读取数据流 135 unsizedChunkReader struct { 136 body io.Reader 137 blockSize int64 138 } 139 140 // 未知数据流大小的情况下读取数据流 141 sizedChunkReader struct { 142 body io.ReaderAt 143 totalSize int64 144 blockSize int64 145 } 146 ) 147 148 // 使用并发 Goroutine 上传数据 149 func uploadByWorkers(uploader resumeUploaderBase, ctx context.Context, body chunkReader) error { 150 initWorkers() 151 152 recovered, recoveredSize, err := uploader.initUploader(ctx) 153 if err != nil { 154 return err 155 } 156 targetKey, _ := uploader.getKey() 157 return uplog.WithBlock(ctx, uploader.version(), uint64(body.expectedSize()), uint64(recoveredSize), uploader.getBucket(), targetKey, uploader.getUpToken(), func(ctx context.Context) error { 158 // 读取 Chunk 并创建任务 159 var ( 160 uErr error 161 locker sync.Mutex 162 waiter sync.WaitGroup 163 ) 164 rErr := body.readChunks(recovered, func(chunkID, off, size int64, reader io.ReaderAt) error { 165 locker.Lock() 166 if uErr != nil { 167 locker.Unlock() 168 return uErr 169 } 170 locker.Unlock() 171 172 waiter.Add(1) 173 tasks <- func() { 174 pErr := uploader.uploadChunk(ctx, chunk{ 175 id: chunkID, 176 offset: off, 177 size: size, 178 reader: reader, 179 }) 180 181 locker.Lock() 182 if uErr == nil { 183 uErr = pErr 184 } 185 locker.Unlock() 186 waiter.Done() 187 } 188 return nil 189 }) 190 waiter.Wait() 191 if rErr != nil { 192 return rErr 193 } 194 if uErr != nil { 195 return uErr 196 } 197 return uploader.final(ctx) 198 }) 199 } 200 201 func newUnsizedChunkReader(body io.Reader, blockSize int64) *unsizedChunkReader { 202 return &unsizedChunkReader{body: body, blockSize: blockSize} 203 } 204 205 func (r *unsizedChunkReader) readChunks(recovered []int64, f func(chunkID, off, size int64, reader io.ReaderAt) error) error { 206 var ( 207 lastChunk = false 208 chunkID int64 = 0 209 off int64 = 0 210 chunkSize int 211 err error 212 recoveredMap = make(map[int64]struct{}, len(recovered)) 213 ) 214 215 for _, roff := range recovered { 216 recoveredMap[roff] = struct{}{} 217 } 218 219 for !lastChunk { 220 buf := make([]byte, r.blockSize) 221 if chunkSize, err = io.ReadFull(r.body, buf); err != nil { 222 switch err { 223 case io.EOF: 224 lastChunk = true 225 return nil 226 case io.ErrUnexpectedEOF: 227 buf = buf[:chunkSize] 228 lastChunk = true 229 default: 230 return api.NewError(ErrNextReader, err.Error()) 231 } 232 } 233 234 if _, ok := recoveredMap[off]; !ok { 235 if err = f(chunkID, off, int64(len(buf)), bytes.NewReader(buf)); err != nil { 236 return err 237 } 238 } 239 chunkID += 1 240 off += int64(chunkSize) 241 } 242 return nil 243 } 244 245 func (r *unsizedChunkReader) expectedSize() int64 { 246 return 0 247 } 248 249 func newSizedChunkReader(body io.ReaderAt, totalSize, blockSize int64) *sizedChunkReader { 250 return &sizedChunkReader{body: body, totalSize: totalSize, blockSize: blockSize} 251 } 252 253 func (r *sizedChunkReader) readChunks(recovered []int64, f func(chunkID, off, size int64, reader io.ReaderAt) error) error { 254 var ( 255 chunkID int64 = 0 256 off int64 = 0 257 err error 258 recoveredMap = make(map[int64]struct{}, len(recovered)) 259 ) 260 261 for _, roff := range recovered { 262 recoveredMap[roff] = struct{}{} 263 } 264 265 for off < r.totalSize { 266 shouldRead := r.totalSize - off 267 if shouldRead > r.blockSize { 268 shouldRead = r.blockSize 269 } 270 if _, ok := recoveredMap[off]; !ok { 271 if err = f(chunkID, off, shouldRead, io.NewSectionReader(r.body, off, shouldRead)); err != nil { 272 return err 273 } 274 } 275 off += shouldRead 276 chunkID += 1 277 } 278 return nil 279 } 280 281 func (r *sizedChunkReader) expectedSize() int64 { 282 return r.totalSize 283 } 284 285 func getRecorderKey(recorder Recorder, upToken string, key string, 286 resumeVersion string, partSize int64, fileDetails *fileDetailsInfo, 287 ) string { 288 if recorder == nil || fileDetails == nil || len(upToken) == 0 { 289 return "" 290 } 291 292 accessKey, bucket, err := getAkBucketFromUploadToken(upToken) 293 if err != nil { 294 return "" 295 } 296 297 return recorder.GenerateRecorderKey( 298 []string{accessKey, bucket, key, resumeVersion, fileDetails.fileFullPath, strconv.FormatInt(partSize, 10)}, 299 fileDetails.fileInfo) 300 }