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.

selector.go 8.5 kB

7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. package strategy
  2. import (
  3. "fmt"
  4. "math"
  5. "reflect"
  6. "time"
  7. "github.com/samber/lo"
  8. "gitlink.org.cn/cloudream/common/pkgs/bitmap"
  9. "gitlink.org.cn/cloudream/common/utils/math2"
  10. "gitlink.org.cn/cloudream/common/utils/sort2"
  11. "gitlink.org.cn/cloudream/storage2/client/internal/metacache"
  12. "gitlink.org.cn/cloudream/storage2/client/types"
  13. "gitlink.org.cn/cloudream/storage2/common/consts"
  14. cortypes "gitlink.org.cn/cloudream/storage2/coordinator/types"
  15. )
  16. type Request struct {
  17. Detail types.ObjectDetail
  18. Range math2.Range
  19. DestHub cortypes.HubID // 可以为0。此字段不为0时,DestLocation字段无意义。
  20. DestLocation cortypes.LocationID // 可以为0
  21. }
  22. type Strategy interface {
  23. GetDetail() types.ObjectDetail
  24. }
  25. // 直接下载完整对象
  26. type DirectStrategy struct {
  27. Detail types.ObjectDetail
  28. UserSpace types.UserSpaceDetail
  29. }
  30. func (s *DirectStrategy) GetDetail() types.ObjectDetail {
  31. return s.Detail
  32. }
  33. // 从指定对象重建对象
  34. type ECReconstructStrategy struct {
  35. Detail types.ObjectDetail
  36. Redundancy types.ECRedundancy
  37. Blocks []types.ObjectBlock
  38. UserSpaces []types.UserSpaceDetail
  39. }
  40. func (s *ECReconstructStrategy) GetDetail() types.ObjectDetail {
  41. return s.Detail
  42. }
  43. type LRCReconstructStrategy struct {
  44. Detail types.ObjectDetail
  45. Redundancy types.LRCRedundancy
  46. Blocks []types.ObjectBlock
  47. Spaces []types.UserSpaceDetail
  48. }
  49. func (s *LRCReconstructStrategy) GetDetail() types.ObjectDetail {
  50. return s.Detail
  51. }
  52. type Selector struct {
  53. cfg Config
  54. storageMeta *metacache.UserSpaceMeta
  55. hubMeta *metacache.HubMeta
  56. connectivity *metacache.Connectivity
  57. }
  58. func NewSelector(cfg Config, storageMeta *metacache.UserSpaceMeta, hubMeta *metacache.HubMeta, connectivity *metacache.Connectivity) *Selector {
  59. return &Selector{
  60. cfg: cfg,
  61. storageMeta: storageMeta,
  62. hubMeta: hubMeta,
  63. connectivity: connectivity,
  64. }
  65. }
  66. func (s *Selector) Select(req Request) (Strategy, error) {
  67. req2 := request2{
  68. Detail: req.Detail,
  69. Range: req.Range,
  70. DestLocation: req.DestLocation,
  71. }
  72. if req.DestHub != 0 {
  73. req2.DestHub = s.hubMeta.Get(req.DestHub)
  74. }
  75. switch red := req.Detail.Object.Redundancy.(type) {
  76. case *types.NoneRedundancy:
  77. return s.selectForNoneOrRep(req2)
  78. case *types.RepRedundancy:
  79. return s.selectForNoneOrRep(req2)
  80. case *types.ECRedundancy:
  81. return s.selectForEC(req2, *red)
  82. case *types.LRCRedundancy:
  83. return s.selectForLRC(req2, *red)
  84. }
  85. return nil, fmt.Errorf("unsupported redundancy type: %v of object %v", reflect.TypeOf(req.Detail.Object.Redundancy), req.Detail.Object.ObjectID)
  86. }
  87. type downloadSpaceInfo struct {
  88. Space types.UserSpaceDetail
  89. ObjectPinned bool
  90. Blocks []types.ObjectBlock
  91. Distance float64
  92. }
  93. type downloadBlock struct {
  94. Space types.UserSpaceDetail
  95. Block types.ObjectBlock
  96. }
  97. type request2 struct {
  98. Detail types.ObjectDetail
  99. Range math2.Range
  100. DestHub *cortypes.Hub
  101. DestLocation cortypes.LocationID
  102. }
  103. func (s *Selector) selectForNoneOrRep(req request2) (Strategy, error) {
  104. sortedStgs := s.sortDownloadStorages(req)
  105. if len(sortedStgs) == 0 {
  106. return nil, fmt.Errorf("no storage available for download")
  107. }
  108. _, blks := s.getMinReadingBlockSolution(sortedStgs, 1)
  109. if len(blks) == 0 {
  110. return nil, fmt.Errorf("no block available for download")
  111. }
  112. return &DirectStrategy{
  113. Detail: req.Detail,
  114. UserSpace: sortedStgs[0].Space,
  115. }, nil
  116. }
  117. func (s *Selector) selectForEC(req request2, red types.ECRedundancy) (Strategy, error) {
  118. sortedStgs := s.sortDownloadStorages(req)
  119. if len(sortedStgs) == 0 {
  120. return nil, fmt.Errorf("no storage available for download")
  121. }
  122. bsc, blocks := s.getMinReadingBlockSolution(sortedStgs, red.K)
  123. osc, stg := s.getMinReadingObjectSolution(sortedStgs, red.K)
  124. if bsc < osc {
  125. bs := make([]types.ObjectBlock, len(blocks))
  126. ss := make([]types.UserSpaceDetail, len(blocks))
  127. for i, b := range blocks {
  128. bs[i] = b.Block
  129. ss[i] = b.Space
  130. }
  131. return &ECReconstructStrategy{
  132. Detail: req.Detail,
  133. Redundancy: red,
  134. Blocks: bs,
  135. UserSpaces: ss,
  136. }, nil
  137. }
  138. // bsc >= osc,如果osc是MaxFloat64,那么bsc也一定是,也就意味着没有足够块来恢复文件
  139. if osc == math.MaxFloat64 {
  140. return nil, fmt.Errorf("no enough blocks to reconstruct the object %v , want %d, get only %d", req.Detail.Object.ObjectID, red.K, len(blocks))
  141. }
  142. return &DirectStrategy{
  143. Detail: req.Detail,
  144. UserSpace: stg,
  145. }, nil
  146. }
  147. func (s *Selector) selectForLRC(req request2, red types.LRCRedundancy) (Strategy, error) {
  148. sortedStgs := s.sortDownloadStorages(req)
  149. if len(sortedStgs) == 0 {
  150. return nil, fmt.Errorf("no storage available for download")
  151. }
  152. var blocks []downloadBlock
  153. selectedBlkIdx := make(map[int]bool)
  154. for _, stg := range sortedStgs {
  155. for _, b := range stg.Blocks {
  156. if b.Index >= red.M() || selectedBlkIdx[b.Index] {
  157. continue
  158. }
  159. blocks = append(blocks, downloadBlock{
  160. Space: stg.Space,
  161. Block: b,
  162. })
  163. selectedBlkIdx[b.Index] = true
  164. }
  165. }
  166. if len(blocks) < red.K {
  167. return nil, fmt.Errorf("not enough blocks to download lrc object")
  168. }
  169. bs := make([]types.ObjectBlock, len(blocks))
  170. ss := make([]types.UserSpaceDetail, len(blocks))
  171. for i, b := range blocks {
  172. bs[i] = b.Block
  173. ss[i] = b.Space
  174. }
  175. return &LRCReconstructStrategy{
  176. Detail: req.Detail,
  177. Redundancy: red,
  178. Blocks: bs,
  179. Spaces: ss,
  180. }, nil
  181. }
  182. func (s *Selector) sortDownloadStorages(req request2) []*downloadSpaceInfo {
  183. var spaceIDs []types.UserSpaceID
  184. for _, id := range req.Detail.PinnedAt {
  185. if !lo.Contains(spaceIDs, id) {
  186. spaceIDs = append(spaceIDs, id)
  187. }
  188. }
  189. for _, b := range req.Detail.Blocks {
  190. if !lo.Contains(spaceIDs, b.UserSpaceID) {
  191. spaceIDs = append(spaceIDs, b.UserSpaceID)
  192. }
  193. }
  194. downloadSpaceMap := make(map[types.UserSpaceID]*downloadSpaceInfo)
  195. for _, id := range req.Detail.PinnedAt {
  196. storage, ok := downloadSpaceMap[id]
  197. if !ok {
  198. mod := s.storageMeta.Get(id)
  199. if mod == nil || mod.MasterHub == nil {
  200. continue
  201. }
  202. storage = &downloadSpaceInfo{
  203. Space: *mod,
  204. ObjectPinned: true,
  205. Distance: s.getStorageDistance(req, *mod),
  206. }
  207. downloadSpaceMap[id] = storage
  208. }
  209. storage.ObjectPinned = true
  210. }
  211. for _, b := range req.Detail.Blocks {
  212. space, ok := downloadSpaceMap[b.UserSpaceID]
  213. if !ok {
  214. mod := s.storageMeta.Get(b.UserSpaceID)
  215. if mod == nil || mod.MasterHub == nil {
  216. continue
  217. }
  218. space = &downloadSpaceInfo{
  219. Space: *mod,
  220. Distance: s.getStorageDistance(req, *mod),
  221. }
  222. downloadSpaceMap[b.UserSpaceID] = space
  223. }
  224. space.Blocks = append(space.Blocks, b)
  225. }
  226. return sort2.Sort(lo.Values(downloadSpaceMap), func(left, right *downloadSpaceInfo) int {
  227. return sort2.Cmp(left.Distance, right.Distance)
  228. })
  229. }
  230. func (s *Selector) getStorageDistance(req request2, src types.UserSpaceDetail) float64 {
  231. if req.DestHub != nil {
  232. if src.MasterHub.HubID == req.DestHub.HubID {
  233. return consts.StorageDistanceSameStorage
  234. }
  235. if src.MasterHub.LocationID == req.DestHub.LocationID {
  236. return consts.StorageDistanceSameLocation
  237. }
  238. latency := s.connectivity.Get(src.MasterHub.HubID, req.DestHub.HubID)
  239. if latency == nil || *latency > time.Duration(float64(time.Millisecond)*s.cfg.HighLatencyHubMs) {
  240. return consts.HubDistanceHighLatencyHub
  241. }
  242. return consts.StorageDistanceOther
  243. }
  244. if req.DestLocation != 0 {
  245. if src.MasterHub.LocationID == req.DestLocation {
  246. return consts.StorageDistanceSameLocation
  247. }
  248. }
  249. return consts.StorageDistanceOther
  250. }
  251. func (s *Selector) getMinReadingBlockSolution(sortedStgs []*downloadSpaceInfo, k int) (float64, []downloadBlock) {
  252. gotBlocksMap := bitmap.Bitmap64(0)
  253. var gotBlocks []downloadBlock
  254. dist := float64(0.0)
  255. for _, n := range sortedStgs {
  256. for _, b := range n.Blocks {
  257. if !gotBlocksMap.Get(b.Index) {
  258. gotBlocks = append(gotBlocks, downloadBlock{
  259. Space: n.Space,
  260. Block: b,
  261. })
  262. gotBlocksMap.Set(b.Index, true)
  263. dist += n.Distance
  264. }
  265. if len(gotBlocks) >= k {
  266. return dist, gotBlocks
  267. }
  268. }
  269. }
  270. return math.MaxFloat64, gotBlocks
  271. }
  272. func (s *Selector) getMinReadingObjectSolution(sortedStgs []*downloadSpaceInfo, k int) (float64, types.UserSpaceDetail) {
  273. dist := math.MaxFloat64
  274. var downloadSpace types.UserSpaceDetail
  275. for _, n := range sortedStgs {
  276. if n.ObjectPinned && float64(k)*n.Distance < dist {
  277. dist = float64(k) * n.Distance
  278. stg := n.Space
  279. downloadSpace = stg
  280. }
  281. }
  282. return dist, downloadSpace
  283. }

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