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

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