JCC-Storage/common/pkgs/iterator/download_object_iterator.go

314 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package iterator
import (
"fmt"
"io"
"math/rand"
"reflect"
"github.com/samber/lo"
"gitlink.org.cn/cloudream/common/pkgs/logger"
cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
myio "gitlink.org.cn/cloudream/common/utils/io"
stgglb "gitlink.org.cn/cloudream/storage/common/globals"
stgmodels "gitlink.org.cn/cloudream/storage/common/models"
"gitlink.org.cn/cloudream/storage/common/pkgs/db/model"
"gitlink.org.cn/cloudream/storage/common/pkgs/distlock"
"gitlink.org.cn/cloudream/storage/common/pkgs/distlock/reqbuilder"
"gitlink.org.cn/cloudream/storage/common/pkgs/ec"
coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
)
type DownloadingObjectIterator = Iterator[*IterDownloadingObject]
type IterDownloadingObject struct {
Object model.Object
File io.ReadCloser
}
type DownloadNodeInfo struct {
Node model.Node
IsSameLocation bool
}
type DownloadContext struct {
Distlock *distlock.Service
}
type DownloadObjectIterator struct {
OnClosing func()
objectDetails []stgmodels.ObjectDetail
currentIndex int
downloadCtx *DownloadContext
}
func NewDownloadObjectIterator(objectDetails []stgmodels.ObjectDetail, downloadCtx *DownloadContext) *DownloadObjectIterator {
return &DownloadObjectIterator{
objectDetails: objectDetails,
downloadCtx: downloadCtx,
}
}
func (i *DownloadObjectIterator) MoveNext() (*IterDownloadingObject, error) {
coorCli, err := stgglb.CoordinatorMQPool.Acquire()
if err != nil {
return nil, fmt.Errorf("new coordinator client: %w", err)
}
defer stgglb.CoordinatorMQPool.Release(coorCli)
if i.currentIndex >= len(i.objectDetails) {
return nil, ErrNoMoreItem
}
item, err := i.doMove(coorCli)
i.currentIndex++
return item, err
}
func (iter *DownloadObjectIterator) doMove(coorCli *coormq.Client) (*IterDownloadingObject, error) {
obj := iter.objectDetails[iter.currentIndex]
switch red := obj.Object.Redundancy.(type) {
case *cdssdk.RepRedundancy:
reader, err := iter.downloadRepObject(coorCli, iter.downloadCtx, obj, red)
if err != nil {
return nil, fmt.Errorf("downloading rep object: %w", err)
}
return &IterDownloadingObject{
Object: obj.Object,
File: reader,
}, nil
case *cdssdk.ECRedundancy:
reader, err := iter.downloadECObject(coorCli, iter.downloadCtx, obj, red)
if err != nil {
return nil, fmt.Errorf("downloading ec object: %w", err)
}
return &IterDownloadingObject{
Object: obj.Object,
File: reader,
}, nil
}
return nil, fmt.Errorf("unsupported redundancy type: %v", reflect.TypeOf(obj.Object.Redundancy))
}
func (i *DownloadObjectIterator) Close() {
if i.OnClosing != nil {
i.OnClosing()
}
}
// chooseDownloadNode 选择一个下载节点
// 1. 从与当前客户端相同地域的节点中随机选一个
// 2. 没有用的话从所有节点中随机选一个
func (i *DownloadObjectIterator) chooseDownloadNode(entries []DownloadNodeInfo) DownloadNodeInfo {
sameLocationEntries := lo.Filter(entries, func(e DownloadNodeInfo, i int) bool { return e.IsSameLocation })
if len(sameLocationEntries) > 0 {
return sameLocationEntries[rand.Intn(len(sameLocationEntries))]
}
return entries[rand.Intn(len(entries))]
}
func (iter *DownloadObjectIterator) downloadRepObject(coorCli *coormq.Client, ctx *DownloadContext, obj stgmodels.ObjectDetail, repRed *cdssdk.RepRedundancy) (io.ReadCloser, error) {
//采取直接读,优先选内网节点
var chosenNodes []DownloadNodeInfo
for i := range obj.Blocks {
if len(obj.Blocks[i].CachedNodeIDs) == 0 {
return nil, fmt.Errorf("no node has block %d", obj.Blocks[i].Index)
}
getNodesResp, err := coorCli.GetNodes(coormq.NewGetNodes(obj.Blocks[i].CachedNodeIDs))
if err != nil {
continue
}
downloadNodes := lo.Map(getNodesResp.Nodes, func(node model.Node, index int) DownloadNodeInfo {
return DownloadNodeInfo{
Node: node,
IsSameLocation: node.LocationID == stgglb.Local.LocationID,
}
})
chosenNodes = append(chosenNodes, iter.chooseDownloadNode(downloadNodes))
}
var fileStrs []io.ReadCloser
for i := range obj.Blocks {
str, err := downloadFile(ctx, chosenNodes[i], obj.Blocks[i].FileHash)
if err != nil {
for i -= 1; i >= 0; i-- {
fileStrs[i].Close()
}
return nil, fmt.Errorf("donwloading file: %w", err)
}
fileStrs = append(fileStrs, str)
}
fileReaders, filesCloser := myio.ToReaders(fileStrs)
return myio.AfterReadClosed(myio.Length(myio.Join(fileReaders), obj.Object.Size), func(c io.ReadCloser) {
filesCloser()
}), nil
}
func (iter *DownloadObjectIterator) downloadECObject(coorCli *coormq.Client, ctx *DownloadContext, obj stgmodels.ObjectDetail, ecRed *cdssdk.ECRedundancy) (io.ReadCloser, error) {
//采取直接读,优先选内网节点
var chosenNodes []DownloadNodeInfo
var chosenBlocks []stgmodels.ObjectBlockDetail
for i := range obj.Blocks {
if len(chosenBlocks) == ecRed.K {
break
}
// 块没有被任何节点缓存或者获取失败都没关系只要能获取到k个块的信息就行
if len(obj.Blocks[i].CachedNodeIDs) == 0 {
continue
}
getNodesResp, err := coorCli.GetNodes(coormq.NewGetNodes(obj.Blocks[i].CachedNodeIDs))
if err != nil {
continue
}
downloadNodes := lo.Map(getNodesResp.Nodes, func(node model.Node, index int) DownloadNodeInfo {
return DownloadNodeInfo{
Node: node,
IsSameLocation: node.LocationID == stgglb.Local.LocationID,
}
})
chosenBlocks = append(chosenBlocks, obj.Blocks[i])
chosenNodes = append(chosenNodes, iter.chooseDownloadNode(downloadNodes))
}
if len(chosenBlocks) < ecRed.K {
return nil, fmt.Errorf("no enough blocks to reconstruct the file, want %d, get only %d", ecRed.K, len(chosenBlocks))
}
var fileStrs []io.ReadCloser
rs, err := ec.NewRs(ecRed.K, ecRed.N, ecRed.ChunkSize)
if err != nil {
return nil, fmt.Errorf("new rs: %w", err)
}
for i := range chosenBlocks {
str, err := downloadFile(ctx, chosenNodes[i], chosenBlocks[i].FileHash)
if err != nil {
for i -= 1; i >= 0; i-- {
fileStrs[i].Close()
}
return nil, fmt.Errorf("donwloading file: %w", err)
}
fileStrs = append(fileStrs, str)
}
fileReaders, filesCloser := myio.ToReaders(fileStrs)
var indexes []int
for _, b := range chosenBlocks {
indexes = append(indexes, b.Index)
}
outputs, outputsCloser := myio.ToReaders(rs.ReconstructData(fileReaders, indexes))
return myio.AfterReadClosed(myio.Length(myio.ChunkedJoin(outputs, int(ecRed.ChunkSize)), obj.Object.Size), func(c io.ReadCloser) {
filesCloser()
outputsCloser()
}), nil
}
func downloadFile(ctx *DownloadContext, node DownloadNodeInfo, fileHash string) (io.ReadCloser, error) {
// 如果客户端与节点在同一个地域,则使用内网地址连接节点
nodeIP := node.Node.ExternalIP
grpcPort := node.Node.ExternalGRPCPort
if node.IsSameLocation {
nodeIP = node.Node.LocalIP
grpcPort = node.Node.LocalGRPCPort
logger.Infof("client and node %d are at the same location, use local ip", node.Node.NodeID)
}
if stgglb.IPFSPool != nil {
logger.Infof("try to use local IPFS to download file")
reader, err := downloadFromLocalIPFS(ctx, fileHash)
if err == nil {
return reader, nil
}
logger.Warnf("download from local IPFS failed, so try to download from node %s, err: %s", nodeIP, err.Error())
}
return downloadFromNode(ctx, node.Node.NodeID, nodeIP, grpcPort, fileHash)
}
func downloadFromNode(ctx *DownloadContext, nodeID cdssdk.NodeID, nodeIP string, grpcPort int, fileHash string) (io.ReadCloser, error) {
// 二次获取锁
mutex, err := reqbuilder.NewBuilder().
// 用于从IPFS下载文件
IPFS().ReadOneRep(nodeID, fileHash).
MutexLock(ctx.Distlock)
if err != nil {
return nil, fmt.Errorf("acquire locks failed, err: %w", err)
}
// 连接grpc
agtCli, err := stgglb.AgentRPCPool.Acquire(nodeIP, grpcPort)
if err != nil {
return nil, fmt.Errorf("new agent grpc client: %w", err)
}
reader, err := agtCli.GetIPFSFile(fileHash)
if err != nil {
return nil, fmt.Errorf("getting ipfs file: %w", err)
}
reader = myio.AfterReadClosed(reader, func(io.ReadCloser) {
mutex.Unlock()
})
return reader, nil
}
func downloadFromLocalIPFS(ctx *DownloadContext, fileHash string) (io.ReadCloser, error) {
onClosed := func() {}
if stgglb.Local.NodeID != nil {
// 二次获取锁
mutex, err := reqbuilder.NewBuilder().
// 用于从IPFS下载文件
IPFS().ReadOneRep(*stgglb.Local.NodeID, fileHash).
MutexLock(ctx.Distlock)
if err != nil {
return nil, fmt.Errorf("acquire locks failed, err: %w", err)
}
onClosed = func() {
mutex.Unlock()
}
}
ipfsCli, err := stgglb.IPFSPool.Acquire()
if err != nil {
return nil, fmt.Errorf("new ipfs client: %w", err)
}
reader, err := ipfsCli.OpenRead(fileHash)
if err != nil {
return nil, fmt.Errorf("read ipfs file failed, err: %w", err)
}
reader = myio.AfterReadClosed(reader, func(io.ReadCloser) {
onClosed()
})
return reader, nil
}