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.

download_object_iterator.go 8.4 kB

2 years ago
2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package iterator
  2. import (
  3. "fmt"
  4. "io"
  5. "math/rand"
  6. "reflect"
  7. "github.com/samber/lo"
  8. "gitlink.org.cn/cloudream/common/pkgs/logger"
  9. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  10. myio "gitlink.org.cn/cloudream/common/utils/io"
  11. stgglb "gitlink.org.cn/cloudream/storage/common/globals"
  12. stgmodels "gitlink.org.cn/cloudream/storage/common/models"
  13. "gitlink.org.cn/cloudream/storage/common/pkgs/db/model"
  14. "gitlink.org.cn/cloudream/storage/common/pkgs/distlock"
  15. "gitlink.org.cn/cloudream/storage/common/pkgs/ec"
  16. coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
  17. )
  18. type DownloadingObjectIterator = Iterator[*IterDownloadingObject]
  19. type IterDownloadingObject struct {
  20. Object model.Object
  21. File io.ReadCloser
  22. }
  23. type DownloadNodeInfo struct {
  24. Node model.Node
  25. IsSameLocation bool
  26. }
  27. type DownloadContext struct {
  28. Distlock *distlock.Service
  29. }
  30. type DownloadObjectIterator struct {
  31. OnClosing func()
  32. objectDetails []stgmodels.ObjectDetail
  33. currentIndex int
  34. downloadCtx *DownloadContext
  35. }
  36. func NewDownloadObjectIterator(objectDetails []stgmodels.ObjectDetail, downloadCtx *DownloadContext) *DownloadObjectIterator {
  37. return &DownloadObjectIterator{
  38. objectDetails: objectDetails,
  39. downloadCtx: downloadCtx,
  40. }
  41. }
  42. func (i *DownloadObjectIterator) MoveNext() (*IterDownloadingObject, error) {
  43. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  44. if err != nil {
  45. return nil, fmt.Errorf("new coordinator client: %w", err)
  46. }
  47. defer stgglb.CoordinatorMQPool.Release(coorCli)
  48. if i.currentIndex >= len(i.objectDetails) {
  49. return nil, ErrNoMoreItem
  50. }
  51. item, err := i.doMove(coorCli)
  52. i.currentIndex++
  53. return item, err
  54. }
  55. func (iter *DownloadObjectIterator) doMove(coorCli *coormq.Client) (*IterDownloadingObject, error) {
  56. obj := iter.objectDetails[iter.currentIndex]
  57. switch red := obj.Object.Redundancy.(type) {
  58. case *cdssdk.NoneRedundancy:
  59. reader, err := iter.downloadNoneOrRepObject(coorCli, iter.downloadCtx, obj)
  60. if err != nil {
  61. return nil, fmt.Errorf("downloading object: %w", err)
  62. }
  63. return &IterDownloadingObject{
  64. Object: obj.Object,
  65. File: reader,
  66. }, nil
  67. case *cdssdk.RepRedundancy:
  68. reader, err := iter.downloadNoneOrRepObject(coorCli, iter.downloadCtx, obj)
  69. if err != nil {
  70. return nil, fmt.Errorf("downloading rep object: %w", err)
  71. }
  72. return &IterDownloadingObject{
  73. Object: obj.Object,
  74. File: reader,
  75. }, nil
  76. case *cdssdk.ECRedundancy:
  77. reader, err := iter.downloadECObject(coorCli, iter.downloadCtx, obj, red)
  78. if err != nil {
  79. return nil, fmt.Errorf("downloading ec object: %w", err)
  80. }
  81. return &IterDownloadingObject{
  82. Object: obj.Object,
  83. File: reader,
  84. }, nil
  85. }
  86. return nil, fmt.Errorf("unsupported redundancy type: %v", reflect.TypeOf(obj.Object.Redundancy))
  87. }
  88. func (i *DownloadObjectIterator) Close() {
  89. if i.OnClosing != nil {
  90. i.OnClosing()
  91. }
  92. }
  93. // chooseDownloadNode 选择一个下载节点
  94. // 1. 从与当前客户端相同地域的节点中随机选一个
  95. // 2. 没有用的话从所有节点中随机选一个
  96. func (i *DownloadObjectIterator) chooseDownloadNode(entries []DownloadNodeInfo) DownloadNodeInfo {
  97. sameLocationEntries := lo.Filter(entries, func(e DownloadNodeInfo, i int) bool { return e.IsSameLocation })
  98. if len(sameLocationEntries) > 0 {
  99. return sameLocationEntries[rand.Intn(len(sameLocationEntries))]
  100. }
  101. return entries[rand.Intn(len(entries))]
  102. }
  103. func (iter *DownloadObjectIterator) downloadNoneOrRepObject(coorCli *coormq.Client, ctx *DownloadContext, obj stgmodels.ObjectDetail) (io.ReadCloser, error) {
  104. if len(obj.Blocks) == 0 {
  105. return nil, fmt.Errorf("no node has this object")
  106. }
  107. //采取直接读,优先选内网节点
  108. var chosenNodes []DownloadNodeInfo
  109. grpBlocks := obj.GroupBlocks()
  110. for _, grp := range grpBlocks {
  111. getNodesResp, err := coorCli.GetNodes(coormq.NewGetNodes(grp.NodeIDs))
  112. if err != nil {
  113. continue
  114. }
  115. downloadNodes := lo.Map(getNodesResp.Nodes, func(node model.Node, index int) DownloadNodeInfo {
  116. return DownloadNodeInfo{
  117. Node: node,
  118. IsSameLocation: node.LocationID == stgglb.Local.LocationID,
  119. }
  120. })
  121. chosenNodes = append(chosenNodes, iter.chooseDownloadNode(downloadNodes))
  122. }
  123. var fileStrs []io.ReadCloser
  124. for i := range grpBlocks {
  125. str, err := downloadFile(ctx, chosenNodes[i], grpBlocks[i].FileHash)
  126. if err != nil {
  127. for i -= 1; i >= 0; i-- {
  128. fileStrs[i].Close()
  129. }
  130. return nil, fmt.Errorf("donwloading file: %w", err)
  131. }
  132. fileStrs = append(fileStrs, str)
  133. }
  134. fileReaders, filesCloser := myio.ToReaders(fileStrs)
  135. return myio.AfterReadClosed(myio.Length(myio.Join(fileReaders), obj.Object.Size), func(c io.ReadCloser) {
  136. filesCloser()
  137. }), nil
  138. }
  139. func (iter *DownloadObjectIterator) downloadECObject(coorCli *coormq.Client, ctx *DownloadContext, obj stgmodels.ObjectDetail, ecRed *cdssdk.ECRedundancy) (io.ReadCloser, error) {
  140. //采取直接读,优先选内网节点
  141. var chosenNodes []DownloadNodeInfo
  142. var chosenBlocks []stgmodels.GrouppedObjectBlock
  143. grpBlocks := obj.GroupBlocks()
  144. for i := range grpBlocks {
  145. if len(chosenBlocks) == ecRed.K {
  146. break
  147. }
  148. getNodesResp, err := coorCli.GetNodes(coormq.NewGetNodes(grpBlocks[i].NodeIDs))
  149. if err != nil {
  150. continue
  151. }
  152. downloadNodes := lo.Map(getNodesResp.Nodes, func(node model.Node, index int) DownloadNodeInfo {
  153. return DownloadNodeInfo{
  154. Node: node,
  155. IsSameLocation: node.LocationID == stgglb.Local.LocationID,
  156. }
  157. })
  158. chosenBlocks = append(chosenBlocks, grpBlocks[i])
  159. chosenNodes = append(chosenNodes, iter.chooseDownloadNode(downloadNodes))
  160. }
  161. if len(chosenBlocks) < ecRed.K {
  162. return nil, fmt.Errorf("no enough blocks to reconstruct the file, want %d, get only %d", ecRed.K, len(chosenBlocks))
  163. }
  164. var fileStrs []io.ReadCloser
  165. rs, err := ec.NewRs(ecRed.K, ecRed.N, ecRed.ChunkSize)
  166. if err != nil {
  167. return nil, fmt.Errorf("new rs: %w", err)
  168. }
  169. for i := range chosenBlocks {
  170. str, err := downloadFile(ctx, chosenNodes[i], chosenBlocks[i].FileHash)
  171. if err != nil {
  172. for i -= 1; i >= 0; i-- {
  173. fileStrs[i].Close()
  174. }
  175. return nil, fmt.Errorf("donwloading file: %w", err)
  176. }
  177. fileStrs = append(fileStrs, str)
  178. }
  179. fileReaders, filesCloser := myio.ToReaders(fileStrs)
  180. var indexes []int
  181. for _, b := range chosenBlocks {
  182. indexes = append(indexes, b.Index)
  183. }
  184. outputs, outputsCloser := myio.ToReaders(rs.ReconstructData(fileReaders, indexes))
  185. return myio.AfterReadClosed(myio.Length(myio.ChunkedJoin(outputs, int(ecRed.ChunkSize)), obj.Object.Size), func(c io.ReadCloser) {
  186. filesCloser()
  187. outputsCloser()
  188. }), nil
  189. }
  190. func downloadFile(ctx *DownloadContext, node DownloadNodeInfo, fileHash string) (io.ReadCloser, error) {
  191. // 如果客户端与节点在同一个地域,则使用内网地址连接节点
  192. nodeIP := node.Node.ExternalIP
  193. grpcPort := node.Node.ExternalGRPCPort
  194. if node.IsSameLocation {
  195. nodeIP = node.Node.LocalIP
  196. grpcPort = node.Node.LocalGRPCPort
  197. logger.Infof("client and node %d are at the same location, use local ip", node.Node.NodeID)
  198. }
  199. if stgglb.IPFSPool != nil {
  200. logger.Infof("try to use local IPFS to download file")
  201. reader, err := downloadFromLocalIPFS(ctx, fileHash)
  202. if err == nil {
  203. return reader, nil
  204. }
  205. logger.Warnf("download from local IPFS failed, so try to download from node %s, err: %s", nodeIP, err.Error())
  206. }
  207. return downloadFromNode(ctx, node.Node.NodeID, nodeIP, grpcPort, fileHash)
  208. }
  209. func downloadFromNode(ctx *DownloadContext, nodeID cdssdk.NodeID, nodeIP string, grpcPort int, fileHash string) (io.ReadCloser, error) {
  210. agtCli, err := stgglb.AgentRPCPool.Acquire(nodeIP, grpcPort)
  211. if err != nil {
  212. return nil, fmt.Errorf("new agent grpc client: %w", err)
  213. }
  214. reader, err := agtCli.GetIPFSFile(fileHash)
  215. if err != nil {
  216. return nil, fmt.Errorf("getting ipfs file: %w", err)
  217. }
  218. reader = myio.AfterReadClosed(reader, func(io.ReadCloser) {
  219. agtCli.Close()
  220. })
  221. return reader, nil
  222. }
  223. func downloadFromLocalIPFS(ctx *DownloadContext, fileHash string) (io.ReadCloser, error) {
  224. ipfsCli, err := stgglb.IPFSPool.Acquire()
  225. if err != nil {
  226. return nil, fmt.Errorf("new ipfs client: %w", err)
  227. }
  228. reader, err := ipfsCli.OpenRead(fileHash)
  229. if err != nil {
  230. return nil, fmt.Errorf("read ipfs file failed, err: %w", err)
  231. }
  232. reader = myio.AfterReadClosed(reader, func(io.ReadCloser) {
  233. ipfsCli.Close()
  234. })
  235. return reader, nil
  236. }

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