package strategy import ( "fmt" "math" "reflect" "time" "github.com/samber/lo" "gitlink.org.cn/cloudream/common/pkgs/bitmap" "gitlink.org.cn/cloudream/common/utils/math2" "gitlink.org.cn/cloudream/common/utils/sort2" "gitlink.org.cn/cloudream/jcs-pub/client/internal/metacache" "gitlink.org.cn/cloudream/jcs-pub/client/types" "gitlink.org.cn/cloudream/jcs-pub/common/consts" cortypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types" ) type Request struct { Detail types.ObjectDetail Range math2.Range DestHub cortypes.HubID // 可以为0。此字段不为0时,DestLocation字段无意义。 DestLocation cortypes.LocationID // 可以为0 } type Strategy interface { GetDetail() types.ObjectDetail } // 直接下载完整对象 type DirectStrategy struct { Detail types.ObjectDetail UserSpace types.UserSpaceDetail } func (s *DirectStrategy) GetDetail() types.ObjectDetail { return s.Detail } // 从指定对象重建对象 type ECReconstructStrategy struct { Detail types.ObjectDetail Redundancy types.ECRedundancy Blocks []types.ObjectBlock UserSpaces []types.UserSpaceDetail } func (s *ECReconstructStrategy) GetDetail() types.ObjectDetail { return s.Detail } type LRCReconstructStrategy struct { Detail types.ObjectDetail Redundancy types.LRCRedundancy Blocks []types.ObjectBlock Spaces []types.UserSpaceDetail } func (s *LRCReconstructStrategy) GetDetail() types.ObjectDetail { return s.Detail } type Selector struct { cfg Config spaceMeta *metacache.UserSpaceMeta hubMeta *metacache.HubMeta connectivity *metacache.Connectivity } func NewSelector(cfg Config, storageMeta *metacache.UserSpaceMeta, hubMeta *metacache.HubMeta, connectivity *metacache.Connectivity) *Selector { return &Selector{ cfg: cfg, spaceMeta: storageMeta, hubMeta: hubMeta, connectivity: connectivity, } } func (s *Selector) Select(req Request) (Strategy, error) { req2 := request2{ Detail: req.Detail, Range: req.Range, DestLocation: req.DestLocation, } if req.DestHub != 0 { req2.DestHub = s.hubMeta.Get(req.DestHub) } switch red := req.Detail.Object.Redundancy.(type) { case *types.NoneRedundancy: return s.selectForNoneOrRep(req2) case *types.RepRedundancy: return s.selectForNoneOrRep(req2) case *types.ECRedundancy: return s.selectForEC(req2, *red) case *types.LRCRedundancy: return s.selectForLRC(req2, *red) } return nil, fmt.Errorf("unsupported redundancy type: %v of object %v", reflect.TypeOf(req.Detail.Object.Redundancy), req.Detail.Object.ObjectID) } type downloadSpaceInfo struct { Space types.UserSpaceDetail ObjectPinned bool Blocks []types.ObjectBlock Distance float64 } type downloadBlock struct { Space types.UserSpaceDetail Block types.ObjectBlock } type request2 struct { Detail types.ObjectDetail Range math2.Range DestHub *cortypes.Hub DestLocation cortypes.LocationID } func (s *Selector) selectForNoneOrRep(req request2) (Strategy, error) { sortedStgs := s.sortDownloadStorages(req) if len(sortedStgs) == 0 { return nil, fmt.Errorf("no storage available for download") } _, blks := s.getMinReadingBlockSolution(sortedStgs, 1) if len(blks) == 0 { return nil, fmt.Errorf("no block available for download") } return &DirectStrategy{ Detail: req.Detail, UserSpace: sortedStgs[0].Space, }, nil } func (s *Selector) selectForEC(req request2, red types.ECRedundancy) (Strategy, error) { sortedStgs := s.sortDownloadStorages(req) if len(sortedStgs) == 0 { return nil, fmt.Errorf("no storage available for download") } bsc, blocks := s.getMinReadingBlockSolution(sortedStgs, red.K) osc, stg := s.getMinReadingObjectSolution(sortedStgs, red.K) if bsc < osc { bs := make([]types.ObjectBlock, len(blocks)) ss := make([]types.UserSpaceDetail, len(blocks)) for i, b := range blocks { bs[i] = b.Block ss[i] = b.Space } return &ECReconstructStrategy{ Detail: req.Detail, Redundancy: red, Blocks: bs, UserSpaces: ss, }, nil } // bsc >= osc,如果osc是MaxFloat64,那么bsc也一定是,也就意味着没有足够块来恢复文件 if osc == math.MaxFloat64 { 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)) } return &DirectStrategy{ Detail: req.Detail, UserSpace: stg, }, nil } func (s *Selector) selectForLRC(req request2, red types.LRCRedundancy) (Strategy, error) { sortedStgs := s.sortDownloadStorages(req) if len(sortedStgs) == 0 { return nil, fmt.Errorf("no storage available for download") } var blocks []downloadBlock selectedBlkIdx := make(map[int]bool) for _, stg := range sortedStgs { for _, b := range stg.Blocks { if b.Index >= red.M() || selectedBlkIdx[b.Index] { continue } blocks = append(blocks, downloadBlock{ Space: stg.Space, Block: b, }) selectedBlkIdx[b.Index] = true } } if len(blocks) < red.K { return nil, fmt.Errorf("not enough blocks to download lrc object") } bs := make([]types.ObjectBlock, len(blocks)) ss := make([]types.UserSpaceDetail, len(blocks)) for i, b := range blocks { bs[i] = b.Block ss[i] = b.Space } return &LRCReconstructStrategy{ Detail: req.Detail, Redundancy: red, Blocks: bs, Spaces: ss, }, nil } func (s *Selector) sortDownloadStorages(req request2) []*downloadSpaceInfo { var spaceIDs []types.UserSpaceID for _, id := range req.Detail.PinnedAt { if !lo.Contains(spaceIDs, id) { spaceIDs = append(spaceIDs, id) } } for _, b := range req.Detail.Blocks { if !lo.Contains(spaceIDs, b.UserSpaceID) { spaceIDs = append(spaceIDs, b.UserSpaceID) } } downloadSpaceMap := make(map[types.UserSpaceID]*downloadSpaceInfo) for _, id := range req.Detail.PinnedAt { storage, ok := downloadSpaceMap[id] if !ok { mod := s.spaceMeta.Get(id) if mod == nil { continue } storage = &downloadSpaceInfo{ Space: *mod, ObjectPinned: true, Distance: s.getStorageDistance(req, *mod), } downloadSpaceMap[id] = storage } storage.ObjectPinned = true } for _, b := range req.Detail.Blocks { space, ok := downloadSpaceMap[b.UserSpaceID] if !ok { mod := s.spaceMeta.Get(b.UserSpaceID) if mod == nil { continue } space = &downloadSpaceInfo{ Space: *mod, Distance: s.getStorageDistance(req, *mod), } downloadSpaceMap[b.UserSpaceID] = space } space.Blocks = append(space.Blocks, b) } return sort2.Sort(lo.Values(downloadSpaceMap), func(left, right *downloadSpaceInfo) int { return sort2.Cmp(left.Distance, right.Distance) }) } func (s *Selector) getStorageDistance(req request2, src types.UserSpaceDetail) float64 { if req.DestHub != nil { if src.RecommendHub.HubID == req.DestHub.HubID { return consts.StorageDistanceSameStorage } if src.RecommendHub.LocationID == req.DestHub.LocationID { return consts.StorageDistanceSameLocation } latency := s.connectivity.Get(src.RecommendHub.HubID, req.DestHub.HubID) if latency == nil || *latency > time.Duration(float64(time.Millisecond)*s.cfg.HighLatencyHubMs) { return consts.HubDistanceHighLatencyHub } return consts.StorageDistanceOther } if req.DestLocation != 0 { if src.RecommendHub.LocationID == req.DestLocation { return consts.StorageDistanceSameLocation } } return consts.StorageDistanceOther } func (s *Selector) getMinReadingBlockSolution(sortedStgs []*downloadSpaceInfo, k int) (float64, []downloadBlock) { gotBlocksMap := bitmap.Bitmap64(0) var gotBlocks []downloadBlock dist := float64(0.0) for _, n := range sortedStgs { for _, b := range n.Blocks { if !gotBlocksMap.Get(b.Index) { gotBlocks = append(gotBlocks, downloadBlock{ Space: n.Space, Block: b, }) gotBlocksMap.Set(b.Index, true) dist += n.Distance } if len(gotBlocks) >= k { return dist, gotBlocks } } } return math.MaxFloat64, gotBlocks } func (s *Selector) getMinReadingObjectSolution(sortedStgs []*downloadSpaceInfo, k int) (float64, types.UserSpaceDetail) { dist := math.MaxFloat64 var downloadSpace types.UserSpaceDetail for _, n := range sortedStgs { if n.ObjectPinned && float64(k)*n.Distance < dist { dist = float64(k) * n.Distance stg := n.Space downloadSpace = stg } } return dist, downloadSpace }