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

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

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