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

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