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.

iterator.go 7.3 kB

7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package downloader
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "reflect"
  7. "time"
  8. "gitlink.org.cn/cloudream/common/pkgs/logger"
  9. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/exec"
  10. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/plan/ops"
  11. "gitlink.org.cn/cloudream/jcs-pub/common/types/datamap"
  12. "gitlink.org.cn/cloudream/common/utils/io2"
  13. "gitlink.org.cn/cloudream/common/utils/math2"
  14. "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader/strategy"
  15. "gitlink.org.cn/cloudream/jcs-pub/client/internal/publock"
  16. stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
  17. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
  18. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/ops2"
  19. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser"
  20. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/iterator"
  21. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  22. )
  23. type downloadSpaceInfo struct {
  24. Space jcstypes.UserSpaceDetail
  25. ObjectPinned bool
  26. Blocks []jcstypes.ObjectBlock
  27. Distance float64
  28. }
  29. type DownloadContext struct {
  30. PubLock *publock.PubLock
  31. }
  32. type DownloadObjectIterator struct {
  33. OnClosing func()
  34. downloader *Downloader
  35. reqs []downloadReqeust2
  36. currentIndex int
  37. }
  38. func NewDownloadObjectIterator(downloader *Downloader, downloadObjs []downloadReqeust2) *DownloadObjectIterator {
  39. return &DownloadObjectIterator{
  40. downloader: downloader,
  41. reqs: downloadObjs,
  42. }
  43. }
  44. func (i *DownloadObjectIterator) MoveNext() (*Downloading, error) {
  45. if i.currentIndex >= len(i.reqs) {
  46. return nil, iterator.ErrNoMoreItem
  47. }
  48. req := i.reqs[i.currentIndex]
  49. if req.Detail == nil {
  50. return &Downloading{
  51. Object: nil,
  52. File: nil,
  53. Request: req.Raw,
  54. }, nil
  55. }
  56. strg, err := i.downloader.selector.Select(strategy.Request{
  57. Detail: *req.Detail,
  58. Range: math2.NewRange(req.Raw.Offset, req.Raw.Length),
  59. DestLocation: stgglb.Local.Location,
  60. })
  61. if err != nil {
  62. return nil, fmt.Errorf("selecting download strategy: %w", err)
  63. }
  64. var reader io.ReadCloser
  65. switch strg := strg.(type) {
  66. case *strategy.DirectStrategy:
  67. reader, err = i.downloadDirect(req, *strg)
  68. if err != nil {
  69. return nil, fmt.Errorf("downloading object %v: %w", req.Raw.ObjectID, err)
  70. }
  71. case *strategy.ECReconstructStrategy:
  72. reader, err = i.downloadECReconstruct(req, *strg)
  73. if err != nil {
  74. return nil, fmt.Errorf("downloading ec object %v: %w", req.Raw.ObjectID, err)
  75. }
  76. case *strategy.LRCReconstructStrategy:
  77. reader, err = i.downloadLRCReconstruct(req, *strg)
  78. if err != nil {
  79. return nil, fmt.Errorf("downloading lrc object %v: %w", req.Raw.ObjectID, err)
  80. }
  81. default:
  82. return nil, fmt.Errorf("unsupported strategy type: %v", reflect.TypeOf(strg))
  83. }
  84. i.currentIndex++
  85. return &Downloading{
  86. Object: &req.Detail.Object,
  87. File: reader,
  88. Request: req.Raw,
  89. }, nil
  90. }
  91. func (i *DownloadObjectIterator) Close() {
  92. if i.OnClosing != nil {
  93. i.OnClosing()
  94. }
  95. }
  96. func (i *DownloadObjectIterator) downloadDirect(req downloadReqeust2, strg strategy.DirectStrategy) (io.ReadCloser, error) {
  97. logger.Debugf("downloading object %v from storage %v", req.Raw.ObjectID, strg.UserSpace.UserSpace.Storage.String())
  98. var strHandle *exec.DriverReadStream
  99. ft := ioswitch2.NewFromTo()
  100. toExec, handle := ioswitch2.NewToDriver(ioswitch2.RawStream())
  101. toExec.Range = math2.Range{
  102. Offset: req.Raw.Offset,
  103. }
  104. len := req.Detail.Object.Size - req.Raw.Offset
  105. if req.Raw.Length != -1 {
  106. len = req.Raw.Length
  107. toExec.Range.Length = &len
  108. }
  109. fromSpace := strg.UserSpace
  110. shouldAtClient := i.downloader.speedStats.ShouldAtClient(len)
  111. if shouldAtClient {
  112. fromSpace.RecommendHub = nil
  113. }
  114. ft.AddFrom(ioswitch2.NewFromShardstore(req.Detail.Object.FileHash, fromSpace, ioswitch2.RawStream())).AddTo(toExec)
  115. strHandle = handle
  116. plans := exec.NewPlanBuilder()
  117. if err := parser.Parse(ft, plans); err != nil {
  118. return nil, fmt.Errorf("parsing plan: %w", err)
  119. }
  120. exeCtx := exec.NewExecContext()
  121. exec.SetValueByType(exeCtx, i.downloader.stgPool)
  122. exec := plans.Execute(exeCtx)
  123. rd, err := exec.BeginRead(strHandle)
  124. if err != nil {
  125. return nil, err
  126. }
  127. counter := io2.CounterCloser(rd, nil)
  128. go func() {
  129. startTime := time.Now()
  130. ret, err := exec.Wait(context.TODO())
  131. if err != nil {
  132. logger.Warnf("downloading object %v: %v", req.Raw.ObjectID, err)
  133. }
  134. transBytes := int64(0)
  135. for _, v := range ret.GetArray(ops2.BaseReadStatsStoreKey) {
  136. v2 := v.(*ops2.BaseReadStatsValue)
  137. i.downloader.speedStats.Record(v2.Length, v2.ElapsedTime, v2.Location.IsDriver)
  138. transBytes += v2.Length
  139. }
  140. for _, v := range ret.GetArray(ops.SendStreamStatsStoreKey) {
  141. v2 := v.(*ops.SendStreamStatsValue)
  142. transBytes += v2.Length
  143. }
  144. i.downloader.evtPub.Publish(&datamap.BodyObjectAccessStats{
  145. ObjectID: req.Raw.ObjectID,
  146. RequestSize: len,
  147. TransferAmount: transBytes,
  148. ElapsedTime: time.Since(startTime),
  149. })
  150. }()
  151. return counter, nil
  152. }
  153. func (i *DownloadObjectIterator) downloadECReconstruct(req downloadReqeust2, strg strategy.ECReconstructStrategy) (io.ReadCloser, error) {
  154. var logStrs []any = []any{fmt.Sprintf("downloading ec object %v from: ", req.Raw.ObjectID)}
  155. for i, b := range strg.Blocks {
  156. if i > 0 {
  157. logStrs = append(logStrs, ", ")
  158. }
  159. logStrs = append(logStrs, fmt.Sprintf("%v@%v", b.Index, strg.UserSpaces[i].UserSpace.Storage.String()))
  160. }
  161. logger.Debug(logStrs...)
  162. length := req.Detail.Object.Size - req.Raw.Offset
  163. if req.Raw.Length != -1 {
  164. length = req.Raw.Length
  165. }
  166. shouldAtClient := i.downloader.speedStats.ShouldAtClient(length)
  167. downloadBlks := make([]downloadBlock, len(strg.Blocks))
  168. for i, b := range strg.Blocks {
  169. fromSpace := strg.UserSpaces[i]
  170. if shouldAtClient {
  171. fromSpace.RecommendHub = nil
  172. }
  173. downloadBlks[i] = downloadBlock{
  174. Block: b,
  175. Space: fromSpace,
  176. }
  177. }
  178. pr, pw := io.Pipe()
  179. counter := io2.CounterCloser(pr, nil)
  180. go func() {
  181. startTime := time.Now()
  182. readPos := req.Raw.Offset
  183. totalReadLen := req.Detail.Object.Size - req.Raw.Offset
  184. if req.Raw.Length >= 0 {
  185. totalReadLen = math2.Min(req.Raw.Length, totalReadLen)
  186. }
  187. firstStripIndex := readPos / strg.Redundancy.StripSize()
  188. stripIter := NewStripIterator(i.downloader, req.Detail.Object, downloadBlks, strg.Redundancy, firstStripIndex, i.downloader.strips, i.downloader.cfg.ECStripPrefetchCount)
  189. // defer顺序不能改,因为CollectStats需要在Close之后调用
  190. defer func() {
  191. stats := stripIter.CollectStats()
  192. i.downloader.evtPub.Publish(&datamap.BodyObjectAccessStats{
  193. ObjectID: req.Raw.ObjectID,
  194. RequestSize: length,
  195. TransferAmount: stats.TransferredBytes,
  196. ElapsedTime: time.Since(startTime),
  197. })
  198. }()
  199. defer stripIter.Close()
  200. for totalReadLen > 0 {
  201. strip, err := stripIter.MoveNext()
  202. if err == iterator.ErrNoMoreItem {
  203. pw.CloseWithError(io.ErrUnexpectedEOF)
  204. return
  205. }
  206. if err != nil {
  207. pw.CloseWithError(err)
  208. return
  209. }
  210. readRelativePos := readPos - strip.Position
  211. curReadLen := math2.Min(totalReadLen, strg.Redundancy.StripSize()-readRelativePos)
  212. err = io2.WriteAll(pw, strip.Data[readRelativePos:readRelativePos+curReadLen])
  213. if err != nil {
  214. pw.CloseWithError(err)
  215. return
  216. }
  217. totalReadLen -= curReadLen
  218. readPos += curReadLen
  219. }
  220. pw.Close()
  221. }()
  222. return counter, nil
  223. }

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