You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

ec_object_iterator.go 4.9 kB

2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package iterator
  2. import (
  3. "fmt"
  4. "io"
  5. "math/rand"
  6. "github.com/samber/lo"
  7. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  8. myio "gitlink.org.cn/cloudream/common/utils/io"
  9. stgglb "gitlink.org.cn/cloudream/storage/common/globals"
  10. stgmodels "gitlink.org.cn/cloudream/storage/common/models"
  11. "gitlink.org.cn/cloudream/storage/common/pkgs/db/model"
  12. "gitlink.org.cn/cloudream/storage/common/pkgs/ec"
  13. coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
  14. )
  15. type ECObjectIterator struct {
  16. OnClosing func()
  17. objects []model.Object
  18. objectECData []stgmodels.ObjectECData
  19. currentIndex int
  20. inited bool
  21. ecInfo cdssdk.ECRedundancyInfo
  22. ec model.Ec
  23. downloadCtx *DownloadContext
  24. cliLocation model.Location
  25. }
  26. func NewECObjectIterator(objects []model.Object, objectECData []stgmodels.ObjectECData, ecInfo cdssdk.ECRedundancyInfo, ec model.Ec, downloadCtx *DownloadContext) *ECObjectIterator {
  27. return &ECObjectIterator{
  28. objects: objects,
  29. objectECData: objectECData,
  30. ecInfo: ecInfo,
  31. ec: ec,
  32. downloadCtx: downloadCtx,
  33. }
  34. }
  35. func (i *ECObjectIterator) MoveNext() (*IterDownloadingObject, error) {
  36. // TODO 加锁
  37. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  38. if err != nil {
  39. return nil, fmt.Errorf("new coordinator client: %w", err)
  40. }
  41. defer stgglb.CoordinatorMQPool.Release(coorCli)
  42. if !i.inited {
  43. i.inited = true
  44. findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(stgglb.Local.ExternalIP))
  45. if err != nil {
  46. return nil, fmt.Errorf("finding client location: %w", err)
  47. }
  48. i.cliLocation = findCliLocResp.Location
  49. }
  50. if i.currentIndex >= len(i.objects) {
  51. return nil, ErrNoMoreItem
  52. }
  53. item, err := i.doMove(coorCli)
  54. i.currentIndex++
  55. return item, err
  56. }
  57. func (iter *ECObjectIterator) doMove(coorCli *coormq.Client) (*IterDownloadingObject, error) {
  58. obj := iter.objects[iter.currentIndex]
  59. ecData := iter.objectECData[iter.currentIndex]
  60. //采取直接读,优先选内网节点
  61. var chosenNodes []DownloadNodeInfo
  62. var chosenBlocks []stgmodels.ObjectBlockData
  63. for i := range ecData.Blocks {
  64. if len(chosenBlocks) == iter.ec.EcK {
  65. break
  66. }
  67. // 块没有被任何节点缓存或者获取失败都没关系,只要能获取到k个块的信息就行
  68. if len(ecData.Blocks[i].NodeIDs) == 0 {
  69. continue
  70. }
  71. getNodesResp, err := coorCli.GetNodes(coormq.NewGetNodes(ecData.Blocks[i].NodeIDs))
  72. if err != nil {
  73. continue
  74. }
  75. downloadNodes := lo.Map(getNodesResp.Nodes, func(node model.Node, index int) DownloadNodeInfo {
  76. return DownloadNodeInfo{
  77. Node: node,
  78. IsSameLocation: node.LocationID == iter.cliLocation.LocationID,
  79. }
  80. })
  81. chosenBlocks = append(chosenBlocks, ecData.Blocks[i])
  82. chosenNodes = append(chosenNodes, iter.chooseDownloadNode(downloadNodes))
  83. }
  84. if len(chosenBlocks) < iter.ec.EcK {
  85. return nil, fmt.Errorf("no enough blocks to reconstruct the file, want %d, get only %d", iter.ec.EcK, len(chosenBlocks))
  86. }
  87. reader, err := iter.downloadEcObject(iter.downloadCtx, obj.Size, chosenNodes, chosenBlocks)
  88. if err != nil {
  89. return nil, fmt.Errorf("ec read failed, err: %w", err)
  90. }
  91. return &IterDownloadingObject{
  92. Object: obj,
  93. File: reader,
  94. }, nil
  95. }
  96. func (i *ECObjectIterator) Close() {
  97. if i.OnClosing != nil {
  98. i.OnClosing()
  99. }
  100. }
  101. // chooseDownloadNode 选择一个下载节点
  102. // 1. 从与当前客户端相同地域的节点中随机选一个
  103. // 2. 没有用的话从所有节点中随机选一个
  104. func (i *ECObjectIterator) chooseDownloadNode(entries []DownloadNodeInfo) DownloadNodeInfo {
  105. sameLocationEntries := lo.Filter(entries, func(e DownloadNodeInfo, i int) bool { return e.IsSameLocation })
  106. if len(sameLocationEntries) > 0 {
  107. return sameLocationEntries[rand.Intn(len(sameLocationEntries))]
  108. }
  109. return entries[rand.Intn(len(entries))]
  110. }
  111. func (iter *ECObjectIterator) downloadEcObject(ctx *DownloadContext, fileSize int64, nodes []DownloadNodeInfo, blocks []stgmodels.ObjectBlockData) (io.ReadCloser, error) {
  112. var fileStrs []io.ReadCloser
  113. rs, err := ec.NewRs(iter.ec.EcK, iter.ec.EcN, iter.ecInfo.ChunkSize)
  114. if err != nil {
  115. return nil, fmt.Errorf("new rs: %w", err)
  116. }
  117. for i := range blocks {
  118. str, err := downloadFile(ctx, nodes[i], blocks[i].FileHash)
  119. if err != nil {
  120. for i -= 1; i >= 0; i-- {
  121. fileStrs[i].Close()
  122. }
  123. return nil, fmt.Errorf("donwloading file: %w", err)
  124. }
  125. fileStrs = append(fileStrs, str)
  126. }
  127. fileReaders, filesCloser := myio.ToReaders(fileStrs)
  128. var indexes []int
  129. for _, b := range blocks {
  130. indexes = append(indexes, b.Index)
  131. }
  132. outputs, outputsCloser := myio.ToReaders(rs.ReconstructData(fileReaders, indexes))
  133. return myio.AfterReadClosed(myio.Length(myio.ChunkedJoin(outputs, int(iter.ecInfo.ChunkSize)), fileSize), func(c io.ReadCloser) {
  134. filesCloser()
  135. outputsCloser()
  136. }), nil
  137. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。