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  }