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 11 kB

1 year ago
2 years ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. package downloader
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "math"
  7. "reflect"
  8. "time"
  9. "github.com/samber/lo"
  10. "gitlink.org.cn/cloudream/common/pkgs/bitmap"
  11. "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
  12. "gitlink.org.cn/cloudream/common/pkgs/logger"
  13. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  14. "gitlink.org.cn/cloudream/common/utils/io2"
  15. "gitlink.org.cn/cloudream/common/utils/math2"
  16. "gitlink.org.cn/cloudream/common/utils/sort2"
  17. "gitlink.org.cn/cloudream/storage/common/consts"
  18. stgglb "gitlink.org.cn/cloudream/storage/common/globals"
  19. stgmod "gitlink.org.cn/cloudream/storage/common/models"
  20. "gitlink.org.cn/cloudream/storage/common/pkgs/distlock"
  21. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch2"
  22. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch2/parser"
  23. "gitlink.org.cn/cloudream/storage/common/pkgs/iterator"
  24. coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
  25. )
  26. type downloadStorageInfo struct {
  27. Storage stgmod.StorageDetail
  28. ObjectPinned bool
  29. Blocks []stgmod.ObjectBlock
  30. Distance float64
  31. }
  32. type DownloadContext struct {
  33. Distlock *distlock.Service
  34. }
  35. type DownloadObjectIterator struct {
  36. OnClosing func()
  37. downloader *Downloader
  38. reqs []downloadReqeust2
  39. currentIndex int
  40. inited bool
  41. coorCli *coormq.Client
  42. allStorages map[cdssdk.StorageID]stgmod.StorageDetail
  43. }
  44. func NewDownloadObjectIterator(downloader *Downloader, downloadObjs []downloadReqeust2) *DownloadObjectIterator {
  45. return &DownloadObjectIterator{
  46. downloader: downloader,
  47. reqs: downloadObjs,
  48. }
  49. }
  50. func (i *DownloadObjectIterator) MoveNext() (*Downloading, error) {
  51. if !i.inited {
  52. if err := i.init(); err != nil {
  53. return nil, err
  54. }
  55. i.inited = true
  56. }
  57. if i.currentIndex >= len(i.reqs) {
  58. return nil, iterator.ErrNoMoreItem
  59. }
  60. item, err := i.doMove()
  61. i.currentIndex++
  62. return item, err
  63. }
  64. func (i *DownloadObjectIterator) init() error {
  65. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  66. if err != nil {
  67. return fmt.Errorf("new coordinator client: %w", err)
  68. }
  69. i.coorCli = coorCli
  70. allStgIDsMp := make(map[cdssdk.StorageID]bool)
  71. for _, obj := range i.reqs {
  72. if obj.Detail == nil {
  73. continue
  74. }
  75. for _, p := range obj.Detail.PinnedAt {
  76. allStgIDsMp[p] = true
  77. }
  78. for _, b := range obj.Detail.Blocks {
  79. allStgIDsMp[b.StorageID] = true
  80. }
  81. }
  82. stgIDs := lo.Keys(allStgIDsMp)
  83. getStgs, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails(stgIDs))
  84. if err != nil {
  85. return fmt.Errorf("getting storage details: %w", err)
  86. }
  87. i.allStorages = make(map[cdssdk.StorageID]stgmod.StorageDetail)
  88. for idx, s := range getStgs.Storages {
  89. if s == nil {
  90. return fmt.Errorf("storage %v not found", stgIDs[idx])
  91. }
  92. if s.Shard == nil {
  93. return fmt.Errorf("storage %v has no shard store", stgIDs[idx])
  94. }
  95. i.allStorages[s.Storage.StorageID] = *s
  96. }
  97. return nil
  98. }
  99. func (iter *DownloadObjectIterator) doMove() (*Downloading, error) {
  100. req := iter.reqs[iter.currentIndex]
  101. if req.Detail == nil {
  102. return &Downloading{
  103. Object: nil,
  104. File: nil,
  105. Request: req.Raw,
  106. }, nil
  107. }
  108. switch red := req.Detail.Object.Redundancy.(type) {
  109. case *cdssdk.NoneRedundancy:
  110. reader, err := iter.downloadNoneOrRepObject(req)
  111. if err != nil {
  112. return nil, fmt.Errorf("downloading object %v: %w", req.Raw.ObjectID, err)
  113. }
  114. return &Downloading{
  115. Object: &req.Detail.Object,
  116. File: reader,
  117. Request: req.Raw,
  118. }, nil
  119. case *cdssdk.RepRedundancy:
  120. reader, err := iter.downloadNoneOrRepObject(req)
  121. if err != nil {
  122. return nil, fmt.Errorf("downloading rep object %v: %w", req.Raw.ObjectID, err)
  123. }
  124. return &Downloading{
  125. Object: &req.Detail.Object,
  126. File: reader,
  127. Request: req.Raw,
  128. }, nil
  129. case *cdssdk.ECRedundancy:
  130. reader, err := iter.downloadECObject(req, red)
  131. if err != nil {
  132. return nil, fmt.Errorf("downloading ec object %v: %w", req.Raw.ObjectID, err)
  133. }
  134. return &Downloading{
  135. Object: &req.Detail.Object,
  136. File: reader,
  137. Request: req.Raw,
  138. }, nil
  139. case *cdssdk.LRCRedundancy:
  140. reader, err := iter.downloadLRCObject(req, red)
  141. if err != nil {
  142. return nil, fmt.Errorf("downloading lrc object %v: %w", req.Raw.ObjectID, err)
  143. }
  144. return &Downloading{
  145. Object: &req.Detail.Object,
  146. File: reader,
  147. Request: req.Raw,
  148. }, nil
  149. }
  150. return nil, fmt.Errorf("unsupported redundancy type: %v of object %v", reflect.TypeOf(req.Detail.Object.Redundancy), req.Raw.ObjectID)
  151. }
  152. func (i *DownloadObjectIterator) Close() {
  153. if i.OnClosing != nil {
  154. i.OnClosing()
  155. }
  156. }
  157. func (iter *DownloadObjectIterator) downloadNoneOrRepObject(obj downloadReqeust2) (io.ReadCloser, error) {
  158. allStgs, err := iter.sortDownloadStorages(obj)
  159. if err != nil {
  160. return nil, err
  161. }
  162. bsc, blocks := iter.getMinReadingBlockSolution(allStgs, 1)
  163. osc, stg := iter.getMinReadingObjectSolution(allStgs, 1)
  164. if bsc < osc {
  165. logger.Debugf("downloading object %v from storage %v", obj.Raw.ObjectID, blocks[0].Storage.Storage)
  166. return iter.downloadFromStorage(&blocks[0].Storage, obj)
  167. }
  168. if osc == math.MaxFloat64 {
  169. // bsc >= osc,如果osc是MaxFloat64,那么bsc也一定是,也就意味着没有足够块来恢复文件
  170. return nil, fmt.Errorf("no storage has this object")
  171. }
  172. logger.Debugf("downloading object %v from storage %v(%v)", obj.Raw.ObjectID, stg)
  173. return iter.downloadFromStorage(stg, obj)
  174. }
  175. func (iter *DownloadObjectIterator) downloadECObject(req downloadReqeust2, ecRed *cdssdk.ECRedundancy) (io.ReadCloser, error) {
  176. allNodes, err := iter.sortDownloadStorages(req)
  177. if err != nil {
  178. return nil, err
  179. }
  180. bsc, blocks := iter.getMinReadingBlockSolution(allNodes, ecRed.K)
  181. osc, stg := iter.getMinReadingObjectSolution(allNodes, ecRed.K)
  182. if bsc < osc {
  183. var logStrs []any = []any{fmt.Sprintf("downloading ec object %v from blocks: ", req.Raw.ObjectID)}
  184. for i, b := range blocks {
  185. if i > 0 {
  186. logStrs = append(logStrs, ", ")
  187. }
  188. logStrs = append(logStrs, fmt.Sprintf("%v@%v(%v)", b.Block.Index, b.Storage))
  189. }
  190. logger.Debug(logStrs...)
  191. pr, pw := io.Pipe()
  192. go func() {
  193. readPos := req.Raw.Offset
  194. totalReadLen := req.Detail.Object.Size - req.Raw.Offset
  195. if req.Raw.Length >= 0 {
  196. totalReadLen = math2.Min(req.Raw.Length, totalReadLen)
  197. }
  198. firstStripIndex := readPos / ecRed.StripSize()
  199. stripIter := NewStripIterator(req.Detail.Object, blocks, ecRed, firstStripIndex, iter.downloader.strips, iter.downloader.cfg.ECStripPrefetchCount)
  200. defer stripIter.Close()
  201. for totalReadLen > 0 {
  202. strip, err := stripIter.MoveNext()
  203. if err == iterator.ErrNoMoreItem {
  204. pw.CloseWithError(io.ErrUnexpectedEOF)
  205. return
  206. }
  207. if err != nil {
  208. pw.CloseWithError(err)
  209. return
  210. }
  211. readRelativePos := readPos - strip.Position
  212. curReadLen := math2.Min(totalReadLen, ecRed.StripSize()-readRelativePos)
  213. err = io2.WriteAll(pw, strip.Data[readRelativePos:readRelativePos+curReadLen])
  214. if err != nil {
  215. pw.CloseWithError(err)
  216. return
  217. }
  218. totalReadLen -= curReadLen
  219. readPos += curReadLen
  220. }
  221. pw.Close()
  222. }()
  223. return pr, nil
  224. }
  225. // bsc >= osc,如果osc是MaxFloat64,那么bsc也一定是,也就意味着没有足够块来恢复文件
  226. if osc == math.MaxFloat64 {
  227. return nil, fmt.Errorf("no enough blocks to reconstruct the object %v , want %d, get only %d", req.Raw.ObjectID, ecRed.K, len(blocks))
  228. }
  229. logger.Debugf("downloading ec object %v from storage %v(%v)", req.Raw.ObjectID, stg)
  230. return iter.downloadFromStorage(stg, req)
  231. }
  232. func (iter *DownloadObjectIterator) sortDownloadStorages(req downloadReqeust2) ([]*downloadStorageInfo, error) {
  233. var stgIDs []cdssdk.StorageID
  234. for _, id := range req.Detail.PinnedAt {
  235. if !lo.Contains(stgIDs, id) {
  236. stgIDs = append(stgIDs, id)
  237. }
  238. }
  239. for _, b := range req.Detail.Blocks {
  240. if !lo.Contains(stgIDs, b.StorageID) {
  241. stgIDs = append(stgIDs, b.StorageID)
  242. }
  243. }
  244. downloadNodeMap := make(map[cdssdk.StorageID]*downloadStorageInfo)
  245. for _, id := range req.Detail.PinnedAt {
  246. node, ok := downloadNodeMap[id]
  247. if !ok {
  248. mod := iter.allStorages[id]
  249. node = &downloadStorageInfo{
  250. Storage: mod,
  251. ObjectPinned: true,
  252. Distance: iter.getNodeDistance(mod),
  253. }
  254. downloadNodeMap[id] = node
  255. }
  256. node.ObjectPinned = true
  257. }
  258. for _, b := range req.Detail.Blocks {
  259. node, ok := downloadNodeMap[b.StorageID]
  260. if !ok {
  261. mod := iter.allStorages[b.StorageID]
  262. node = &downloadStorageInfo{
  263. Storage: mod,
  264. Distance: iter.getNodeDistance(mod),
  265. }
  266. downloadNodeMap[b.StorageID] = node
  267. }
  268. node.Blocks = append(node.Blocks, b)
  269. }
  270. return sort2.Sort(lo.Values(downloadNodeMap), func(left, right *downloadStorageInfo) int {
  271. return sort2.Cmp(left.Distance, right.Distance)
  272. }), nil
  273. }
  274. func (iter *DownloadObjectIterator) getMinReadingBlockSolution(sortedStgs []*downloadStorageInfo, k int) (float64, []downloadBlock) {
  275. gotBlocksMap := bitmap.Bitmap64(0)
  276. var gotBlocks []downloadBlock
  277. dist := float64(0.0)
  278. for _, n := range sortedStgs {
  279. for _, b := range n.Blocks {
  280. if !gotBlocksMap.Get(b.Index) {
  281. gotBlocks = append(gotBlocks, downloadBlock{
  282. Storage: n.Storage,
  283. Block: b,
  284. })
  285. gotBlocksMap.Set(b.Index, true)
  286. dist += n.Distance
  287. }
  288. if len(gotBlocks) >= k {
  289. return dist, gotBlocks
  290. }
  291. }
  292. }
  293. return math.MaxFloat64, gotBlocks
  294. }
  295. func (iter *DownloadObjectIterator) getMinReadingObjectSolution(sortedStgs []*downloadStorageInfo, k int) (float64, *stgmod.StorageDetail) {
  296. dist := math.MaxFloat64
  297. var downloadStg *stgmod.StorageDetail
  298. for _, n := range sortedStgs {
  299. if n.ObjectPinned && float64(k)*n.Distance < dist {
  300. dist = float64(k) * n.Distance
  301. stg := n.Storage
  302. downloadStg = &stg
  303. }
  304. }
  305. return dist, downloadStg
  306. }
  307. func (iter *DownloadObjectIterator) getNodeDistance(stg stgmod.StorageDetail) float64 {
  308. if stgglb.Local.NodeID != nil {
  309. if stg.MasterHub.NodeID == *stgglb.Local.NodeID {
  310. return consts.NodeDistanceSameNode
  311. }
  312. }
  313. if stg.MasterHub.LocationID == stgglb.Local.LocationID {
  314. return consts.NodeDistanceSameLocation
  315. }
  316. c := iter.downloader.conn.Get(stg.MasterHub.NodeID)
  317. if c == nil || c.Delay == nil || *c.Delay > time.Duration(float64(time.Millisecond)*iter.downloader.cfg.HighLatencyNodeMs) {
  318. return consts.NodeDistanceHighLatencyNode
  319. }
  320. return consts.NodeDistanceOther
  321. }
  322. func (iter *DownloadObjectIterator) downloadFromStorage(stg *stgmod.StorageDetail, req downloadReqeust2) (io.ReadCloser, error) {
  323. var strHandle *exec.DriverReadStream
  324. ft := ioswitch2.NewFromTo()
  325. toExec, handle := ioswitch2.NewToDriver(-1)
  326. toExec.Range = exec.Range{
  327. Offset: req.Raw.Offset,
  328. }
  329. if req.Raw.Length != -1 {
  330. len := req.Raw.Length
  331. toExec.Range.Length = &len
  332. }
  333. // TODO FileHash应该是FileHash类型
  334. ft.AddFrom(ioswitch2.NewFromShardstore(req.Detail.Object.FileHash, *stg.MasterHub, stg.Storage, -1)).AddTo(toExec)
  335. strHandle = handle
  336. parser := parser.NewParser(cdssdk.DefaultECRedundancy)
  337. plans := exec.NewPlanBuilder()
  338. if err := parser.Parse(ft, plans); err != nil {
  339. return nil, fmt.Errorf("parsing plan: %w", err)
  340. }
  341. // TODO2 注入依赖
  342. exeCtx := exec.NewExecContext()
  343. exec := plans.Execute(exeCtx)
  344. go exec.Wait(context.TODO())
  345. return exec.BeginRead(strHandle)
  346. }

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