feat(flashnode): support read block.#1000151055

Signed-off-by: baihailong <baihailong@oppo.com>
This commit is contained in:
baihailong 2025-06-18 09:55:31 +08:00 committed by chihe
parent c094de085f
commit c7aac8a558
16 changed files with 1126 additions and 178 deletions

View File

@ -330,6 +330,7 @@ func newCmdFlashGroupList(client *master.MasterClient) *cobra.Command {
stdoutln("[Flash Groups]")
slots := make([]*slotInfo, 0)
reservedSlots := make([]*slotInfo, 0)
tbl := table{formatFlashGroupViewTile}
for _, group := range fgView.FlashGroups {
sort.Slice(group.Slots, func(i, j int) bool {
@ -341,7 +342,16 @@ func newCmdFlashGroupList(client *master.MasterClient) *cobra.Command {
slot: slot,
})
}
tbl = tbl.append(arow(group.ID, group.Weight, len(group.Slots), group.Status, group.SlotStatus, len(group.PendingSlots), group.Step, group.FlashNodeCount))
sort.Slice(group.ReservedSlots, func(i, j int) bool {
return group.ReservedSlots[i] < group.ReservedSlots[j]
})
for _, slot := range group.ReservedSlots {
reservedSlots = append(reservedSlots, &slotInfo{
fgID: group.ID,
slot: slot,
})
}
tbl = tbl.append(arow(group.ID, group.Weight, len(group.Slots), len(group.ReservedSlots), group.Status, group.SlotStatus, len(group.PendingSlots), group.Step, group.FlashNodeCount, group.IsReducingSlots))
}
stdoutln(alignTable(tbl...))
@ -357,6 +367,15 @@ func newCmdFlashGroupList(client *master.MasterClient) *cobra.Command {
}
stdoutlnf("num:%d slot:%d fg:%d percent:%0.5f%%", i+1, info.slot, info.fgID, info.percent)
}
stdoutln("ReservedSlots:")
for i, info := range reservedSlots {
if i < len(reservedSlots)-1 {
info.percent = float64(reservedSlots[i+1].slot-info.slot) * 100 / math.MaxUint32
} else {
info.percent = float64(math.MaxUint32-info.slot) * 100 / math.MaxUint32
}
stdoutlnf("num:%d slot:%d fg:%d percent:%0.5f%%", i+1, info.slot, info.fgID, info.percent)
}
return
},
}
@ -462,6 +481,7 @@ func newCmdFlashGroupGraph(client *master.MasterClient) *cobra.Command {
groupn := make(map[uint64]int)
groupStatusMap := make(map[uint64]string)
slots := make([]slotInfo, 0)
reservedSlots := make([]slotInfo, 0)
for _, fg := range fgView.FlashGroups {
groups[fg.ID] = fg
groupn[fg.ID] = 0
@ -476,11 +496,22 @@ func newCmdFlashGroupGraph(client *master.MasterClient) *cobra.Command {
slot: slot,
})
}
for _, slot := range fg.ReservedSlots {
if _, in := set[slot]; in {
continue
}
groupn[fg.ID]++
reservedSlots = append(reservedSlots, slotInfo{
fgID: fg.ID,
slot: slot,
})
}
}
sort.Slice(slots, func(i, j int) bool {
return slots[i].slot < slots[j].slot
})
stdoutln("[Flash Groups]")
stdoutln("[Slots]")
tbl := table{arow("Slot", "ID", "Status", "Count", "Ref", "Proportion")}
for idx, slot := range slots {
g := groups[slot.fgID]
@ -494,6 +525,24 @@ func newCmdFlashGroupGraph(client *master.MasterClient) *cobra.Command {
}
stdoutln(alignTable(tbl...))
sort.Slice(reservedSlots, func(i, j int) bool {
return reservedSlots[i].slot < reservedSlots[j].slot
})
stdoutln("[ReservedSlots]")
tbl1 := table{arow("Slot", "ID", "Status", "Count", "Ref", "Proportion")}
for idx, slot := range reservedSlots {
g := groups[slot.fgID]
var p string
if idx == len(reservedSlots)-1 {
p = proportion(slot.slot, math.MaxUint32)
} else {
p = proportion(slot.slot, reservedSlots[idx+1].slot)
}
tbl1 = tbl1.append(arow(slot.slot, g.ID, g.Status.String(), g.FlashNodeCount, groupn[g.ID], p))
}
stdoutln(alignTable(tbl1...))
fnView, err := client.NodeAPI().ListFlashNodes(-1)
if err != nil {
return

View File

@ -1481,7 +1481,7 @@ var (
"STORAGE CLASS", "INODE COUNT", "USED SIZE", "QUOTA")
formatFlashNodeSimpleViewTableTitle = arow("Zone", "ID", "Address", "Active", "Enable", "FlashGroupID", "ReportTime")
formatFlashNodeViewTableTitle = append(formatFlashNodeSimpleViewTableTitle[:], "DataPath", "HitRate", "Evicts", "Limit", "MaxAlloc", "HasAlloc", "Num", "Status")
formatFlashGroupViewTile = arow("ID", "Weight", "Slots", "Status", "SlotStatus", "PendingSlots", "Step", "FlashNodeCount")
formatFlashGroupViewTile = arow("ID", "Weight", "Slots", "ReservedSlots", "Status", "SlotStatus", "PendingSlots", "Step", "FlashNodeCount", "ReducingSlots")
QosHeader = fmt.Sprintf(qosPattern, "NAME", "TOTAL-MB", "USED-MB")
)
@ -1508,6 +1508,8 @@ func formatFlashGroupView(fg *proto.FlashGroupAdminView) string {
fmt.Sprintf(" ID:%v\n", fg.ID) +
fmt.Sprintf(" Weight:%v\n", fg.Weight) +
fmt.Sprintf(" Slots:%v\n", fg.Slots) +
fmt.Sprintf(" ReservedSlots:%v\n", fg.ReservedSlots) +
fmt.Sprintf(" IsReducingSlots:%v\n", fg.IsReducingSlots) +
fmt.Sprintf(" Status:%v\n", fg.Status) +
fmt.Sprintf(" SlotStatus:%v\n", fg.SlotStatus) +
fmt.Sprintf(" PedningSlots:%v\n", fg.PendingSlots) +

View File

@ -13,6 +13,7 @@ import (
const (
PageSize = 4 * 1024
CACHE_BLOCK_SIZE = 1 << 20
CACHE_OBJECT_BLOCK_SIZE = 4 * 1024 * 1024
ReadCacheTimeout = 1 // second
DefaultCacheTTLSec = 5 * 24 * 3600
FlashGroupDefaultWeight = 1
@ -259,20 +260,60 @@ func (pbh *PutBlockHead) String() string {
return fmt.Sprintf("PutBlockHead[UniKey(%v) BlockLen(%v) TTL(%v)]", pbh.UniKey, pbh.BlockLen, pbh.TTL)
}
func (cr *CacheReadRequestBase) DecodeBinaryFrom(b []byte) {
keyLen := binary.BigEndian.Uint16(b[:2])
cr.Key = string(b[2 : 2+keyLen])
off := 2 + uint32(keyLen)
cr.TTL = int64(binary.BigEndian.Uint64(b[off : off+8])) // TTL (uint64, 8 bytes)
off += 8
cr.Slot = binary.BigEndian.Uint64(b[off : off+8]) // Slot (uint64, 8 bytes)
off += 8
cr.Offset = binary.BigEndian.Uint64(b[off : off+8]) // Offset (uint64, 8 bytes)
off += 8
cr.Size_ = binary.BigEndian.Uint64(b[off : off+8]) // Size (uint64, 8 bytes)
}
func (cr *CacheReadRequestBase) EncodeBinaryTo(b []byte) {
binary.BigEndian.PutUint16(b[:2], uint16(len(cr.Key))) // Length of Key (uint16, 2bytes)
copy(b[2:2+len(cr.Key)], cr.Key) // Key
off := 2 + len(cr.Key)
binary.BigEndian.PutUint64(b[off:off+8], uint64(cr.TTL)) // TTL (uint64, 8 bytes)
off += 8
binary.BigEndian.PutUint64(b[off:off+8], cr.Slot) // Slot (uint64, 8 bytes)
off += 8
binary.BigEndian.PutUint64(b[off:off+8], cr.Offset) // Offset (uint64, 8 bytes)
off += 8
binary.BigEndian.PutUint64(b[off:off+8], cr.Size_) // Size (uint64, 8 bytes)
}
func (cr *CacheReadRequestBase) EncodeBinaryLen() int {
return 2 + len(cr.Key) + 8 + 8 + 8 + 8
}
func (cr *CacheReadRequestBase) String() string {
if cr == nil {
return ""
}
return fmt.Sprintf("cacheReadRequestBase[Key(%v) TTL(%v) Slot(%v) Offset(%v) Size(%v)]",
cr.Key, cr.TTL, cr.Slot, cr.Offset, cr.Size_)
}
type FlashGroupsAdminView struct {
FlashGroups []FlashGroupAdminView
}
type FlashGroupAdminView struct {
ID uint64
Slots []uint32
Weight uint32
Status FlashGroupStatus
SlotStatus SlotStatus
PendingSlots []uint32
Step uint32
FlashNodeCount int
ZoneFlashNodes map[string][]*FlashNodeViewInfo
ID uint64
Slots []uint32
ReservedSlots []uint32
IsReducingSlots bool
Weight uint32
Status FlashGroupStatus
SlotStatus SlotStatus
PendingSlots []uint32
Step uint32
FlashNodeCount int
ZoneFlashNodes map[string][]*FlashNodeViewInfo
}
type FlashNodeViewInfo struct {

396
proto/distributed_cache.pb.go Normal file → Executable file
View File

@ -5,20 +5,17 @@ package proto
import (
fmt "fmt"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/golang/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/golang/protobuf/proto"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = proto.Marshal
_ = fmt.Errorf
_ = math.Inf
)
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
@ -384,47 +381,129 @@ func (m *PutBlockHead) GetTTL() uint64 {
return 0
}
type CacheReadRequestBase struct {
Key string `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"`
TTL int64 `protobuf:"varint,2,opt,name=TTL,proto3" json:"TTL,omitempty"`
Slot uint64 `protobuf:"varint,3,opt,name=Slot,proto3" json:"Slot,omitempty"`
Offset uint64 `protobuf:"varint,4,opt,name=Offset,proto3" json:"Offset,omitempty"`
Size_ uint64 `protobuf:"varint,5,opt,name=Size,proto3" json:"Size,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CacheReadRequestBase) Reset() { *m = CacheReadRequestBase{} }
func (*CacheReadRequestBase) ProtoMessage() {}
func (*CacheReadRequestBase) Descriptor() ([]byte, []int) {
return fileDescriptor_9a29821e5022ef34, []int{5}
}
func (m *CacheReadRequestBase) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *CacheReadRequestBase) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_CacheReadRequestBase.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *CacheReadRequestBase) XXX_Merge(src proto.Message) {
xxx_messageInfo_CacheReadRequestBase.Merge(m, src)
}
func (m *CacheReadRequestBase) XXX_Size() int {
return m.Size()
}
func (m *CacheReadRequestBase) XXX_DiscardUnknown() {
xxx_messageInfo_CacheReadRequestBase.DiscardUnknown(m)
}
var xxx_messageInfo_CacheReadRequestBase proto.InternalMessageInfo
func (m *CacheReadRequestBase) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
func (m *CacheReadRequestBase) GetTTL() int64 {
if m != nil {
return m.TTL
}
return 0
}
func (m *CacheReadRequestBase) GetSlot() uint64 {
if m != nil {
return m.Slot
}
return 0
}
func (m *CacheReadRequestBase) GetOffset() uint64 {
if m != nil {
return m.Offset
}
return 0
}
func (m *CacheReadRequestBase) GetSize_() uint64 {
if m != nil {
return m.Size_
}
return 0
}
func init() {
proto.RegisterType((*DataSource)(nil), "proto.DataSource")
proto.RegisterType((*CacheRequest)(nil), "proto.CacheRequest")
proto.RegisterType((*CacheReadRequest)(nil), "proto.CacheReadRequest")
proto.RegisterType((*CachePrepareRequest)(nil), "proto.CachePrepareRequest")
proto.RegisterType((*PutBlockHead)(nil), "proto.PutBlockHead")
proto.RegisterType((*CacheReadRequestBase)(nil), "proto.CacheReadRequestBase")
}
func init() { proto.RegisterFile("distributed_cache.proto", fileDescriptor_9a29821e5022ef34) }
var fileDescriptor_9a29821e5022ef34 = []byte{
// 463 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x52, 0xc1, 0x6e, 0xd3, 0x40,
0x10, 0xad, 0xeb, 0xc4, 0xa1, 0x93, 0x20, 0xc2, 0x16, 0xc1, 0xaa, 0x07, 0x63, 0xe5, 0x64, 0x09,
0x91, 0x4a, 0xe5, 0x80, 0xc4, 0xb1, 0x84, 0xaa, 0x11, 0x15, 0x44, 0xdb, 0xd2, 0x03, 0x17, 0xe4,
0xd8, 0x93, 0x64, 0x85, 0xeb, 0x2d, 0xde, 0xb5, 0x54, 0x90, 0xf8, 0x8f, 0x1e, 0xf9, 0x12, 0xce,
0x1c, 0xb9, 0x72, 0x43, 0xe1, 0x47, 0x90, 0x67, 0xd7, 0xad, 0x5b, 0x8e, 0x9c, 0x3c, 0xef, 0xcd,
0x7a, 0xe6, 0xbd, 0xb7, 0x0b, 0x8f, 0x32, 0xa9, 0x4d, 0x29, 0xe7, 0x95, 0xc1, 0xec, 0x43, 0x9a,
0xa4, 0x2b, 0x1c, 0x9f, 0x97, 0xca, 0x28, 0xd6, 0xa5, 0xcf, 0xce, 0xd3, 0xa5, 0x34, 0xab, 0x6a,
0x3e, 0x4e, 0xd5, 0xd9, 0xee, 0x52, 0x2d, 0xd5, 0x2e, 0xd1, 0xf3, 0x6a, 0x41, 0x88, 0x00, 0x55,
0xf6, 0xaf, 0xd1, 0x77, 0x0f, 0x60, 0x92, 0x98, 0xe4, 0x58, 0x55, 0x65, 0x8a, 0x2c, 0x04, 0x38,
0x90, 0x39, 0xbe, 0x5d, 0x2c, 0x34, 0x1a, 0xee, 0x45, 0x5e, 0xdc, 0x11, 0x2d, 0x86, 0x45, 0xd0,
0x9f, 0x25, 0xa5, 0x91, 0x46, 0xaa, 0x62, 0x3a, 0xe1, 0x9b, 0x74, 0xa0, 0x4d, 0xb1, 0x1d, 0xb8,
0xf3, 0xea, 0xc2, 0x60, 0x61, 0xa6, 0x13, 0xee, 0x53, 0xfb, 0x0a, 0xb3, 0x11, 0x0c, 0x6c, 0xed,
0xe6, 0x77, 0xa8, 0x7f, 0x83, 0x63, 0x0c, 0x3a, 0xc7, 0xf2, 0x0b, 0xf2, 0x2e, 0xf5, 0xa8, 0x66,
0x0f, 0xa0, 0x7b, 0xa8, 0xb4, 0xd1, 0x3c, 0x88, 0xfc, 0x78, 0x4b, 0x58, 0xf0, 0xa2, 0x73, 0xf9,
0xed, 0xf1, 0xc6, 0xe8, 0x97, 0x07, 0x83, 0x97, 0x75, 0x0c, 0x02, 0x3f, 0x55, 0xa8, 0x0d, 0x7b,
0x08, 0xc1, 0xa9, 0xca, 0xab, 0x33, 0x24, 0xf9, 0x5b, 0xc2, 0xa1, 0x7a, 0xc8, 0xb4, 0x50, 0x19,
0x3a, 0xd1, 0x16, 0xb0, 0x18, 0xee, 0x1d, 0xc8, 0x0b, 0xcc, 0x5a, 0xae, 0xad, 0xea, 0xdb, 0x34,
0xe3, 0xd0, 0x3b, 0xc5, 0x52, 0x4b, 0x55, 0x90, 0xee, 0xbb, 0xa2, 0x81, 0xec, 0x09, 0xf4, 0x6c,
0x7c, 0x9a, 0x77, 0x23, 0x3f, 0xee, 0xef, 0xdd, 0xb7, 0xe1, 0x8e, 0xaf, 0x83, 0x15, 0xcd, 0x09,
0x36, 0x04, 0xff, 0xe4, 0xe4, 0x88, 0x07, 0x91, 0x17, 0xfb, 0xa2, 0x2e, 0xc9, 0x71, 0xae, 0x0c,
0xef, 0x39, 0xc7, 0xb9, 0x32, 0xce, 0xdb, 0x57, 0x18, 0x3a, 0x6b, 0x49, 0xd6, 0xd8, 0x7b, 0x7e,
0xd3, 0x2e, 0x99, 0xec, 0xef, 0x6d, 0xbb, 0x8d, 0xed, 0x96, 0xf8, 0x27, 0x17, 0x67, 0xd0, 0x06,
0x10, 0xdc, 0x0a, 0xdc, 0xbf, 0x0e, 0xdc, 0xad, 0x37, 0xb0, 0x4d, 0x13, 0x66, 0x25, 0x9e, 0x27,
0x25, 0xfe, 0xb7, 0x82, 0xfa, 0x71, 0xe5, 0x89, 0x5e, 0xbd, 0x51, 0x19, 0x6a, 0xbe, 0x49, 0x77,
0xd9, 0x62, 0xdc, 0xd6, 0xf7, 0x30, 0x98, 0x55, 0x66, 0x3f, 0x57, 0xe9, 0xc7, 0x43, 0x4c, 0xb2,
0x5a, 0xf7, 0xbb, 0x42, 0xbe, 0xc6, 0xcf, 0xcd, 0x7d, 0x5a, 0x54, 0x3f, 0x34, 0x3a, 0x74, 0x84,
0x05, 0x39, 0xf2, 0xc5, 0x15, 0x6e, 0x42, 0xb6, 0x96, 0xea, 0xd2, 0xce, 0xde, 0x1f, 0xfe, 0x58,
0x87, 0xde, 0xcf, 0x75, 0xe8, 0xfd, 0x5e, 0x87, 0xde, 0xe5, 0x9f, 0x70, 0x63, 0x1e, 0x90, 0xea,
0x67, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x42, 0xb3, 0xc8, 0x05, 0x57, 0x03, 0x00, 0x00,
// 497 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x92, 0xcf, 0x6e, 0xd3, 0x40,
0x10, 0xc6, 0xeb, 0x38, 0x7f, 0xe8, 0x24, 0x88, 0xb0, 0xad, 0x60, 0xd5, 0x83, 0xb1, 0x72, 0xb2,
0x84, 0x48, 0xa5, 0x72, 0x40, 0xe2, 0x18, 0x42, 0xd5, 0x88, 0x0a, 0x22, 0xb7, 0xf4, 0xc0, 0x05,
0x39, 0xf6, 0x24, 0x59, 0xe1, 0x7a, 0x8b, 0x77, 0x2d, 0x15, 0x24, 0x24, 0x1e, 0xa3, 0x47, 0x9e,
0x84, 0x33, 0x47, 0xae, 0xdc, 0x50, 0x78, 0x11, 0xe4, 0xd9, 0x75, 0xe3, 0xa6, 0xdc, 0x38, 0x79,
0xbe, 0xd9, 0xf5, 0xcc, 0x7c, 0xbf, 0x59, 0x78, 0x98, 0x08, 0xa5, 0x73, 0x31, 0x2b, 0x34, 0x26,
0xef, 0xe3, 0x28, 0x5e, 0xe2, 0xf0, 0x22, 0x97, 0x5a, 0xb2, 0x16, 0x7d, 0xf6, 0x9e, 0x2c, 0x84,
0x5e, 0x16, 0xb3, 0x61, 0x2c, 0xcf, 0xf7, 0x17, 0x72, 0x21, 0xf7, 0x29, 0x3d, 0x2b, 0xe6, 0xa4,
0x48, 0x50, 0x64, 0xfe, 0x1a, 0x7c, 0x77, 0x00, 0xc6, 0x91, 0x8e, 0x4e, 0x64, 0x91, 0xc7, 0xc8,
0x3c, 0x80, 0x43, 0x91, 0xe2, 0x9b, 0xf9, 0x5c, 0xa1, 0xe6, 0x8e, 0xef, 0x04, 0xcd, 0xb0, 0x96,
0x61, 0x3e, 0x74, 0xa7, 0x51, 0xae, 0x85, 0x16, 0x32, 0x9b, 0x8c, 0x79, 0x83, 0x2e, 0xd4, 0x53,
0x6c, 0x0f, 0xee, 0xbc, 0xbc, 0xd4, 0x98, 0xe9, 0xc9, 0x98, 0xbb, 0x74, 0x7c, 0xad, 0xd9, 0x00,
0x7a, 0x26, 0xb6, 0xf5, 0x9b, 0x74, 0x7e, 0x23, 0xc7, 0x18, 0x34, 0x4f, 0xc4, 0x67, 0xe4, 0x2d,
0x3a, 0xa3, 0x98, 0xed, 0x42, 0xeb, 0x48, 0x2a, 0xad, 0x78, 0xdb, 0x77, 0x83, 0xed, 0xd0, 0x88,
0xe7, 0xcd, 0xab, 0x6f, 0x8f, 0xb6, 0x06, 0xbf, 0x1c, 0xe8, 0xbd, 0x28, 0x31, 0x84, 0xf8, 0xb1,
0x40, 0xa5, 0xd9, 0x03, 0x68, 0x9f, 0xc9, 0xb4, 0x38, 0x47, 0x1a, 0x7f, 0x3b, 0xb4, 0xaa, 0x2c,
0x32, 0xc9, 0x64, 0x82, 0x76, 0x68, 0x23, 0x58, 0x00, 0xf7, 0x0e, 0xc5, 0x25, 0x26, 0x35, 0xd7,
0x66, 0xea, 0xcd, 0x34, 0xe3, 0xd0, 0x39, 0xc3, 0x5c, 0x09, 0x99, 0xd1, 0xdc, 0x77, 0xc3, 0x4a,
0xb2, 0xc7, 0xd0, 0x31, 0xf8, 0x14, 0x6f, 0xf9, 0x6e, 0xd0, 0x3d, 0xb8, 0x6f, 0xe0, 0x0e, 0xd7,
0x60, 0xc3, 0xea, 0x06, 0xeb, 0x83, 0x7b, 0x7a, 0x7a, 0xcc, 0xdb, 0xbe, 0x13, 0xb8, 0x61, 0x19,
0x92, 0xe3, 0x54, 0x6a, 0xde, 0xb1, 0x8e, 0x53, 0xa9, 0xad, 0xb7, 0x2f, 0xd0, 0xb7, 0xd6, 0xa2,
0xa4, 0xb2, 0xf7, 0xec, 0xa6, 0x5d, 0x32, 0xd9, 0x3d, 0xd8, 0xb1, 0x1d, 0xeb, 0x47, 0xe1, 0x2d,
0x2e, 0xd6, 0xa0, 0x01, 0xd0, 0xde, 0x00, 0xee, 0xae, 0x81, 0xdb, 0xf6, 0x1a, 0x76, 0xa8, 0xc2,
0x34, 0xc7, 0x8b, 0x28, 0xc7, 0xff, 0x9e, 0xa0, 0x7c, 0x5c, 0x69, 0xa4, 0x96, 0xaf, 0x65, 0x82,
0x8a, 0x37, 0x68, 0x97, 0xb5, 0x8c, 0xed, 0xfa, 0x0e, 0x7a, 0xd3, 0x42, 0x8f, 0x52, 0x19, 0x7f,
0x38, 0xc2, 0x28, 0x29, 0xe7, 0x7e, 0x9b, 0x89, 0x57, 0xf8, 0xa9, 0xda, 0xa7, 0x51, 0xe5, 0x43,
0xa3, 0x4b, 0xc7, 0x98, 0x91, 0x23, 0x37, 0xbc, 0xd6, 0x15, 0x64, 0x63, 0xa9, 0x0c, 0x6d, 0xed,
0xaf, 0x0e, 0xec, 0x6e, 0x12, 0x1d, 0x45, 0x0a, 0xcb, 0x1f, 0xd6, 0x1d, 0xca, 0xb0, 0x2a, 0xd1,
0xb8, 0xbd, 0x27, 0x77, 0xbd, 0xa7, 0x1a, 0xd4, 0xe6, 0x3f, 0xa1, 0xb6, 0x36, 0xa1, 0x8e, 0xfa,
0x3f, 0x56, 0x9e, 0xf3, 0x73, 0xe5, 0x39, 0xbf, 0x57, 0x9e, 0x73, 0xf5, 0xc7, 0xdb, 0x9a, 0xb5,
0x09, 0xdc, 0xd3, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xb4, 0xc4, 0xac, 0xf4, 0xda, 0x03, 0x00,
0x00,
}
func (m *DataSource) Marshal() (dAtA []byte, err error) {
@ -702,6 +781,60 @@ func (m *PutBlockHead) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *CacheReadRequestBase) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CacheReadRequestBase) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *CacheReadRequestBase) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.XXX_unrecognized != nil {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if m.Size_ != 0 {
i = encodeVarintDistributedCache(dAtA, i, uint64(m.Size_))
i--
dAtA[i] = 0x28
}
if m.Offset != 0 {
i = encodeVarintDistributedCache(dAtA, i, uint64(m.Offset))
i--
dAtA[i] = 0x20
}
if m.Slot != 0 {
i = encodeVarintDistributedCache(dAtA, i, uint64(m.Slot))
i--
dAtA[i] = 0x18
}
if m.TTL != 0 {
i = encodeVarintDistributedCache(dAtA, i, uint64(m.TTL))
i--
dAtA[i] = 0x10
}
if len(m.Key) > 0 {
i -= len(m.Key)
copy(dAtA[i:], m.Key)
i = encodeVarintDistributedCache(dAtA, i, uint64(len(m.Key)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintDistributedCache(dAtA []byte, offset int, v uint64) int {
offset -= sovDistributedCache(v)
base := offset
@ -849,6 +982,34 @@ func (m *PutBlockHead) Size() (n int) {
return n
}
func (m *CacheReadRequestBase) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Key)
if l > 0 {
n += 1 + l + sovDistributedCache(uint64(l))
}
if m.TTL != 0 {
n += 1 + sovDistributedCache(uint64(m.TTL))
}
if m.Slot != 0 {
n += 1 + sovDistributedCache(uint64(m.Slot))
}
if m.Offset != 0 {
n += 1 + sovDistributedCache(uint64(m.Offset))
}
if m.Size_ != 0 {
n += 1 + sovDistributedCache(uint64(m.Size_))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func sovDistributedCache(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
@ -1610,6 +1771,165 @@ func (m *PutBlockHead) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *CacheReadRequestBase) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDistributedCache
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CacheReadRequestBase: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CacheReadRequestBase: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDistributedCache
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthDistributedCache
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthDistributedCache
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Key = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType)
}
m.TTL = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDistributedCache
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.TTL |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Slot", wireType)
}
m.Slot = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDistributedCache
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Slot |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Offset", wireType)
}
m.Offset = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDistributedCache
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Offset |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Size_", wireType)
}
m.Size_ = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDistributedCache
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Size_ |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipDistributedCache(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthDistributedCache
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipDistributedCache(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0

View File

@ -48,4 +48,13 @@ message PutBlockHead {
string UniKey = 1;
int64 BlockLen = 2;
uint64 TTL = 3;
}
}
message CacheReadRequestBase {
option (gogoproto.goproto_stringer) = false;
string Key = 1;
int64 TTL = 2;
uint64 Slot = 3;
uint64 Offset = 4;
uint64 Size = 5;
}

View File

@ -300,6 +300,7 @@ const (
OpFlashNodeCacheRead uint8 = 0xDC
OpFlashNodeCachePutBlock uint8 = 0xD8
OpFlashNodeCacheDelete uint8 = 0xD9
OpFlashNodeCacheReadObject uint8 = 0xDD
OpFlashNodeSetReadIOLimits uint8 = 0xED
OpFlashNodeSetWriteIOLimits uint8 = 0xEE
OpFlashNodeScan uint8 = 0xD4
@ -1096,6 +1097,40 @@ func (p *Packet) WriteToConn(c net.Conn) (err error) {
return
}
func (p *Packet) WriteToConnForOCS(c net.Conn) (err error) {
headSize := p.CalcPacketHeaderSize()
header, err := Buffers.Get(headSize)
if err != nil {
header = make([]byte, headSize)
}
// log.LogErrorf("action[WriteToConn] buffer get nil,opcode %v head len [%v]", p.Opcode, len(header))
defer Buffers.Put(header)
c.SetWriteDeadline(time.Now().Add(WriteDeadlineTime * time.Second))
p.MarshalHeader(header)
if _, err = c.Write(header); err == nil {
// write dir version info.
if p.IsVersionList() {
d, err1 := p.MarshalVersionSlice()
if err1 != nil {
log.LogErrorf("MarshalVersionSlice: marshal version ifo failed, err %s", err1.Error())
return err1
}
_, err = c.Write(d)
if err != nil {
return err
}
}
if _, err = c.Write(p.Arg[:int(p.ArgLen)]); err == nil {
if p.Data != nil && p.Size != 0 {
_, err = c.Write(p.Data[:])
}
}
}
return
}
// ReadFull is a wrapper function of io.ReadFull.
func ReadFull(c net.Conn, buf *[]byte, readSize int) (err error) {
*buf = make([]byte, readSize)

View File

@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/cubefs/cubefs/proto"
@ -336,6 +337,16 @@ func (c *Cluster) scheduleToUpdateFlashGroupRespCache() {
for {
if c.partition != nil && c.partition.IsRaftLeader() {
c.flashNodeTopo.updateClientResponse()
// sync fg if slots changed
c.flashNodeTopo.flashGroupMap.Range(func(_, value interface{}) bool {
fg := value.(*FlashGroup)
slotChanged := atomic.LoadInt32(&fg.SlotChanged) != 0
if slotChanged {
c.syncUpdateFlashGroup(fg)
atomic.StoreInt32(&fg.SlotChanged, 0)
}
return true
})
}
select {
case <-c.stopc:

View File

@ -1,7 +1,10 @@
package flashgroupmanager
import (
"math/rand"
"sync"
"sync/atomic"
"time"
"github.com/cubefs/cubefs/proto"
)
@ -12,13 +15,17 @@ const (
)
type flashGroupValue struct {
ID uint64
Slots []uint32 // FlashGroup's position in hasher ring, set by cli. value is range of crc32.
SlotStatus proto.SlotStatus
PendingSlots []uint32
Step uint32
Weight uint32
Status proto.FlashGroupStatus
ID uint64
Slots []uint32 // FlashGroup's position in hasher ring, set by cli. value is range of crc32.
ReservedSlots []uint32
SlotStatus proto.SlotStatus
PendingSlots []uint32
Step uint32
Weight uint32
Status proto.FlashGroupStatus
LostAllFlashNode int32
ReducingSlots int32
SlotChanged int32
}
type FlashGroup struct {
@ -30,13 +37,15 @@ type FlashGroup struct {
func (fg *FlashGroup) GetAdminView() (view proto.FlashGroupAdminView) {
fg.lock.RLock()
view = proto.FlashGroupAdminView{
ID: fg.ID,
Slots: fg.Slots,
Weight: fg.Weight,
Status: fg.Status,
SlotStatus: fg.SlotStatus,
PendingSlots: fg.PendingSlots,
Step: fg.Step,
ID: fg.ID,
Slots: fg.Slots,
ReservedSlots: fg.ReservedSlots,
IsReducingSlots: fg.ReducingSlots != 0,
Weight: fg.Weight,
Status: fg.Status,
SlotStatus: fg.SlotStatus,
PendingSlots: fg.PendingSlots,
Step: fg.Step,
}
view.ZoneFlashNodes = make(map[string][]*proto.FlashNodeViewInfo)
view.FlashNodeCount = len(fg.flashNodes)
@ -75,6 +84,49 @@ func argConvertFlashGroupStatus(active bool) proto.FlashGroupStatus {
return proto.FlashGroupStatus_Inactive
}
func (fg *FlashGroup) IsLostAllFlashNode() bool {
return atomic.LoadInt32(&fg.LostAllFlashNode) != 0
}
func (fg *FlashGroup) ReduceSlot() {
reducingSlots := atomic.LoadInt32(&fg.ReducingSlots) != 0
if reducingSlots {
return
}
atomic.StoreInt32(&fg.ReducingSlots, 1)
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
go func() {
for {
<-ticker.C
if fg.IsLostAllFlashNode() {
fg.executeReduceSlot()
atomic.StoreInt32(&fg.SlotChanged, 1)
} else {
atomic.StoreInt32(&fg.ReducingSlots, 0)
return
}
}
}()
}
func (fg *FlashGroup) executeReduceSlot() {
rand.Seed(time.Now().UnixNano())
numToSelect := len(fg.Slots) / 4
if numToSelect == 0 {
return
}
selectedIndexes := rand.Perm(len(fg.Slots))[:numToSelect]
for i := len(selectedIndexes) - 1; i >= 0; i-- {
index := selectedIndexes[i]
// add selected slot to ReservedSlots
fg.ReservedSlots = append(fg.ReservedSlots, fg.Slots[index])
// remove selected slot from Slots
fg.Slots = append(fg.Slots[:index], fg.Slots[index+1:]...)
}
}
func (fg *FlashGroup) GetStatus() (st proto.FlashGroupStatus) {
fg.lock.RLock()
st = fg.Status
@ -82,11 +134,11 @@ func (fg *FlashGroup) GetStatus() (st proto.FlashGroupStatus) {
return
}
func (fg *FlashGroup) getFlashNodeHostsEnabled() (hosts []string) {
func (fg *FlashGroup) getFlashNodeHostsEnableAndActive() (hosts []string) {
hosts = make([]string, 0, len(fg.flashNodes))
fg.lock.RLock()
for host, flashNode := range fg.flashNodes {
if !flashNode.isEnable() {
if !flashNode.isActiveAndEnable() {
continue
}
hosts = append(hosts, host)

View File

@ -69,13 +69,6 @@ func (flashNode *FlashNode) GetFlashNodeViewInfo() (info *proto.FlashNodeViewInf
return
}
func (flashNode *FlashNode) isEnable() (ok bool) {
flashNode.RLock()
ok = flashNode.IsEnable
flashNode.RUnlock()
return
}
func (flashNode *FlashNode) isActiveAndEnable() (ok bool) {
flashNode.RLock()
ok = flashNode.IsActive && flashNode.IsEnable

View File

@ -173,12 +173,36 @@ func (t *FlashNodeTopology) updateClientResponse() []byte {
func (t *FlashNodeTopology) getFlashGroupView() (fgv *proto.FlashGroupView) {
fgv = new(proto.FlashGroupView)
fgv.Enable = true
fgCount := 0
t.flashGroupMap.Range(func(_, _ interface{}) bool {
fgCount++
return true
})
disableFlashGroupNum := 0
maxDisableFlashGroupCount := fgCount * 2 / 3
t.flashGroupMap.Range(func(_, value interface{}) bool {
fg := value.(*FlashGroup)
if fg.GetStatus().IsActive() {
hosts := fg.getFlashNodeHostsEnabled()
hosts := fg.getFlashNodeHostsEnableAndActive()
if len(hosts) == 0 {
return true
atomic.StoreInt32(&fg.LostAllFlashNode, 1)
fg.ReduceSlot()
} else if len(fg.ReservedSlots) > 0 {
fg.Slots = append(fg.Slots, fg.ReservedSlots...)
fg.ReservedSlots = nil
atomic.StoreInt32(&fg.LostAllFlashNode, 0)
atomic.StoreInt32(&fg.SlotChanged, 1)
}
if len(fg.Slots) == 0 {
disableFlashGroupNum++
if disableFlashGroupNum >= maxDisableFlashGroupCount && len(fg.ReservedSlots) > 0 {
fg.Slots = append(fg.Slots, fg.ReservedSlots...)
atomic.StoreInt32(&fg.SlotChanged, 1)
} else {
return true
}
}
fgv.FlashGroups = append(fgv.FlashGroups, &proto.FlashGroupInfo{
ID: fg.ID,

View File

@ -183,7 +183,7 @@ func (cb *CacheBlock) WriteAt(data []byte, offset, size int64) (err error) {
}
// Read reads data from an extent.
func (cb *CacheBlock) Read(ctx context.Context, data []byte, offset, size int64, waitForBlock bool) (crc uint32, err error) {
func (cb *CacheBlock) Read(ctx context.Context, data []byte, offset, size int64, waitForBlock bool, readCrc bool) (crc uint32, err error) {
var file *os.File
if err = cb.ready(ctx, waitForBlock); err != nil {
return
@ -206,6 +206,11 @@ func (cb *CacheBlock) Read(ctx context.Context, data []byte, offset, size int64,
if realSize >= size {
realSize = size
}
if cb.sourceType == SourceTypeBlock {
realSize = util.PageSize
}
log.LogDebugf("action[Read] read cache block:%v, offset:%d, allocSize:%d, usedSize:%d", cb.blockKey, offset, cb.allocSize, cb.usedSize)
if file, err = cb.GetOrOpenFileHandler(); err != nil {
@ -216,7 +221,19 @@ func (cb *CacheBlock) Read(ctx context.Context, data []byte, offset, size int64,
log.LogErrorf("action[Read] read cacheBlock:%v failed, filename:%v realSize:%d", cb.blockKey, file.Name(), realSize)
return
}
crc = crc32.ChecksumIEEE(data)
if readCrc {
sliceIndex := offset / proto.PageSize
crcOffset := cb.allocSize + HeaderSize + sliceIndex*4
crcBuf := make([]byte, 4)
if _, err = file.ReadAt(crcBuf, crcOffset); err != nil {
log.LogErrorf("action[Read] read crc:%v failed, filename:%v realSize:%d", cb.blockKey, file.Name(), realSize)
return
}
crc = binary.BigEndian.Uint32(crcBuf)
} else {
crc = crc32.ChecksumIEEE(data)
}
return
}
@ -877,3 +894,18 @@ func CalcAllocSizeV2(reqLen int) int {
}
return reqLen
}
func (cb *CacheBlock) VerifyObjectReq(offset, size uint64) error {
end := offset + size - 1
if offset/proto.CACHE_OBJECT_BLOCK_SIZE != end/proto.CACHE_OBJECT_BLOCK_SIZE {
log.LogErrorf("invalid range offset(%v) size(%v)", offset, size)
return fmt.Errorf("invalid range offset(%v) size(%v)", offset, size)
}
if uint64(cb.usedSize) <= end {
log.LogWarnf("block is not read, usedSize(%v) offset(%v) size(%v)", cb.usedSize, offset, size)
return fmt.Errorf("block is not ready")
}
return nil
}

View File

@ -253,7 +253,7 @@ func TestBlockReadCache(t *testing.T) {
require.NoError(t, err)
cacheBlock.notifyReady()
bytesRead := make([]byte, 1024)
_, err = cacheBlock.Read(context.Background(), bytesRead, offset, 1024, true)
_, err = cacheBlock.Read(context.Background(), bytesRead, offset, 1024, true, false)
require.NoError(t, err)
require.Equal(t, bytesRead, bytes)
}
@ -306,7 +306,7 @@ func testParallelOperation(t *testing.T) {
case <-ticker.C:
bytesRead := make([]byte, 1024)
offset := rand.Intn(int(cacheBlock.allocSize))
cacheBlock.Read(context.Background(), bytesRead, int64(offset), 1024, true)
cacheBlock.Read(context.Background(), bytesRead, int64(offset), 1024, true, false)
}
}
}()

View File

@ -534,6 +534,10 @@ func (c *CacheEngine) DeleteCacheBlock(key string) {
func (c *CacheEngine) GetCacheBlockForRead(volume string, inode, offset uint64, version uint32, size uint64) (block *CacheBlock, err error) {
key := GenCacheBlockKey(volume, inode, offset, version)
return c.GetCacheBlockForReadByKey(key)
}
func (c *CacheEngine) GetCacheBlockForReadByKey(key string) (block *CacheBlock, err error) {
v, ok := c.keyToDiskMap.Load(key)
if ok {
cacheItem := v.(*lruCacheItem)

View File

@ -63,6 +63,8 @@ func (f *FlashNode) handlePacket(conn net.Conn, p *proto.Packet) (err error) {
err = f.opCachePutBlock(conn, p)
case proto.OpFlashNodeCacheDelete:
err = f.opCacheDelete(conn, p)
case proto.OpFlashNodeCacheReadObject:
err = f.opCacheObjectGet(conn, p)
case proto.OpFlashNodeSetReadIOLimits:
err = f.opSetReadIOLimits(conn, p)
case proto.OpFlashNodeSetWriteIOLimits:
@ -386,6 +388,67 @@ func (f *FlashNode) opCachePutBlock(conn net.Conn, p *proto.Packet) (err error)
return err
}
func (f *FlashNode) opCacheObjectGet(conn net.Conn, p *proto.Packet) (err error) {
var key string
bgTime := stat.BeginStat()
defer func() {
stat.EndStat("FlashNode:opCacheObjectGet", err, bgTime, 1)
}()
defer func() {
if err != nil {
if !proto.IsFlashNodeLimitError(err) {
log.LogWarnf("action[opCacheObjectGet] key:[%s], logMsg:%s", key,
p.LogMessage(p.GetOpMsg(), conn.RemoteAddr().String(), p.StartT, err))
}
p.PacketErrorWithBody(proto.OpErr, ([]byte)(err.Error()))
if e := p.WriteToConn(conn); e != nil {
log.LogErrorf("action[opCacheObjectGet] write to conn %v", e)
}
}
}()
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Duration(f.handleReadTimeout)*time.Millisecond)
defer ctxCancel()
req := new(proto.CacheReadRequestBase)
if err = p.UnmarshalDataPb(req); err != nil {
return
}
log.LogDebugf("opCacheObjectGet req(%v)", req)
uniKey := req.Key
pDir := cachengine.MapKeyToDirectory(uniKey)
blockKey := cachengine.GenCacheBlockKeyV2(pDir, uniKey)
f.updateSlotStat(req.Slot)
block, err := f.cacheEngine.GetCacheBlockForReadByKey(blockKey)
if err != nil {
// if not find block, check whether should cache
err = f.shouldCache(key)
return
}
err = block.VerifyObjectReq(req.Offset, req.Size_)
if err != nil {
return
}
bgTime2 := stat.BeginStat()
// reply to client as quick as possible if hit cache
err2 := f.limitRead.RunNoWait(int(req.Size_), false, func() {
err = f.doObjectReadRequest(ctx, conn, req, p, block)
})
if err2 != nil {
err = err2
stat.EndStat("HitCacheRead", err, bgTime2, 1)
return
}
stat.EndStat("HitCacheRead", err, bgTime2, 1)
return
}
func (f *FlashNode) opFlashNodeScan(conn net.Conn, p *proto.Packet) (err error) {
data := p.Data
responseAckOKToMaster(conn, p)
@ -582,7 +645,7 @@ func (f *FlashNode) doStreamReadRequest(ctx context.Context, conn net.Conn, req
reply.ExtentOffset = offset
p.Size = currReadSize
p.ExtentOffset = offset
reply.CRC, err = block.Read(ctx, reply.Data[:], offset, int64(currReadSize), f.waitForCacheBlock)
reply.CRC, err = block.Read(ctx, reply.Data[:], offset, int64(currReadSize), f.waitForCacheBlock, false)
if err != nil {
bufRelease()
return
@ -614,6 +677,99 @@ func (f *FlashNode) doStreamReadRequest(ctx context.Context, conn net.Conn, req
return
}
func (f *FlashNode) doObjectReadRequest(ctx context.Context, conn net.Conn, req *proto.CacheReadRequestBase, p *proto.Packet,
block *cachengine.CacheBlock,
) (err error) {
const action = "action[doObjectReadRequest]"
offset := int64(req.Offset)
defer func() {
if err != nil {
// too many caching logs
if strings.Compare(err.Error(), "require data is caching") != 0 {
log.LogWarnf("%s cache block(%v) err:%v", action, block.String(), err)
}
} else {
f.metrics.updateReadCountMetric(block.GetRootPath())
f.metrics.updateReadBytesMetric(req.Size_, block.GetRootPath())
}
}()
// first reply to client
firstReply := proto.NewPacket()
firstReply.ReqID = p.ReqID
firstReply.Size = uint32(req.Size_)
firstReply.ResultCode = proto.OpOk
firstReply.Opcode = p.Opcode
firstReply.StartT = p.StartT
if err = firstReply.WriteToConn(conn); err != nil {
log.LogErrorf("testRead key:[%s] %s", req.Key,
firstReply.LogMessage(firstReply.GetOpMsg(), conn.RemoteAddr().String(), firstReply.StartT, err))
return
}
// reply data to client
end := offset + int64(req.Size_)
for {
err = nil
reply := proto.NewPacket()
reply.ReqID = p.ReqID
reply.StartT = p.StartT
var bufOnce sync.Once
buf, bufErr := proto.Buffers.Get(util.PageSize)
bufRelease := func() {
bufOnce.Do(func() {
if bufErr == nil {
proto.Buffers.Put(reply.Data[:util.PageSize])
}
})
}
if bufErr != nil {
buf = make([]byte, util.PageSize)
}
reply.Data = buf
alignedOffset := offset / util.PageSize * util.PageSize
reply.KernelOffset = uint64(offset)
reply.ExtentOffset = offset - alignedOffset
p.Size = util.PageSize
p.ExtentOffset = offset
reply.CRC, err = block.Read(ctx, reply.Data[:], alignedOffset, util.PageSize, f.waitForCacheBlock, true)
if err != nil {
bufRelease()
return
}
p.CRC = reply.CRC
realNeedSize := uint32(util.Min(int(util.PageSize-reply.ExtentOffset), int(end-offset)))
reply.Size = realNeedSize
reply.ResultCode = proto.OpOk
reply.Opcode = p.Opcode
p.ResultCode = proto.OpOk
bgTime := stat.BeginStat()
if err = reply.WriteToConnForOCS(conn); err != nil {
bufRelease()
log.LogErrorf("%s key:[%s] %s", action, req.Key,
reply.LogMessage(reply.GetOpMsg(), conn.RemoteAddr().String(), reply.StartT, err))
return
}
stat.EndStat("HitCacheRead:ReplyToClient", err, bgTime, 1)
offset = alignedOffset + util.PageSize
bufRelease()
if log.EnableInfo() {
log.LogInfof("%s ReqID[%d] key:[%s] reply[%s] block[%s]", action, p.ReqID, req.Key,
reply.LogMessage(reply.GetOpMsg(), conn.RemoteAddr().String(), reply.StartT, err), block.String())
}
if uint64(offset) >= req.Offset+req.Size_ {
break
}
}
p.PacketOkReply()
return
}
func (f *FlashNode) opCachePrepare(conn net.Conn, p *proto.Packet) (err error) {
action := "action[opCachePrepare]"
var volume string

View File

@ -55,6 +55,7 @@ func testTCP(t *testing.T) {
t.Run("ManualScan", testTCPManualScan)
t.Run("CachePutBlock", testTCPCachePutBlock)
t.Run("CacheDelete", testTCPCacheDelete)
t.Run("CacheReadObject", testTCPObjectCacheRead)
}
func testTCPHeartbeat(t *testing.T) {
@ -297,3 +298,41 @@ func testShouldCache(t *testing.T) {
require.Equal(t, countA, int64(2))
require.Equal(t, countB, int64(8))
}
func testTCPObjectCacheRead(t *testing.T) {
conn := newTCPConn(t)
defer conn.Close()
p := proto.NewPacketReqID()
r := proto.NewPacket()
flashServer.readLimiter.SetBurst(0)
flashServer.readLimiter.SetLimit(0)
time.Sleep(time.Second)
p.Opcode = proto.OpFlashNodeCacheReadObject
require.NoError(t, p.WriteToConn(conn))
require.NoError(t, r.ReadFromConn(conn, 3))
require.Equal(t, proto.OpErr, r.ResultCode) // Read limited
flashServer.readLimiter.SetBurst(200)
flashServer.readLimiter.SetLimit(20)
time.Sleep(time.Second)
require.NoError(t, p.WriteToConn(conn))
require.NoError(t, r.ReadFromConn(conn, 3))
require.Equal(t, proto.OpErr, r.ResultCode) // CacheReadRequestBase is nil
p.Size = 1
p.Data = []byte{'{'}
require.NoError(t, p.WriteToConn(conn))
require.NoError(t, r.ReadFromConn(conn, 3))
require.Equal(t, proto.OpErr, r.ResultCode) // CacheReadRequestBase invalid
req := new(proto.CacheReadRequestBase)
req.Key = "1234567"
req.Slot = 12345678
req.Offset = 0
req.Size_ = 1024
req.TTL = _ttl
p.MarshalDataPb(req)
require.NoError(t, p.WriteToConn(conn))
require.NoError(t, r.ReadFromConn(conn, 3))
require.Equal(t, proto.OpErr, r.ResultCode)
}

View File

@ -1,9 +1,10 @@
package remotecache
import (
"bytes"
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"io"
@ -11,6 +12,7 @@ import (
"runtime/debug"
"strings"
"sync"
"syscall"
"time"
"github.com/cubefs/cubefs/proto"
@ -19,7 +21,6 @@ import (
"github.com/cubefs/cubefs/util/auditlog"
"github.com/cubefs/cubefs/util/btree"
"github.com/cubefs/cubefs/util/bytespool"
"github.com/cubefs/cubefs/util/errors"
"github.com/cubefs/cubefs/util/exporter"
"github.com/cubefs/cubefs/util/iputil"
"github.com/cubefs/cubefs/util/log"
@ -118,15 +119,12 @@ func NewRemoteCacheClient(masters []string, blockSize uint64) (rc *RemoteCacheCl
rc.wg.Add(1)
go rc.refresh()
// rc.Started = true
log.LogDebugf("NewRemoteCacheClient sucess")
return rc, err
}
func (rc *RemoteCacheClient) Stop() {
// rc.Started = false
close(rc.stopC)
rc.conns.Close()
rc.wg.Wait()
@ -411,73 +409,16 @@ func (rc *RemoteCacheClient) getMinFlashGroup() (*FlashGroup, uint32) {
return nil, 0
}
func (rc *RemoteCacheClient) getFlashGroup(key string, fixedFileOffset uint64) (uint32, *FlashGroup, uint32) {
slot := proto.ComputeCacheBlockSlot(key, 0, fixedFileOffset)
func (rc *RemoteCacheClient) getFlashGroupByKey(key string) (uint32, *FlashGroup, uint32) {
slot := proto.ComputeCacheBlockSlot(key, 0, 0)
fg, ownerSlot := rc.GetFlashGroupBySlot(slot)
return slot, fg, ownerSlot
}
func (rc *RemoteCacheClient) readFromRemoteCache(ctx context.Context, key string, offset, size uint64, cReadRequests []*CacheReadRequest) (total int, err error) {
metric := exporter.NewTPCnt("RemoteCacheClient:readFromRemoteCache")
metricBytes := exporter.NewCounter("RemoteCacheClient:readFromRemoteCacheBytes")
defer func() {
metric.SetWithLabels(err, map[string]string{"key": key})
metricBytes.AddWithLabels(int64(total), map[string]string{"key": key})
}()
var read int
for _, req := range cReadRequests {
if len(req.CacheRequest.Sources) == 0 {
total += int(req.Size_)
continue
}
slot, fg, ownerSlot := rc.getFlashGroup(req.CacheRequest.Volume, req.CacheRequest.FixedFileOffset)
if fg == nil {
err = fmt.Errorf("readFromRemoteCache failed: cannot find any flashGroups")
return
}
req.CacheRequest.Slot = uint64(slot)<<32 | uint64(ownerSlot)
// if read, err = s.client.RemoteCache.Read(ctx, fg, s.inode, req); err != nil {
if read, err = rc.Read(ctx, fg, 0, req); err != nil {
if !proto.IsFlashNodeLimitError(err) {
log.LogWarnf("readFromRemoteCache: flashGroup read failed. offset(%v) size(%v) fg(%v) req(%v) err(%v)", offset, size, fg, req, err)
}
return
} else {
log.LogDebugf("readFromRemoteCache: inode(%d) cacheReadRequest version %v, source %v",
req.CacheRequest.Inode, req.CacheRequest.Version, req.CacheRequest.Sources)
total += read
}
}
log.LogDebugf("readFromRemoteCache: cacheReadRequests(%v) offset(%v) size(%v) total(%v)", cReadRequests, offset, size, total)
return total, nil
}
func (rc *RemoteCacheClient) Get(ctx context.Context, reqId, key string, from, to int64) (r io.ReadCloser, length int, shouldCache bool, err error) {
size := to - from
data := make([]byte, size)
var cacheReadRequests []*CacheReadRequest
cacheReadRequests, err = rc.prepareCacheRequests(key, uint64(from), uint64(size), data)
if err == nil {
var read int
if read, err = rc.readFromRemoteCache(ctx, key, uint64(from), uint64(size), cacheReadRequests); err == nil {
remoteCacheHitMetric := exporter.NewCounter("readRemoteCacheHit")
remoteCacheHitMetric.AddWithLabels(1, map[string]string{"key": key})
return io.NopCloser(bytes.NewReader(data[:read])), read, false, err
} else {
log.LogWarnf("RemoteCacheClient:Get failed, err(%v)", err)
}
}
return nil, 0, false, nil
}
func (rc *RemoteCacheClient) Put(ctx context.Context, reqId, key string, r io.Reader, length int64) (err error) {
if length <= 0 {
log.LogWarnf("put data length is less and equal 0")
return nil
if length <= 0 || length > proto.CACHE_OBJECT_BLOCK_SIZE {
log.LogWarnf("put data length %v is leq 0 or gt 4M", length)
return fmt.Errorf("put data length %v is leq 0 or gt 4M", length)
}
slot := proto.ComputeCacheBlockSlot(key, 0, 0)
fg, _ := rc.GetFlashGroupBySlot(slot)
@ -585,15 +526,15 @@ type CacheReadRequest struct {
Data []byte
}
func NewRemoteCachePacket(reqId int64, opcode uint8) *proto.Packet {
func NewRemoteCachePacket(reqId string, opcode uint8) *proto.Packet {
p := new(proto.Packet)
p.Magic = proto.ProtoMagic
if reqId == 0 {
p.ReqID = proto.GenerateRequestID()
} else {
p.ReqID = reqId
}
p.ReqID = proto.GenerateRequestID()
p.Opcode = opcode
if len(reqId) != 0 {
p.Arg = []byte(reqId)
p.ArgLen = uint32(len(reqId))
}
return p
}
@ -603,29 +544,6 @@ func NewFlashCacheReply() *proto.Packet {
return p
}
func (rc *RemoteCacheClient) prepareCacheRequests(key string, offset, size uint64, data []byte) ([]*CacheReadRequest, error) {
bgTime := stat.BeginStat()
defer func() {
stat.EndStat("prepareCacheRequests", nil, bgTime, 1)
}()
var (
cReadRequests []*CacheReadRequest
cRequests = make([]*proto.CacheRequest, 0)
)
for fixedOff := offset / rc.blockSize * rc.blockSize; fixedOff < offset+size; fixedOff += rc.blockSize {
cReq := &proto.CacheRequest{
Volume: key,
FixedFileOffset: fixedOff,
TTL: rc.TTL,
}
cRequests = append(cRequests, cReq)
}
cReadRequests = rc.GetCacheReadRequests(offset, size, data, cRequests)
log.LogDebugf("prepareCacheRequests: extent[offset=%v,size=%v] cReadRequests %v ", offset, size, cReadRequests)
return cReadRequests, nil
}
func (rc *RemoteCacheClient) GetCacheReadRequests(offset uint64, size uint64, data []byte, cRequests []*proto.CacheRequest) (cReadRequests []*CacheReadRequest) {
cReadRequests = make([]*CacheReadRequest, 0, len(cRequests))
startFixedOff := offset / rc.blockSize * rc.blockSize
@ -680,7 +598,7 @@ func (rc *RemoteCacheClient) Read(ctx context.Context, fg *FlashGroup, reqId int
log.LogWarnf("FlashGroup Read failed: fg(%v) err(%v)", fg, err)
return
}
reqPacket = NewRemoteCachePacket(reqId, proto.OpFlashNodeCacheRead)
reqPacket = NewRemoteCachePacket("", proto.OpFlashNodeCacheRead)
if err = reqPacket.MarshalDataPb(&req.CacheReadRequest); err != nil {
log.LogWarnf("FlashGroup Read: failed to MarshalData (%+v). err(%v)", req, err)
return
@ -737,7 +655,7 @@ func (rc *RemoteCacheClient) Prepare(ctx context.Context, fg *FlashGroup, req *p
log.LogWarnf("FlashGroup prepare failed: err(%v)", err)
return
}
reqPacket := NewRemoteCachePacket(0, proto.OpFlashNodeCachePrepare)
reqPacket := NewRemoteCachePacket("", proto.OpFlashNodeCachePrepare)
if err = reqPacket.MarshalDataPb(req); err != nil {
log.LogWarnf("FlashGroup Prepare: failed to MarshalData (%v), err(%v)", req, err)
return
@ -802,3 +720,266 @@ func (rc *RemoteCacheClient) getReadReply(conn *net.TCPConn, reqPacket *proto.Pa
}
return
}
func (rc *RemoteCacheClient) Get(ctx context.Context, key string, reqId string, from, to int64) (r io.ReadCloser, length int64, shouldCache bool, err error) {
log.LogDebugf("RemoteCacheClient Get: key(%v) reqId(%v) from(%v) to(%v)", key, reqId, from, to)
if from < 0 || to-from <= 0 {
return nil, 0, false, fmt.Errorf("invalid range: from(%v) to(%v)", from, to)
}
remoteCacheMetric := exporter.NewCounter("readRemoteCache")
remoteCacheMetric.AddWithLabels(1, map[string]string{"key": key})
if r, length, err = rc.readObjectFromRemoteCache(ctx, key, reqId, uint64(from), uint64(to-from)); err == nil {
remoteCacheHitMetric := exporter.NewCounter("readRemoteCacheHit")
remoteCacheHitMetric.AddWithLabels(1, map[string]string{"key": key})
} else if errors.Is(err, proto.ErrorNotExistShouldCache) {
shouldCache = true
}
return
}
func (rc *RemoteCacheClient) readObjectFromRemoteCache(ctx context.Context, key string, reqId string, offset, size uint64) (reader *RemoteCacheReader, length int64, err error) {
metric := exporter.NewTPCnt("RemoteCacheClient:readObjectFromRemoteCache")
metricBytes := exporter.NewCounter("RemoteCacheClient:readFromRemoteCacheBytes")
defer func() {
metric.SetWithLabels(err, map[string]string{"key": key})
metricBytes.AddWithLabels(length, map[string]string{"key": key})
}()
slot, fg, ownerSlot := rc.getFlashGroupByKey(key)
if fg == nil {
err = fmt.Errorf("readObjectFromRemoteCache failed: cannot find any flashGroups")
return
}
var req proto.CacheReadRequestBase
req.Key = key
req.TTL = rc.TTL
req.Slot = uint64(slot)<<32 | uint64(ownerSlot)
req.Offset = offset
req.Size_ = size
if reader, length, err = rc.ReadObject(ctx, fg, reqId, &req); err != nil {
if !proto.IsFlashNodeLimitError(err) {
log.LogWarnf("readObjectFromRemoteCache: flashGroup read failed. offset(%v) size(%v) fg(%v) req(%v) reqId(%v) err(%v)", offset, size, fg, req, reqId, err)
}
return
}
log.LogDebugf("readObjectFromRemoteCache: cacheReadRequestBase(%v) key(%v) reqId(%v)", req, key, reqId)
return
}
func (reader *RemoteCacheReader) getReadObjectReply(p *proto.Packet) (length int64, err error) {
header, err := proto.Buffers.Get(util.PacketHeaderSize)
if err != nil {
header = make([]byte, util.PacketHeaderSize)
}
defer proto.Buffers.Put(header)
var n int
if n, err = io.ReadFull(reader.conn, header); err != nil {
return
}
if n != util.PacketHeaderSize {
return 0, syscall.EBADMSG
}
if err = p.UnmarshalHeader(header); err != nil {
return
}
if err = p.TryReadExtraFieldsFromConn(reader.conn); err != nil {
return
}
if p.ArgLen > 0 {
p.Arg = make([]byte, int(p.ArgLen))
if _, err = io.ReadFull(reader.conn, p.Arg[:int(p.ArgLen)]); err != nil {
return 0, err
}
}
if p.ResultCode != proto.OpOk {
size := p.Size
p.Data = make([]byte, size)
if _, err = io.ReadFull(reader.conn, p.Data[:size]); err != nil {
return 0, err
}
err = fmt.Errorf("%v", string(p.Data))
if !proto.IsFlashNodeLimitError(err) {
log.LogWarnf("getReadObjectReply: ResultCode NOK, err(%v) ResultCode(%v)", err, p.ResultCode)
}
return
}
return int64(p.Size), nil
}
type RemoteCacheReader struct {
conn *net.TCPConn
reader *bufio.Reader
rc *RemoteCacheClient
}
func (rc *RemoteCacheClient) NewRemoteCacheReader(conn *net.TCPConn) *RemoteCacheReader {
r := &RemoteCacheReader{
conn: conn,
reader: bufio.NewReaderSize(conn, 4096),
rc: rc,
}
return r
}
func (reader *RemoteCacheReader) Read(p []byte) (n int, err error) {
reply := new(proto.Packet)
var expectLen int64
expectLen, err = reader.getReadObjectReply(reply)
if err != nil {
return
}
buff := bytespool.Alloc(proto.PageSize)
defer bytespool.Free(buff)
_, err = io.ReadFull(reader.conn, buff)
if err != nil {
log.LogErrorf("RemoteCacheReader:Read err(%v)", err)
return
}
// check crc
actualCrc := crc32.ChecksumIEEE(buff)
if actualCrc != reply.CRC {
err = fmt.Errorf("inconsistent CRC, offset(%v) extentOffset(%v) expect(%v) actualCrc(%v)", reply.KernelOffset, reply.ExtentOffset, reply.CRC, actualCrc)
log.LogErrorf("RemoteCacheReader:Read check crc failed offset(%v) extentOffset(%v) expect(%v) actualCrc(%v)",
reply.KernelOffset, reply.ExtentOffset, reply.CRC, actualCrc)
return
}
log.LogDebugf("RemoteCacheReader:Read offset(%v) extentOffset(%v) expectLen(%v) p.len(%v)", reply.KernelOffset, reply.ExtentOffset, expectLen, len(p))
extentOffset := reply.ExtentOffset
copy(p[:expectLen], buff[extentOffset:extentOffset+expectLen])
return int(expectLen), nil
}
func (reader *RemoteCacheReader) Close() error {
log.LogDebugf("RemoteCacheReader Close, addr(%v)", reader.conn.RemoteAddr())
reader.rc.conns.PutConnect(reader.conn, false)
return nil
}
func (rc *RemoteCacheClient) ReadObjectFirstReply(conn *net.TCPConn, p *proto.Packet) (length int64, err error) {
header, err := proto.Buffers.Get(util.PacketHeaderSize)
if err != nil {
header = make([]byte, util.PacketHeaderSize)
}
defer proto.Buffers.Put(header)
var n int
if n, err = io.ReadFull(conn, header); err != nil {
return
}
if n != util.PacketHeaderSize {
return 0, syscall.EBADMSG
}
if err = p.UnmarshalHeader(header); err != nil {
return
}
if err = p.TryReadExtraFieldsFromConn(conn); err != nil {
return
}
if p.ArgLen > 0 {
p.Arg = make([]byte, int(p.ArgLen))
if _, err = io.ReadFull(conn, p.Arg[:int(p.ArgLen)]); err != nil {
return 0, err
}
}
if p.ResultCode != proto.OpOk {
size := p.Size
p.Data = make([]byte, size)
if _, err = io.ReadFull(conn, p.Data[:size]); err != nil {
return 0, err
}
err = fmt.Errorf("%v", string(p.Data))
if !proto.IsFlashNodeLimitError(err) {
log.LogWarnf("ReadObjectFirstReply: ResultCode NOK, err(%v) ResultCode(%v)", err, p.ResultCode)
}
return
}
return int64(p.Size), nil
}
func (rc *RemoteCacheClient) ReadObject(ctx context.Context, fg *FlashGroup, reqId string, req *proto.CacheReadRequestBase) (reader *RemoteCacheReader, length int64, err error) {
var (
conn *net.TCPConn
moved bool
addr string
reqPacket *proto.Packet
)
bgTime := stat.BeginStat()
defer func() {
if err != nil && strings.Contains(err.Error(), "timeout") {
err = fmt.Errorf("read timeout")
}
parts := strings.Split(addr, ":")
if len(parts) > 0 && addr != "" {
stat.EndStat(fmt.Sprintf("flashNode:%v", parts[0]), err, bgTime, 1)
}
stat.EndStat("flashNode", err, bgTime, 1)
}()
for {
if ctx.Err() != nil {
return nil, 0, ctx.Err()
}
addr = fg.getFlashHost()
if addr == "" {
err = fmt.Errorf("no available host")
log.LogWarnf("FlashGroup Read failed: fg(%v) err(%v)", fg, err)
return
}
reqPacket = NewRemoteCachePacket(reqId, proto.OpFlashNodeCacheReadObject)
if err = reqPacket.MarshalDataPb(req); err != nil {
log.LogWarnf("FlashGroup Read: failed to MarshalData (%+v). err(%v)", req, err)
return
}
if conn, err = rc.conns.GetConnect(addr); err != nil {
log.LogWarnf("FlashGroup Read: get connection failed, addr(%v) reqPacket(%v) err(%v) remoteCacheMultiRead(%v)", addr, req, err, rc.RemoteCacheMultiRead)
moved = fg.moveToUnknownRank(addr, err, rc.FlashNodeTimeoutCount)
if rc.RemoteCacheMultiRead {
log.LogInfof("Retrying due to GetConnect of addr(%v) failure err(%v)", addr, err)
continue
}
return
}
if err = reqPacket.WriteToConn(conn); err != nil {
log.LogWarnf("FlashGroup Read: failed to write to addr(%v) err(%v) remoteCacheMultiRead(%v)", addr, err, rc.RemoteCacheMultiRead)
rc.conns.PutConnect(conn, err != nil)
moved = fg.moveToUnknownRank(addr, err, rc.FlashNodeTimeoutCount)
if rc.RemoteCacheMultiRead {
log.LogInfof("Retrying due to write to addr(%v) failure err(%v)", addr, err)
continue
}
return
}
reply := new(proto.Packet)
length, err = rc.ReadObjectFirstReply(conn, reply)
if err != nil {
log.LogWarnf("ReadObject getReadObjectReply from(%v) failed, reply(%v) error(%v)", conn.RemoteAddr(), reply, err)
rc.conns.PutConnect(conn, true)
return nil, 0, err
}
reader = rc.NewRemoteCacheReader(conn)
break
}
log.LogDebugf("FlashGroup Read: flashGroup(%v) addr(%v) CacheReadRequest(%v) reqPacket(%v) err(%v) moved(%v) remoteCacheMultiRead(%v)",
fg, addr, req, reqPacket, err, moved, rc.RemoteCacheMultiRead)
return
}