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.

uploader.go 8.7 kB

7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package uploader
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "math"
  7. "math/rand"
  8. "time"
  9. "github.com/samber/lo"
  10. "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
  11. "gitlink.org.cn/cloudream/common/utils/sort2"
  12. "gitlink.org.cn/cloudream/scheduler/common/pkgs/db"
  13. cdssdk "gitlink.org.cn/cloudream/storage2/client/types"
  14. stgglb "gitlink.org.cn/cloudream/storage2/common/globals"
  15. stgmod "gitlink.org.cn/cloudream/storage2/common/models"
  16. "gitlink.org.cn/cloudream/storage2/common/pkgs/connectivity"
  17. "gitlink.org.cn/cloudream/storage2/common/pkgs/distlock"
  18. "gitlink.org.cn/cloudream/storage2/common/pkgs/distlock/reqbuilder"
  19. "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitch2"
  20. "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitch2/ops2"
  21. "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitch2/parser"
  22. "gitlink.org.cn/cloudream/storage2/common/pkgs/metacache"
  23. coormq "gitlink.org.cn/cloudream/storage2/common/pkgs/mq/coordinator"
  24. "gitlink.org.cn/cloudream/storage2/common/pkgs/storage/agtpool"
  25. "gitlink.org.cn/cloudream/storage2/common/pkgs/storage/factory"
  26. )
  27. type Uploader struct {
  28. distlock *distlock.Service
  29. connectivity *connectivity.Collector
  30. stgAgts *agtpool.AgentPool
  31. stgMeta *metacache.UserSpaceMeta
  32. db *db.DB
  33. }
  34. func NewUploader(distlock *distlock.Service, connectivity *connectivity.Collector, stgAgts *agtpool.AgentPool, stgMeta *metacache.UserSpaceMeta, db *db.DB) *Uploader {
  35. return &Uploader{
  36. distlock: distlock,
  37. connectivity: connectivity,
  38. stgAgts: stgAgts,
  39. stgMeta: stgMeta,
  40. }
  41. }
  42. func (u *Uploader) BeginUpdate(pkgID cdssdk.PackageID, affinity cdssdk.UserSpaceID, loadTo []cdssdk.UserSpaceID, loadToPath []string) (*UpdateUploader, 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. getUserStgsResp, err := coorCli.GetUserStorageDetails(coormq.ReqGetUserStorageDetails(userID))
  49. if err != nil {
  50. return nil, fmt.Errorf("getting user storages: %w", err)
  51. }
  52. cons := u.connectivity.GetAll()
  53. var userStgs []UploadStorageInfo
  54. for _, stg := range getUserStgsResp.Storages {
  55. if stg.MasterHub == nil {
  56. continue
  57. }
  58. delay := time.Duration(math.MaxInt64)
  59. con, ok := cons[stg.MasterHub.HubID]
  60. if ok && con.Latency != nil {
  61. delay = *con.Latency
  62. }
  63. userStgs = append(userStgs, UploadStorageInfo{
  64. Storage: stg,
  65. Delay: delay,
  66. IsSameLocation: stg.MasterHub.LocationID == stgglb.Local.LocationID,
  67. })
  68. }
  69. if len(userStgs) == 0 {
  70. return nil, fmt.Errorf("user no available storages")
  71. }
  72. loadToStgs := make([]stgmod.StorageDetail, len(loadTo))
  73. for i, stgID := range loadTo {
  74. stg, ok := lo.Find(getUserStgsResp.Storages, func(stg stgmod.StorageDetail) bool {
  75. return stg.Storage.StorageID == stgID
  76. })
  77. if !ok {
  78. return nil, fmt.Errorf("load to storage %v not found", stgID)
  79. }
  80. if stg.MasterHub == nil {
  81. return nil, fmt.Errorf("load to storage %v has no master hub", stgID)
  82. }
  83. if !factory.GetBuilder(stg).PublicStoreDesc().Enabled() {
  84. return nil, fmt.Errorf("load to storage %v has no public store", stgID)
  85. }
  86. loadToStgs[i] = stg
  87. }
  88. target := u.chooseUploadStorage(userStgs, affinity)
  89. // 给上传节点的IPFS加锁
  90. // TODO 考虑加Object的Create锁
  91. // 防止上传的副本被清除
  92. distMutex, err := reqbuilder.NewBuilder().Shard().Buzy(target.Storage.Storage.StorageID).MutexLock(u.distlock)
  93. if err != nil {
  94. return nil, fmt.Errorf("acquire distlock: %w", err)
  95. }
  96. return &UpdateUploader{
  97. uploader: u,
  98. pkgID: pkgID,
  99. targetStg: target.Storage,
  100. distMutex: distMutex,
  101. loadToStgs: loadToStgs,
  102. loadToPath: loadToPath,
  103. }, nil
  104. }
  105. // chooseUploadStorage 选择一个上传文件的节点
  106. // 1. 选择设置了亲和性的节点
  107. // 2. 从与当前客户端相同地域的节点中随机选一个
  108. // 3. 没有的话从所有节点选择延迟最低的节点
  109. func (w *Uploader) chooseUploadStorage(storages []UploadStorageInfo, stgAffinity cdssdk.StorageID) UploadStorageInfo {
  110. if stgAffinity > 0 {
  111. aff, ok := lo.Find(storages, func(storage UploadStorageInfo) bool { return storage.Storage.Storage.StorageID == stgAffinity })
  112. if ok {
  113. return aff
  114. }
  115. }
  116. sameLocationStorages := lo.Filter(storages, func(e UploadStorageInfo, i int) bool { return e.IsSameLocation })
  117. if len(sameLocationStorages) > 0 {
  118. return sameLocationStorages[rand.Intn(len(sameLocationStorages))]
  119. }
  120. // 选择延迟最低的节点
  121. storages = sort2.Sort(storages, func(e1, e2 UploadStorageInfo) int { return sort2.Cmp(e1.Delay, e2.Delay) })
  122. return storages[0]
  123. }
  124. func (u *Uploader) BeginCreateLoad(userID cdssdk.UserID, bktID cdssdk.BucketID, pkgName string, loadTo []cdssdk.StorageID, loadToPath []string) (*CreateLoadUploader, error) {
  125. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  126. if err != nil {
  127. return nil, fmt.Errorf("new coordinator client: %w", err)
  128. }
  129. defer stgglb.CoordinatorMQPool.Release(coorCli)
  130. getStgs := u.stgMeta.GetMany(loadTo)
  131. targetStgs := make([]stgmod.StorageDetail, len(loadTo))
  132. for i, stg := range getStgs {
  133. if stg == nil {
  134. return nil, fmt.Errorf("storage %v not found", loadTo[i])
  135. }
  136. targetStgs[i] = *stg
  137. }
  138. createPkg, err := coorCli.CreatePackage(coormq.NewCreatePackage(userID, bktID, pkgName))
  139. if err != nil {
  140. return nil, fmt.Errorf("create package: %w", err)
  141. }
  142. reqBld := reqbuilder.NewBuilder()
  143. for _, stg := range targetStgs {
  144. reqBld.Shard().Buzy(stg.Storage.StorageID)
  145. reqBld.Storage().Buzy(stg.Storage.StorageID)
  146. }
  147. lock, err := reqBld.MutexLock(u.distlock)
  148. if err != nil {
  149. return nil, fmt.Errorf("acquire distlock: %w", err)
  150. }
  151. return &CreateLoadUploader{
  152. pkg: createPkg.Package,
  153. userID: userID,
  154. targetSpaces: targetStgs,
  155. loadRoots: loadToPath,
  156. uploader: u,
  157. distlock: lock,
  158. }, nil
  159. }
  160. func (u *Uploader) UploadPart(userID cdssdk.UserID, objID cdssdk.ObjectID, index int, stream io.Reader) error {
  161. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  162. if err != nil {
  163. return fmt.Errorf("new coordinator client: %w", err)
  164. }
  165. defer stgglb.CoordinatorMQPool.Release(coorCli)
  166. details, err := coorCli.GetObjectDetails(coormq.ReqGetObjectDetails([]cdssdk.ObjectID{objID}))
  167. if err != nil {
  168. return err
  169. }
  170. if details.Objects[0] == nil {
  171. return fmt.Errorf("object %v not found", objID)
  172. }
  173. objDe := details.Objects[0]
  174. _, ok := objDe.Object.Redundancy.(*cdssdk.MultipartUploadRedundancy)
  175. if !ok {
  176. return fmt.Errorf("object %v is not a multipart upload", objID)
  177. }
  178. var stg stgmod.StorageDetail
  179. if len(objDe.Blocks) > 0 {
  180. cstg := u.stgMeta.Get(objDe.Blocks[0].StorageID)
  181. if cstg == nil {
  182. return fmt.Errorf("storage %v not found", objDe.Blocks[0].StorageID)
  183. }
  184. stg = *cstg
  185. } else {
  186. getUserStgsResp, err := coorCli.GetUserStorageDetails(coormq.ReqGetUserStorageDetails(userID))
  187. if err != nil {
  188. return fmt.Errorf("getting user storages: %w", err)
  189. }
  190. cons := u.connectivity.GetAll()
  191. var userStgs []UploadStorageInfo
  192. for _, stg := range getUserStgsResp.Storages {
  193. if stg.MasterHub == nil {
  194. continue
  195. }
  196. delay := time.Duration(math.MaxInt64)
  197. con, ok := cons[stg.MasterHub.HubID]
  198. if ok && con.Latency != nil {
  199. delay = *con.Latency
  200. }
  201. userStgs = append(userStgs, UploadStorageInfo{
  202. Storage: stg,
  203. Delay: delay,
  204. IsSameLocation: stg.MasterHub.LocationID == stgglb.Local.LocationID,
  205. })
  206. }
  207. if len(userStgs) == 0 {
  208. return fmt.Errorf("user no available storages")
  209. }
  210. stg = u.chooseUploadStorage(userStgs, 0).Storage
  211. }
  212. lock, err := reqbuilder.NewBuilder().Shard().Buzy(stg.Storage.StorageID).MutexLock(u.distlock)
  213. if err != nil {
  214. return fmt.Errorf("acquire distlock: %w", err)
  215. }
  216. defer lock.Unlock()
  217. ft := ioswitch2.NewFromTo()
  218. fromDrv, hd := ioswitch2.NewFromDriver(ioswitch2.RawStream())
  219. ft.AddFrom(fromDrv).
  220. AddTo(ioswitch2.NewToShardStore(*stg.MasterHub, stg, ioswitch2.RawStream(), "shard"))
  221. plans := exec.NewPlanBuilder()
  222. err = parser.Parse(ft, plans)
  223. if err != nil {
  224. return fmt.Errorf("parse fromto: %w", err)
  225. }
  226. exeCtx := exec.NewExecContext()
  227. exec.SetValueByType(exeCtx, u.stgAgts)
  228. exec := plans.Execute(exeCtx)
  229. exec.BeginWrite(io.NopCloser(stream), hd)
  230. ret, err := exec.Wait(context.TODO())
  231. if err != nil {
  232. return fmt.Errorf("executing plan: %w", err)
  233. }
  234. shardInfo := ret["shard"].(*ops2.ShardInfoValue)
  235. _, err = coorCli.AddMultipartUploadPart(coormq.ReqAddMultipartUploadPart(userID, objID, stgmod.ObjectBlock{
  236. ObjectID: objID,
  237. Index: index,
  238. StorageID: stg.Storage.StorageID,
  239. FileHash: shardInfo.Hash,
  240. Size: shardInfo.Size,
  241. }))
  242. return err
  243. }

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