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.

check_package_redundancy.go 34 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. package event
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "time"
  7. "github.com/samber/lo"
  8. "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
  9. "gitlink.org.cn/cloudream/common/pkgs/logger"
  10. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  11. "gitlink.org.cn/cloudream/common/utils/sort2"
  12. stgglb "gitlink.org.cn/cloudream/storage/common/globals"
  13. stgmod "gitlink.org.cn/cloudream/storage/common/models"
  14. "gitlink.org.cn/cloudream/storage/common/pkgs/distlock/reqbuilder"
  15. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch2"
  16. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch2/parser"
  17. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitchlrc"
  18. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitchlrc/ops2"
  19. lrcparser "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitchlrc/parser"
  20. coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
  21. scevt "gitlink.org.cn/cloudream/storage/common/pkgs/mq/scanner/event"
  22. "gitlink.org.cn/cloudream/storage/scanner/internal/config"
  23. )
  24. const (
  25. monthHours = 30 * 24
  26. yearHours = 365 * 24
  27. )
  28. type CheckPackageRedundancy struct {
  29. *scevt.CheckPackageRedundancy
  30. }
  31. func NewCheckPackageRedundancy(evt *scevt.CheckPackageRedundancy) *CheckPackageRedundancy {
  32. return &CheckPackageRedundancy{
  33. CheckPackageRedundancy: evt,
  34. }
  35. }
  36. type StorageLoadInfo struct {
  37. Storage stgmod.StorageDetail
  38. AccessAmount float64
  39. }
  40. func (t *CheckPackageRedundancy) TryMerge(other Event) bool {
  41. event, ok := other.(*CheckPackageRedundancy)
  42. if !ok {
  43. return false
  44. }
  45. return event.PackageID == t.PackageID
  46. }
  47. func (t *CheckPackageRedundancy) Execute(execCtx ExecuteContext) {
  48. log := logger.WithType[CheckPackageRedundancy]("Event")
  49. startTime := time.Now()
  50. log.Debugf("begin with %v", logger.FormatStruct(t.CheckPackageRedundancy))
  51. defer func() {
  52. log.Debugf("end, time: %v", time.Since(startTime))
  53. }()
  54. // TODO 应该像其他event一样直接读取数据库
  55. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  56. if err != nil {
  57. log.Warnf("new coordinator client: %s", err.Error())
  58. return
  59. }
  60. defer stgglb.CoordinatorMQPool.Release(coorCli)
  61. getObjs, err := coorCli.GetPackageObjectDetails(coormq.ReqGetPackageObjectDetails(t.PackageID))
  62. if err != nil {
  63. log.Warnf("getting package objects: %s", err.Error())
  64. return
  65. }
  66. stats, err := execCtx.Args.DB.PackageAccessStat().GetByPackageID(execCtx.Args.DB.DefCtx(), t.PackageID)
  67. if err != nil {
  68. log.Warnf("getting package access stats: %s", err.Error())
  69. return
  70. }
  71. // TODO UserID
  72. getStgs, err := coorCli.GetUserStorageDetails(coormq.ReqGetUserStorageDetails(1))
  73. if err != nil {
  74. log.Warnf("getting all nodes: %s", err.Error())
  75. return
  76. }
  77. if len(getStgs.Storages) == 0 {
  78. log.Warnf("no available nodes")
  79. return
  80. }
  81. userAllStorages := make(map[cdssdk.StorageID]*StorageLoadInfo)
  82. for _, stg := range getStgs.Storages {
  83. userAllStorages[stg.Storage.StorageID] = &StorageLoadInfo{
  84. Storage: stg,
  85. }
  86. }
  87. for _, stat := range stats {
  88. info, ok := userAllStorages[stat.StorageID]
  89. if !ok {
  90. continue
  91. }
  92. info.AccessAmount = stat.Amount
  93. }
  94. var changedObjects []coormq.UpdatingObjectRedundancy
  95. defRep := cdssdk.DefaultRepRedundancy
  96. defEC := cdssdk.DefaultECRedundancy
  97. // TODO 目前rep的备份数量固定为2,所以这里直接选出两个节点
  98. // TODO 放到chooseRedundancy函数中
  99. mostBlockStgIDs := t.summaryRepObjectBlockNodes(getObjs.Objects, 2)
  100. newRepStgs := t.chooseNewNodesForRep(&defRep, userAllStorages)
  101. rechoosedRepStgs := t.rechooseNodesForRep(mostBlockStgIDs, &defRep, userAllStorages)
  102. newECStgs := t.chooseNewNodesForEC(&defEC, userAllStorages)
  103. // 加锁
  104. builder := reqbuilder.NewBuilder()
  105. for _, node := range newRepStgs {
  106. builder.Shard().Buzy(node.Storage.Storage.StorageID)
  107. }
  108. for _, node := range newECStgs {
  109. builder.Shard().Buzy(node.Storage.Storage.StorageID)
  110. }
  111. mutex, err := builder.MutexLock(execCtx.Args.DistLock)
  112. if err != nil {
  113. log.Warnf("acquiring dist lock: %s", err.Error())
  114. return
  115. }
  116. defer mutex.Unlock()
  117. for _, obj := range getObjs.Objects {
  118. var updating *coormq.UpdatingObjectRedundancy
  119. var err error
  120. newRed, selectedNodes := t.chooseRedundancy(obj, userAllStorages)
  121. switch srcRed := obj.Object.Redundancy.(type) {
  122. case *cdssdk.NoneRedundancy:
  123. switch newRed := newRed.(type) {
  124. case *cdssdk.RepRedundancy:
  125. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> rep")
  126. updating, err = t.noneToRep(obj, newRed, newRepStgs)
  127. case *cdssdk.ECRedundancy:
  128. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> ec")
  129. updating, err = t.noneToEC(obj, newRed, newECStgs)
  130. case *cdssdk.LRCRedundancy:
  131. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> lrc")
  132. updating, err = t.noneToLRC(obj, newRed, selectedNodes)
  133. }
  134. case *cdssdk.RepRedundancy:
  135. switch newRed := newRed.(type) {
  136. case *cdssdk.RepRedundancy:
  137. updating, err = t.repToRep(obj, srcRed, rechoosedRepStgs)
  138. case *cdssdk.ECRedundancy:
  139. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: rep -> ec")
  140. updating, err = t.repToEC(obj, newRed, newECStgs)
  141. }
  142. case *cdssdk.ECRedundancy:
  143. switch newRed := newRed.(type) {
  144. case *cdssdk.RepRedundancy:
  145. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: ec -> rep")
  146. updating, err = t.ecToRep(obj, srcRed, newRed, newRepStgs)
  147. case *cdssdk.ECRedundancy:
  148. uploadNodes := t.rechooseNodesForEC(obj, srcRed, userAllStorages)
  149. updating, err = t.ecToEC(obj, srcRed, newRed, uploadNodes)
  150. }
  151. case *cdssdk.LRCRedundancy:
  152. switch newRed := newRed.(type) {
  153. case *cdssdk.LRCRedundancy:
  154. uploadNodes := t.rechooseNodesForLRC(obj, srcRed, userAllStorages)
  155. updating, err = t.lrcToLRC(obj, srcRed, newRed, uploadNodes)
  156. }
  157. }
  158. if updating != nil {
  159. changedObjects = append(changedObjects, *updating)
  160. }
  161. if err != nil {
  162. log.WithField("ObjectID", obj.Object.ObjectID).Warnf("%s, its redundancy wont be changed", err.Error())
  163. }
  164. }
  165. if len(changedObjects) == 0 {
  166. return
  167. }
  168. _, err = coorCli.UpdateObjectRedundancy(coormq.ReqUpdateObjectRedundancy(changedObjects))
  169. if err != nil {
  170. log.Warnf("requesting to change object redundancy: %s", err.Error())
  171. return
  172. }
  173. }
  174. func (t *CheckPackageRedundancy) chooseRedundancy(obj stgmod.ObjectDetail, userAllStgs map[cdssdk.StorageID]*StorageLoadInfo) (cdssdk.Redundancy, []*StorageLoadInfo) {
  175. switch obj.Object.Redundancy.(type) {
  176. case *cdssdk.NoneRedundancy:
  177. newStgs := t.chooseNewNodesForEC(&cdssdk.DefaultECRedundancy, userAllStgs)
  178. return &cdssdk.DefaultECRedundancy, newStgs
  179. // newLRCNodes := t.chooseNewNodesForLRC(&cdssdk.DefaultLRCRedundancy, userAllNodes)
  180. // return &cdssdk.DefaultLRCRedundancy, newLRCNodes
  181. case *cdssdk.LRCRedundancy:
  182. newLRCStgs := t.rechooseNodesForLRC(obj, &cdssdk.DefaultLRCRedundancy, userAllStgs)
  183. return &cdssdk.DefaultLRCRedundancy, newLRCStgs
  184. }
  185. return nil, nil
  186. }
  187. // 统计每个对象块所在的节点,选出块最多的不超过nodeCnt个节点
  188. func (t *CheckPackageRedundancy) summaryRepObjectBlockNodes(objs []stgmod.ObjectDetail, nodeCnt int) []cdssdk.StorageID {
  189. type stgBlocks struct {
  190. StorageID cdssdk.StorageID
  191. Count int
  192. }
  193. stgBlocksMap := make(map[cdssdk.StorageID]*stgBlocks)
  194. for _, obj := range objs {
  195. shouldUseEC := obj.Object.Size > config.Cfg().ECFileSizeThreshold
  196. if _, ok := obj.Object.Redundancy.(*cdssdk.RepRedundancy); ok && !shouldUseEC {
  197. for _, block := range obj.Blocks {
  198. if _, ok := stgBlocksMap[block.StorageID]; !ok {
  199. stgBlocksMap[block.StorageID] = &stgBlocks{
  200. StorageID: block.StorageID,
  201. Count: 0,
  202. }
  203. }
  204. stgBlocksMap[block.StorageID].Count++
  205. }
  206. }
  207. }
  208. nodes := lo.Values(stgBlocksMap)
  209. sort2.Sort(nodes, func(left *stgBlocks, right *stgBlocks) int {
  210. return right.Count - left.Count
  211. })
  212. ids := lo.Map(nodes, func(item *stgBlocks, idx int) cdssdk.StorageID { return item.StorageID })
  213. if len(ids) > nodeCnt {
  214. ids = ids[:nodeCnt]
  215. }
  216. return ids
  217. }
  218. func (t *CheckPackageRedundancy) chooseNewNodesForRep(red *cdssdk.RepRedundancy, allStgs map[cdssdk.StorageID]*StorageLoadInfo) []*StorageLoadInfo {
  219. sortedNodes := sort2.Sort(lo.Values(allStgs), func(left *StorageLoadInfo, right *StorageLoadInfo) int {
  220. return sort2.Cmp(right.AccessAmount, left.AccessAmount)
  221. })
  222. return t.chooseSoManyNodes(red.RepCount, sortedNodes)
  223. }
  224. func (t *CheckPackageRedundancy) chooseNewNodesForEC(red *cdssdk.ECRedundancy, allStgs map[cdssdk.StorageID]*StorageLoadInfo) []*StorageLoadInfo {
  225. sortedNodes := sort2.Sort(lo.Values(allStgs), func(left *StorageLoadInfo, right *StorageLoadInfo) int {
  226. return sort2.Cmp(right.AccessAmount, left.AccessAmount)
  227. })
  228. return t.chooseSoManyNodes(red.N, sortedNodes)
  229. }
  230. func (t *CheckPackageRedundancy) chooseNewNodesForLRC(red *cdssdk.LRCRedundancy, allNodes map[cdssdk.NodeID]*StorageLoadInfo) []*StorageLoadInfo {
  231. sortedNodes := sort2.Sort(lo.Values(allNodes), func(left *StorageLoadInfo, right *StorageLoadInfo) int {
  232. return sort2.Cmp(right.AccessAmount, left.AccessAmount)
  233. })
  234. return t.chooseSoManyNodes(red.N, sortedNodes)
  235. }
  236. func (t *CheckPackageRedundancy) rechooseNodesForRep(mostBlockStgIDs []cdssdk.StorageID, red *cdssdk.RepRedundancy, allStgs map[cdssdk.StorageID]*StorageLoadInfo) []*StorageLoadInfo {
  237. type rechooseNode struct {
  238. *StorageLoadInfo
  239. HasBlock bool
  240. }
  241. var rechooseStgs []*rechooseNode
  242. for _, stg := range allStgs {
  243. hasBlock := false
  244. for _, id := range mostBlockStgIDs {
  245. if id == stg.Storage.Storage.StorageID {
  246. hasBlock = true
  247. break
  248. }
  249. }
  250. rechooseStgs = append(rechooseStgs, &rechooseNode{
  251. StorageLoadInfo: stg,
  252. HasBlock: hasBlock,
  253. })
  254. }
  255. sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseNode, right *rechooseNode) int {
  256. // 已经缓存了文件块的节点优先选择
  257. v := sort2.CmpBool(right.HasBlock, left.HasBlock)
  258. if v != 0 {
  259. return v
  260. }
  261. return sort2.Cmp(right.AccessAmount, left.AccessAmount)
  262. })
  263. return t.chooseSoManyNodes(red.RepCount, lo.Map(sortedStgs, func(node *rechooseNode, idx int) *StorageLoadInfo { return node.StorageLoadInfo }))
  264. }
  265. func (t *CheckPackageRedundancy) rechooseNodesForEC(obj stgmod.ObjectDetail, red *cdssdk.ECRedundancy, allStgs map[cdssdk.StorageID]*StorageLoadInfo) []*StorageLoadInfo {
  266. type rechooseStg struct {
  267. *StorageLoadInfo
  268. CachedBlockIndex int
  269. }
  270. var rechooseStgs []*rechooseStg
  271. for _, stg := range allStgs {
  272. cachedBlockIndex := -1
  273. for _, block := range obj.Blocks {
  274. if block.StorageID == stg.Storage.Storage.StorageID {
  275. cachedBlockIndex = block.Index
  276. break
  277. }
  278. }
  279. rechooseStgs = append(rechooseStgs, &rechooseStg{
  280. StorageLoadInfo: stg,
  281. CachedBlockIndex: cachedBlockIndex,
  282. })
  283. }
  284. sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseStg, right *rechooseStg) int {
  285. // 已经缓存了文件块的节点优先选择
  286. v := sort2.CmpBool(right.CachedBlockIndex > -1, left.CachedBlockIndex > -1)
  287. if v != 0 {
  288. return v
  289. }
  290. return sort2.Cmp(right.AccessAmount, left.AccessAmount)
  291. })
  292. // TODO 可以考虑选择已有块的节点时,能依然按照Index顺序选择
  293. return t.chooseSoManyNodes(red.N, lo.Map(sortedStgs, func(node *rechooseStg, idx int) *StorageLoadInfo { return node.StorageLoadInfo }))
  294. }
  295. func (t *CheckPackageRedundancy) rechooseNodesForLRC(obj stgmod.ObjectDetail, red *cdssdk.LRCRedundancy, allStgs map[cdssdk.StorageID]*StorageLoadInfo) []*StorageLoadInfo {
  296. type rechooseStg struct {
  297. *StorageLoadInfo
  298. CachedBlockIndex int
  299. }
  300. var rechooseStgs []*rechooseStg
  301. for _, stg := range allStgs {
  302. cachedBlockIndex := -1
  303. for _, block := range obj.Blocks {
  304. if block.StorageID == stg.Storage.Storage.StorageID {
  305. cachedBlockIndex = block.Index
  306. break
  307. }
  308. }
  309. rechooseStgs = append(rechooseStgs, &rechooseStg{
  310. StorageLoadInfo: stg,
  311. CachedBlockIndex: cachedBlockIndex,
  312. })
  313. }
  314. sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseStg, right *rechooseStg) int {
  315. // 已经缓存了文件块的节点优先选择
  316. v := sort2.CmpBool(right.CachedBlockIndex > -1, left.CachedBlockIndex > -1)
  317. if v != 0 {
  318. return v
  319. }
  320. return sort2.Cmp(right.AccessAmount, left.AccessAmount)
  321. })
  322. // TODO 可以考虑选择已有块的节点时,能依然按照Index顺序选择
  323. return t.chooseSoManyNodes(red.N, lo.Map(sortedStgs, func(node *rechooseStg, idx int) *StorageLoadInfo { return node.StorageLoadInfo }))
  324. }
  325. func (t *CheckPackageRedundancy) chooseSoManyNodes(count int, stgs []*StorageLoadInfo) []*StorageLoadInfo {
  326. repeateCount := (count + len(stgs) - 1) / len(stgs)
  327. extendStgs := make([]*StorageLoadInfo, repeateCount*len(stgs))
  328. // 使用复制的方式将节点数扩充到要求的数量
  329. // 复制之后的结构:ABCD -> AAABBBCCCDDD
  330. for p := 0; p < repeateCount; p++ {
  331. for i, node := range stgs {
  332. putIdx := i*repeateCount + p
  333. extendStgs[putIdx] = node
  334. }
  335. }
  336. extendStgs = extendStgs[:count]
  337. var chosen []*StorageLoadInfo
  338. for len(chosen) < count {
  339. // 在每一轮内都选不同地区的节点,如果节点数不够,那么就再来一轮
  340. chosenLocations := make(map[cdssdk.LocationID]bool)
  341. for i, stg := range extendStgs {
  342. if stg == nil {
  343. continue
  344. }
  345. if chosenLocations[stg.Storage.MasterHub.LocationID] {
  346. continue
  347. }
  348. chosen = append(chosen, stg)
  349. chosenLocations[stg.Storage.MasterHub.LocationID] = true
  350. extendStgs[i] = nil
  351. }
  352. }
  353. return chosen
  354. }
  355. func (t *CheckPackageRedundancy) noneToRep(obj stgmod.ObjectDetail, red *cdssdk.RepRedundancy, uploadStgs []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  356. if len(obj.Blocks) == 0 {
  357. return nil, fmt.Errorf("object is not cached on any nodes, cannot change its redundancy to rep")
  358. }
  359. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  360. if err != nil {
  361. return nil, fmt.Errorf("new coordinator client: %w", err)
  362. }
  363. defer stgglb.CoordinatorMQPool.Release(coorCli)
  364. getStgs, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{obj.Blocks[0].StorageID}))
  365. if err != nil {
  366. return nil, fmt.Errorf("requesting to get storages: %w", err)
  367. }
  368. if getStgs.Storages[0] == nil {
  369. return nil, fmt.Errorf("storage %v not found", obj.Blocks[0].StorageID)
  370. }
  371. if getStgs.Storages[0].MasterHub == nil {
  372. return nil, fmt.Errorf("storage %v has no master hub", obj.Blocks[0].StorageID)
  373. }
  374. // 如果选择的备份节点都是同一个,那么就只要上传一次
  375. uploadStgs = lo.UniqBy(uploadStgs, func(item *StorageLoadInfo) cdssdk.StorageID { return item.Storage.Storage.StorageID })
  376. ft := ioswitch2.NewFromTo()
  377. ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *getStgs.Storages[0].MasterHub, getStgs.Storages[0].Storage, -1))
  378. for i, stg := range uploadStgs {
  379. ft.AddTo(ioswitch2.NewToShardStore(*stg.Storage.MasterHub, stg.Storage.Storage, -1, fmt.Sprintf("%d", i)))
  380. }
  381. plans := exec.NewPlanBuilder()
  382. parser := parser.NewParser(cdssdk.DefaultECRedundancy)
  383. err = parser.Parse(ft, plans)
  384. if err != nil {
  385. return nil, fmt.Errorf("parsing plan: %w", err)
  386. }
  387. // TODO 添加依赖
  388. ret, err := plans.Execute(exec.NewExecContext()).Wait(context.Background())
  389. if err != nil {
  390. return nil, fmt.Errorf("executing io plan: %w", err)
  391. }
  392. var blocks []stgmod.ObjectBlock
  393. for i, stg := range uploadStgs {
  394. blocks = append(blocks, stgmod.ObjectBlock{
  395. ObjectID: obj.Object.ObjectID,
  396. Index: 0,
  397. StorageID: stg.Storage.Storage.StorageID,
  398. FileHash: ret[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash,
  399. })
  400. }
  401. return &coormq.UpdatingObjectRedundancy{
  402. ObjectID: obj.Object.ObjectID,
  403. Redundancy: red,
  404. Blocks: blocks,
  405. }, nil
  406. }
  407. func (t *CheckPackageRedundancy) noneToEC(obj stgmod.ObjectDetail, red *cdssdk.ECRedundancy, uploadStgs []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  408. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  409. if err != nil {
  410. return nil, fmt.Errorf("new coordinator client: %w", err)
  411. }
  412. defer stgglb.CoordinatorMQPool.Release(coorCli)
  413. if len(obj.Blocks) == 0 {
  414. return nil, fmt.Errorf("object is not cached on any nodes, cannot change its redundancy to ec")
  415. }
  416. getStgs, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{obj.Blocks[0].StorageID}))
  417. if err != nil {
  418. return nil, fmt.Errorf("requesting to get storages: %w", err)
  419. }
  420. if getStgs.Storages[0] == nil {
  421. return nil, fmt.Errorf("storage %v not found", obj.Blocks[0].StorageID)
  422. }
  423. if getStgs.Storages[0].MasterHub == nil {
  424. return nil, fmt.Errorf("storage %v has no master hub", obj.Blocks[0].StorageID)
  425. }
  426. ft := ioswitch2.NewFromTo()
  427. ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *getStgs.Storages[0].MasterHub, getStgs.Storages[0].Storage, -1))
  428. for i := 0; i < red.N; i++ {
  429. ft.AddTo(ioswitch2.NewToShardStore(*uploadStgs[i].Storage.MasterHub, uploadStgs[i].Storage.Storage, i, fmt.Sprintf("%d", i)))
  430. }
  431. parser := parser.NewParser(*red)
  432. plans := exec.NewPlanBuilder()
  433. err = parser.Parse(ft, plans)
  434. if err != nil {
  435. return nil, fmt.Errorf("parsing plan: %w", err)
  436. }
  437. // TODO 添加依赖
  438. ioRet, err := plans.Execute(exec.NewExecContext()).Wait(context.TODO())
  439. if err != nil {
  440. return nil, fmt.Errorf("executing io plan: %w", err)
  441. }
  442. var blocks []stgmod.ObjectBlock
  443. for i := 0; i < red.N; i++ {
  444. blocks = append(blocks, stgmod.ObjectBlock{
  445. ObjectID: obj.Object.ObjectID,
  446. Index: i,
  447. StorageID: uploadStgs[i].Storage.Storage.StorageID,
  448. FileHash: ioRet[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash,
  449. })
  450. }
  451. return &coormq.UpdatingObjectRedundancy{
  452. ObjectID: obj.Object.ObjectID,
  453. Redundancy: red,
  454. Blocks: blocks,
  455. }, nil
  456. }
  457. func (t *CheckPackageRedundancy) noneToLRC(obj stgmod.ObjectDetail, red *cdssdk.LRCRedundancy, uploadNodes []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  458. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  459. if err != nil {
  460. return nil, fmt.Errorf("new coordinator client: %w", err)
  461. }
  462. defer stgglb.CoordinatorMQPool.Release(coorCli)
  463. if len(obj.Blocks) == 0 {
  464. return nil, fmt.Errorf("object is not cached on any nodes, cannot change its redundancy to ec")
  465. }
  466. getStgs, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{obj.Blocks[0].StorageID}))
  467. if err != nil {
  468. return nil, fmt.Errorf("requesting to get storages: %w", err)
  469. }
  470. if getStgs.Storages[0] == nil {
  471. return nil, fmt.Errorf("storage %v not found", obj.Blocks[0].StorageID)
  472. }
  473. if getStgs.Storages[0].MasterHub == nil {
  474. return nil, fmt.Errorf("storage %v has no master hub", obj.Blocks[0].StorageID)
  475. }
  476. var toes []ioswitchlrc.To
  477. for i := 0; i < red.N; i++ {
  478. toes = append(toes, ioswitchlrc.NewToStorage(*uploadNodes[i].Storage.MasterHub, uploadNodes[i].Storage.Storage, i, fmt.Sprintf("%d", i)))
  479. }
  480. plans := exec.NewPlanBuilder()
  481. err = lrcparser.Encode(ioswitchlrc.NewFromNode(obj.Object.FileHash, *getStgs.Storages[0].MasterHub, getStgs.Storages[0].Storage, -1), toes, plans)
  482. if err != nil {
  483. return nil, fmt.Errorf("parsing plan: %w", err)
  484. }
  485. // TODO 添加依赖
  486. ioRet, err := plans.Execute(exec.NewExecContext()).Wait(context.TODO())
  487. if err != nil {
  488. return nil, fmt.Errorf("executing io plan: %w", err)
  489. }
  490. var blocks []stgmod.ObjectBlock
  491. for i := 0; i < red.N; i++ {
  492. blocks = append(blocks, stgmod.ObjectBlock{
  493. ObjectID: obj.Object.ObjectID,
  494. Index: i,
  495. StorageID: uploadNodes[i].Storage.Storage.StorageID,
  496. FileHash: ioRet[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash,
  497. })
  498. }
  499. return &coormq.UpdatingObjectRedundancy{
  500. ObjectID: obj.Object.ObjectID,
  501. Redundancy: red,
  502. Blocks: blocks,
  503. }, nil
  504. }
  505. func (t *CheckPackageRedundancy) repToRep(obj stgmod.ObjectDetail, red *cdssdk.RepRedundancy, uploadStgs []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  506. if len(obj.Blocks) == 0 {
  507. return nil, fmt.Errorf("object is not cached on any nodes, cannot change its redundancy to rep")
  508. }
  509. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  510. if err != nil {
  511. return nil, fmt.Errorf("new coordinator client: %w", err)
  512. }
  513. defer stgglb.CoordinatorMQPool.Release(coorCli)
  514. getStgs, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{obj.Blocks[0].StorageID}))
  515. if err != nil {
  516. return nil, fmt.Errorf("requesting to get storages: %w", err)
  517. }
  518. if getStgs.Storages[0] == nil {
  519. return nil, fmt.Errorf("storage %v not found", obj.Blocks[0].StorageID)
  520. }
  521. if getStgs.Storages[0].MasterHub == nil {
  522. return nil, fmt.Errorf("storage %v has no master hub", obj.Blocks[0].StorageID)
  523. }
  524. // 如果选择的备份节点都是同一个,那么就只要上传一次
  525. uploadStgs = lo.UniqBy(uploadStgs, func(item *StorageLoadInfo) cdssdk.StorageID { return item.Storage.Storage.StorageID })
  526. ft := ioswitch2.NewFromTo()
  527. ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *getStgs.Storages[0].MasterHub, getStgs.Storages[0].Storage, -1))
  528. for i, stg := range uploadStgs {
  529. ft.AddTo(ioswitch2.NewToShardStore(*stg.Storage.MasterHub, stg.Storage.Storage, -1, fmt.Sprintf("%d", i)))
  530. }
  531. plans := exec.NewPlanBuilder()
  532. parser := parser.NewParser(cdssdk.DefaultECRedundancy)
  533. err = parser.Parse(ft, plans)
  534. if err != nil {
  535. return nil, fmt.Errorf("parsing plan: %w", err)
  536. }
  537. // TODO 添加依赖
  538. ret, err := plans.Execute(exec.NewExecContext()).Wait(context.Background())
  539. if err != nil {
  540. return nil, fmt.Errorf("executing io plan: %w", err)
  541. }
  542. var blocks []stgmod.ObjectBlock
  543. for i, stg := range uploadStgs {
  544. blocks = append(blocks, stgmod.ObjectBlock{
  545. ObjectID: obj.Object.ObjectID,
  546. Index: 0,
  547. StorageID: stg.Storage.Storage.StorageID,
  548. FileHash: ret[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash,
  549. })
  550. }
  551. return &coormq.UpdatingObjectRedundancy{
  552. ObjectID: obj.Object.ObjectID,
  553. Redundancy: red,
  554. Blocks: blocks,
  555. }, nil
  556. }
  557. func (t *CheckPackageRedundancy) repToEC(obj stgmod.ObjectDetail, red *cdssdk.ECRedundancy, uploadNodes []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  558. return t.noneToEC(obj, red, uploadNodes)
  559. }
  560. func (t *CheckPackageRedundancy) ecToRep(obj stgmod.ObjectDetail, srcRed *cdssdk.ECRedundancy, tarRed *cdssdk.RepRedundancy, uploadStgs []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  561. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  562. if err != nil {
  563. return nil, fmt.Errorf("new coordinator client: %w", err)
  564. }
  565. defer stgglb.CoordinatorMQPool.Release(coorCli)
  566. var chosenBlocks []stgmod.GrouppedObjectBlock
  567. var chosenBlockIndexes []int
  568. for _, block := range obj.GroupBlocks() {
  569. if len(block.StorageIDs) > 0 {
  570. chosenBlocks = append(chosenBlocks, block)
  571. chosenBlockIndexes = append(chosenBlockIndexes, block.Index)
  572. }
  573. if len(chosenBlocks) == srcRed.K {
  574. break
  575. }
  576. }
  577. if len(chosenBlocks) < srcRed.K {
  578. return nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
  579. }
  580. // 如果选择的备份节点都是同一个,那么就只要上传一次
  581. uploadStgs = lo.UniqBy(uploadStgs, func(item *StorageLoadInfo) cdssdk.StorageID { return item.Storage.Storage.StorageID })
  582. // 每个被选节点都在自己节点上重建原始数据
  583. parser := parser.NewParser(*srcRed)
  584. planBlder := exec.NewPlanBuilder()
  585. for i := range uploadStgs {
  586. ft := ioswitch2.NewFromTo()
  587. for _, block := range chosenBlocks {
  588. ft.AddFrom(ioswitch2.NewFromShardstore(block.FileHash, *uploadStgs[i].Storage.MasterHub, uploadStgs[i].Storage.Storage, block.Index))
  589. }
  590. len := obj.Object.Size
  591. ft.AddTo(ioswitch2.NewToShardStoreWithRange(*uploadStgs[i].Storage.MasterHub, uploadStgs[i].Storage.Storage, -1, fmt.Sprintf("%d", i), exec.Range{
  592. Offset: 0,
  593. Length: &len,
  594. }))
  595. err := parser.Parse(ft, planBlder)
  596. if err != nil {
  597. return nil, fmt.Errorf("parsing plan: %w", err)
  598. }
  599. }
  600. // TODO 添加依赖
  601. ioRet, err := planBlder.Execute(exec.NewExecContext()).Wait(context.TODO())
  602. if err != nil {
  603. return nil, fmt.Errorf("executing io plan: %w", err)
  604. }
  605. var blocks []stgmod.ObjectBlock
  606. for i := range uploadStgs {
  607. blocks = append(blocks, stgmod.ObjectBlock{
  608. ObjectID: obj.Object.ObjectID,
  609. Index: 0,
  610. StorageID: uploadStgs[i].Storage.Storage.StorageID,
  611. FileHash: ioRet[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash,
  612. })
  613. }
  614. return &coormq.UpdatingObjectRedundancy{
  615. ObjectID: obj.Object.ObjectID,
  616. Redundancy: tarRed,
  617. Blocks: blocks,
  618. }, nil
  619. }
  620. func (t *CheckPackageRedundancy) ecToEC(obj stgmod.ObjectDetail, srcRed *cdssdk.ECRedundancy, tarRed *cdssdk.ECRedundancy, uploadNodes []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  621. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  622. if err != nil {
  623. return nil, fmt.Errorf("new coordinator client: %w", err)
  624. }
  625. defer stgglb.CoordinatorMQPool.Release(coorCli)
  626. grpBlocks := obj.GroupBlocks()
  627. var chosenBlocks []stgmod.GrouppedObjectBlock
  628. for _, block := range grpBlocks {
  629. if len(block.StorageIDs) > 0 {
  630. chosenBlocks = append(chosenBlocks, block)
  631. }
  632. if len(chosenBlocks) == srcRed.K {
  633. break
  634. }
  635. }
  636. if len(chosenBlocks) < srcRed.K {
  637. return nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
  638. }
  639. // 目前EC的参数都相同,所以可以不用重建出完整数据然后再分块,可以直接构建出目的节点需要的块
  640. parser := parser.NewParser(*srcRed)
  641. planBlder := exec.NewPlanBuilder()
  642. var newBlocks []stgmod.ObjectBlock
  643. shouldUpdateBlocks := false
  644. for i, stg := range uploadNodes {
  645. newBlock := stgmod.ObjectBlock{
  646. ObjectID: obj.Object.ObjectID,
  647. Index: i,
  648. StorageID: stg.Storage.Storage.StorageID,
  649. }
  650. grp, ok := lo.Find(grpBlocks, func(grp stgmod.GrouppedObjectBlock) bool { return grp.Index == i })
  651. // 如果新选中的节点已经记录在Block表中,那么就不需要任何变更
  652. if ok && lo.Contains(grp.StorageIDs, stg.Storage.Storage.StorageID) {
  653. newBlock.FileHash = grp.FileHash
  654. newBlocks = append(newBlocks, newBlock)
  655. continue
  656. }
  657. shouldUpdateBlocks = true
  658. // 否则就要重建出这个节点需要的块
  659. ft := ioswitch2.NewFromTo()
  660. for _, block := range chosenBlocks {
  661. stg := stg.Storage
  662. ft.AddFrom(ioswitch2.NewFromShardstore(block.FileHash, *stg.MasterHub, stg.Storage, block.Index))
  663. }
  664. // 输出只需要自己要保存的那一块
  665. ft.AddTo(ioswitch2.NewToShardStore(*stg.Storage.MasterHub, stg.Storage.Storage, i, fmt.Sprintf("%d", i)))
  666. err := parser.Parse(ft, planBlder)
  667. if err != nil {
  668. return nil, fmt.Errorf("parsing plan: %w", err)
  669. }
  670. newBlocks = append(newBlocks, newBlock)
  671. }
  672. // 如果没有任何Plan,Wait会直接返回成功
  673. // TODO 添加依赖
  674. ret, err := planBlder.Execute(exec.NewExecContext()).Wait(context.TODO())
  675. if err != nil {
  676. return nil, fmt.Errorf("executing io plan: %w", err)
  677. }
  678. if !shouldUpdateBlocks {
  679. return nil, nil
  680. }
  681. for k, v := range ret {
  682. idx, err := strconv.ParseInt(k, 10, 64)
  683. if err != nil {
  684. return nil, fmt.Errorf("parsing result key %s as index: %w", k, err)
  685. }
  686. newBlocks[idx].FileHash = v.(*ops2.FileHashValue).Hash
  687. }
  688. return &coormq.UpdatingObjectRedundancy{
  689. ObjectID: obj.Object.ObjectID,
  690. Redundancy: tarRed,
  691. Blocks: newBlocks,
  692. }, nil
  693. }
  694. func (t *CheckPackageRedundancy) lrcToLRC(obj stgmod.ObjectDetail, srcRed *cdssdk.LRCRedundancy, tarRed *cdssdk.LRCRedundancy, uploadNodes []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  695. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  696. if err != nil {
  697. return nil, fmt.Errorf("new coordinator client: %w", err)
  698. }
  699. defer stgglb.CoordinatorMQPool.Release(coorCli)
  700. blocksGrpByIndex := obj.GroupBlocks()
  701. var lostBlocks []int
  702. var lostBlockGrps []int
  703. canGroupReconstruct := true
  704. allBlockFlags := make([]bool, srcRed.N)
  705. for _, block := range blocksGrpByIndex {
  706. allBlockFlags[block.Index] = true
  707. }
  708. for i, ok := range allBlockFlags {
  709. grpID := srcRed.FindGroup(i)
  710. if !ok {
  711. if grpID == -1 {
  712. canGroupReconstruct = false
  713. break
  714. }
  715. if len(lostBlocks) > 0 && lostBlockGrps[len(lostBlockGrps)-1] == grpID {
  716. canGroupReconstruct = false
  717. break
  718. }
  719. lostBlocks = append(lostBlocks, i)
  720. lostBlockGrps = append(lostBlockGrps, grpID)
  721. }
  722. }
  723. if canGroupReconstruct {
  724. // return t.groupReconstructLRC(obj, lostBlocks, lostBlockGrps, blocksGrpByIndex, srcRed, uploadNodes)
  725. }
  726. return t.reconstructLRC(obj, blocksGrpByIndex, srcRed, uploadNodes)
  727. }
  728. /*
  729. func (t *CheckPackageRedundancy) groupReconstructLRC(obj stgmod.ObjectDetail, lostBlocks []int, lostBlockGrps []int, grpedBlocks []stgmod.GrouppedObjectBlock, red *cdssdk.LRCRedundancy, uploadNodes []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  730. grped := make(map[int]stgmod.GrouppedObjectBlock)
  731. for _, b := range grpedBlocks {
  732. grped[b.Index] = b
  733. }
  734. plans := exec.NewPlanBuilder()
  735. for i := 0; i < len(lostBlocks); i++ {
  736. var froms []ioswitchlrc.From
  737. grpEles := red.GetGroupElements(lostBlockGrps[i])
  738. for _, ele := range grpEles {
  739. if ele == lostBlocks[i] {
  740. continue
  741. }
  742. froms = append(froms, ioswitchlrc.NewFromNode(grped[ele].FileHash, nil, ele))
  743. }
  744. err := lrcparser.ReconstructGroup(froms, []ioswitchlrc.To{
  745. ioswitchlrc.NewToNode(uploadNodes[i].Storage, lostBlocks[i], fmt.Sprintf("%d", lostBlocks[i])),
  746. }, plans)
  747. if err != nil {
  748. return nil, fmt.Errorf("parsing plan: %w", err)
  749. }
  750. }
  751. fmt.Printf("plans: %v\n", plans)
  752. // 如果没有任何Plan,Wait会直接返回成功
  753. // TODO 添加依赖
  754. ret, err := plans.Execute(exec.NewExecContext()).Wait(context.TODO())
  755. if err != nil {
  756. return nil, fmt.Errorf("executing io plan: %w", err)
  757. }
  758. var newBlocks []stgmod.ObjectBlock
  759. for _, i := range lostBlocks {
  760. newBlocks = append(newBlocks, stgmod.ObjectBlock{
  761. ObjectID: obj.Object.ObjectID,
  762. Index: i,
  763. StorageID: uploadNodes[i].Storage.Storage.StorageID,
  764. FileHash: ret[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash,
  765. })
  766. }
  767. for _, b := range grpedBlocks {
  768. for _, nodeID := range b.StorageIDs {
  769. newBlocks = append(newBlocks, stgmod.ObjectBlock{
  770. ObjectID: obj.Object.ObjectID,
  771. Index: b.Index,
  772. StorageID: nodeID,
  773. FileHash: b.FileHash,
  774. })
  775. }
  776. }
  777. return &coormq.UpdatingObjectRedundancy{
  778. ObjectID: obj.Object.ObjectID,
  779. Redundancy: red,
  780. Blocks: newBlocks,
  781. }, nil
  782. }
  783. */
  784. func (t *CheckPackageRedundancy) reconstructLRC(obj stgmod.ObjectDetail, grpBlocks []stgmod.GrouppedObjectBlock, red *cdssdk.LRCRedundancy, uploadNodes []*StorageLoadInfo) (*coormq.UpdatingObjectRedundancy, error) {
  785. var chosenBlocks []stgmod.GrouppedObjectBlock
  786. for _, block := range grpBlocks {
  787. if len(block.StorageIDs) > 0 && block.Index < red.M() {
  788. chosenBlocks = append(chosenBlocks, block)
  789. }
  790. if len(chosenBlocks) == red.K {
  791. break
  792. }
  793. }
  794. if len(chosenBlocks) < red.K {
  795. return nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
  796. }
  797. // 目前LRC的参数都相同,所以可以不用重建出完整数据然后再分块,可以直接构建出目的节点需要的块
  798. planBlder := exec.NewPlanBuilder()
  799. var froms []ioswitchlrc.From
  800. var toes []ioswitchlrc.To
  801. var newBlocks []stgmod.ObjectBlock
  802. shouldUpdateBlocks := false
  803. for i, node := range uploadNodes {
  804. newBlock := stgmod.ObjectBlock{
  805. ObjectID: obj.Object.ObjectID,
  806. Index: i,
  807. StorageID: node.Storage.Storage.StorageID,
  808. }
  809. grp, ok := lo.Find(grpBlocks, func(grp stgmod.GrouppedObjectBlock) bool { return grp.Index == i })
  810. // 如果新选中的节点已经记录在Block表中,那么就不需要任何变更
  811. if ok && lo.Contains(grp.StorageIDs, node.Storage.Storage.StorageID) {
  812. newBlock.FileHash = grp.FileHash
  813. newBlocks = append(newBlocks, newBlock)
  814. continue
  815. }
  816. shouldUpdateBlocks = true
  817. // 否则就要重建出这个节点需要的块
  818. for _, block := range chosenBlocks {
  819. fmt.Printf("b: %v\n", block.Index)
  820. stg := node.Storage
  821. froms = append(froms, ioswitchlrc.NewFromNode(block.FileHash, *stg.MasterHub, stg.Storage, block.Index))
  822. }
  823. // 输出只需要自己要保存的那一块
  824. toes = append(toes, ioswitchlrc.NewToStorage(*node.Storage.MasterHub, node.Storage.Storage, i, fmt.Sprintf("%d", i)))
  825. newBlocks = append(newBlocks, newBlock)
  826. }
  827. err := lrcparser.ReconstructAny(froms, toes, planBlder)
  828. if err != nil {
  829. return nil, fmt.Errorf("parsing plan: %w", err)
  830. }
  831. fmt.Printf("plans: %v\n", planBlder)
  832. // 如果没有任何Plan,Wait会直接返回成功
  833. // TODO 添加依赖
  834. ret, err := planBlder.Execute(exec.NewExecContext()).Wait(context.TODO())
  835. if err != nil {
  836. return nil, fmt.Errorf("executing io plan: %w", err)
  837. }
  838. if !shouldUpdateBlocks {
  839. return nil, nil
  840. }
  841. for k, v := range ret {
  842. idx, err := strconv.ParseInt(k, 10, 64)
  843. if err != nil {
  844. return nil, fmt.Errorf("parsing result key %s as index: %w", k, err)
  845. }
  846. newBlocks[idx].FileHash = v.(*ops2.FileHashValue).Hash
  847. }
  848. return &coormq.UpdatingObjectRedundancy{
  849. ObjectID: obj.Object.ObjectID,
  850. Redundancy: red,
  851. Blocks: newBlocks,
  852. }, nil
  853. }
  854. // func (t *CheckPackageRedundancy) pinObject(nodeID cdssdk.NodeID, fileHash string) error {
  855. // agtCli, err := stgglb.AgentMQPool.Acquire(nodeID)
  856. // if err != nil {
  857. // return fmt.Errorf("new agent client: %w", err)
  858. // }
  859. // defer stgglb.AgentMQPool.Release(agtCli)
  860. // _, err = agtCli.PinObject(agtmq.ReqPinObject([]string{fileHash}, false))
  861. // if err != nil {
  862. // return fmt.Errorf("start pinning object: %w", err)
  863. // }
  864. // return nil
  865. // }
  866. func init() {
  867. RegisterMessageConvertor(NewCheckPackageRedundancy)
  868. }

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