mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 02:00:56 +00:00
756 lines
22 KiB
Go
756 lines
22 KiB
Go
// Copyright 2018 The CubeFS Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
|
|
package stream
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"net"
|
|
"runtime/debug"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/cubefs/cubefs/proto"
|
|
"github.com/cubefs/cubefs/sdk/master"
|
|
"github.com/cubefs/cubefs/sdk/meta"
|
|
"github.com/cubefs/cubefs/util"
|
|
"github.com/cubefs/cubefs/util/auditlog"
|
|
"github.com/cubefs/cubefs/util/bloom"
|
|
"github.com/cubefs/cubefs/util/btree"
|
|
"github.com/cubefs/cubefs/util/errors"
|
|
"github.com/cubefs/cubefs/util/iputil"
|
|
"github.com/cubefs/cubefs/util/log"
|
|
"github.com/cubefs/cubefs/util/stat"
|
|
)
|
|
|
|
const (
|
|
BloomBits = 10 * 1024 * 1024 * 8
|
|
BloomHashNum = 7
|
|
|
|
cachePathSeparator = ","
|
|
|
|
pingCount = 3
|
|
_connIdelTimeout = 30 // 30 second
|
|
|
|
RefreshFlashNodesInterval = time.Minute
|
|
RefreshHostLatencyInterval = 20 * time.Second
|
|
|
|
sameZoneWeight = 70
|
|
)
|
|
|
|
type ZoneRankType int
|
|
|
|
const (
|
|
SameZoneRank ZoneRankType = iota
|
|
SameRegionRank
|
|
CrossRegionRank
|
|
UnknownZoneRank
|
|
)
|
|
|
|
type CacheConfig struct {
|
|
Cluster string
|
|
Volume string
|
|
Masters []string
|
|
MW *meta.MetaWrapper
|
|
|
|
SameZoneWeight int
|
|
}
|
|
|
|
type RemoteCache struct {
|
|
cluster string
|
|
volname string
|
|
mc *master.MasterClient
|
|
conns *util.ConnectPool
|
|
hostLatency sync.Map
|
|
flashGroups *btree.BTree
|
|
stopOnce sync.Once
|
|
stopC chan struct{}
|
|
wg sync.WaitGroup
|
|
metaWrapper *meta.MetaWrapper
|
|
cacheBloom *bloom.BloomFilter
|
|
|
|
Started bool
|
|
ClusterEnabled bool
|
|
VolumeEnabled bool
|
|
Path string
|
|
AutoPrepare bool
|
|
TTL int64
|
|
ReadTimeout int64 // ms
|
|
PrepareCh chan *PrepareRemoteCacheRequest
|
|
|
|
clusterEnable func(bool)
|
|
lock sync.Mutex
|
|
|
|
remoteCacheMaxFileSizeGB int64
|
|
remoteCacheOnlyForNotSSD bool
|
|
remoteCacheMultiRead bool
|
|
flashNodeTimeoutCount int32
|
|
sameZoneTimeout int64 // microsecond
|
|
sameRegionTimeout int64 // ms
|
|
|
|
AddressPingMap sync.Map
|
|
}
|
|
|
|
type AddressPingStats struct {
|
|
sync.Mutex
|
|
durations []time.Duration
|
|
index int
|
|
}
|
|
|
|
func (as *AddressPingStats) Add(duration time.Duration) {
|
|
as.Lock()
|
|
defer as.Unlock()
|
|
if as.index < 5 {
|
|
as.durations = append(as.durations, duration)
|
|
} else {
|
|
as.durations[as.index%5] = duration
|
|
}
|
|
as.index++
|
|
}
|
|
|
|
func (as *AddressPingStats) Average() time.Duration {
|
|
as.Lock()
|
|
defer as.Unlock()
|
|
if len(as.durations) == 0 {
|
|
return 0
|
|
}
|
|
var total time.Duration
|
|
for _, d := range as.durations {
|
|
total += d
|
|
}
|
|
return total / time.Duration(len(as.durations))
|
|
}
|
|
|
|
func (rc *RemoteCache) UpdateRemoteCacheConfig(client *ExtentClient, view *proto.SimpleVolView) {
|
|
// cannot set vol's RemoteCacheReadTimeoutSec <= 0
|
|
if view.RemoteCacheReadTimeout <= 0 {
|
|
view.RemoteCacheReadTimeout = proto.DefaultRemoteCacheClientReadTimeout
|
|
}
|
|
if view.RemoteCacheSameZoneTimeout <= 0 {
|
|
view.RemoteCacheSameZoneTimeout = proto.DefaultRemoteCacheSameZoneTimeout
|
|
}
|
|
if view.RemoteCacheSameRegionTimeout <= 0 {
|
|
view.RemoteCacheSameRegionTimeout = proto.DefaultRemoteCacheSameRegionTimeout
|
|
}
|
|
if rc.VolumeEnabled != view.RemoteCacheEnable {
|
|
log.LogInfof("RcVolumeEnabled: %v -> %v", rc.VolumeEnabled, view.RemoteCacheEnable)
|
|
rc.VolumeEnabled = view.RemoteCacheEnable
|
|
}
|
|
|
|
// check if RemoteCache.ClusterEnabled is set to true after it has been set to false last time
|
|
if !client.RemoteCache.ClusterEnabled && rc.mc != nil {
|
|
if fgv, err := rc.mc.AdminAPI().ClientFlashGroups(); err != nil {
|
|
log.LogWarnf("updateFlashGroups: err(%v)", err)
|
|
return
|
|
} else {
|
|
rc.clusterEnable(fgv.Enable && len(fgv.FlashGroups) != 0)
|
|
}
|
|
}
|
|
|
|
// RemoteCache may be nil if the first initialization failed, it will not be set nil anymore even if remote cache is disabled
|
|
if client.IsRemoteCacheEnabled() {
|
|
if !rc.Started {
|
|
log.LogInfof("UpdateRemoteCacheConfig: initRemoteCache")
|
|
if err := rc.Init(client); err != nil {
|
|
log.LogErrorf("updateRemoteCacheConfig: initRemoteCache failed, err: %v", err)
|
|
return
|
|
}
|
|
}
|
|
} else if rc.Started {
|
|
client.RemoteCache.Stop()
|
|
log.LogInfo("stop RemoteCache")
|
|
}
|
|
|
|
if rc.Path != view.RemoteCachePath {
|
|
oldPath := client.RemoteCache.Path
|
|
rc.Path = view.RemoteCachePath
|
|
log.LogInfof("RcPath: %v -> %v, but(%v)", oldPath, view.RemoteCachePath, rc.Path)
|
|
}
|
|
|
|
if rc.AutoPrepare != view.RemoteCacheAutoPrepare {
|
|
log.LogInfof("RcAutoPrepare: %v -> %v", rc.AutoPrepare, view.RemoteCacheAutoPrepare)
|
|
rc.AutoPrepare = view.RemoteCacheAutoPrepare
|
|
}
|
|
if rc.TTL != view.RemoteCacheTTL {
|
|
log.LogInfof("RcTTL: %d -> %d", rc.TTL, view.RemoteCacheTTL)
|
|
rc.TTL = view.RemoteCacheTTL
|
|
}
|
|
if rc.ReadTimeout != view.RemoteCacheReadTimeout {
|
|
log.LogInfof("RcReadTimeoutSec: %d(ms) -> %d(ms)", rc.ReadTimeout, view.RemoteCacheReadTimeout)
|
|
rc.ReadTimeout = view.RemoteCacheReadTimeout
|
|
}
|
|
|
|
if rc.remoteCacheMaxFileSizeGB != view.RemoteCacheMaxFileSizeGB {
|
|
log.LogInfof("RcMaxFileSizeGB: %d(GB) -> %d(GB)", rc.remoteCacheMaxFileSizeGB, view.RemoteCacheMaxFileSizeGB)
|
|
rc.remoteCacheMaxFileSizeGB = view.RemoteCacheMaxFileSizeGB
|
|
}
|
|
|
|
if rc.remoteCacheOnlyForNotSSD != view.RemoteCacheOnlyForNotSSD {
|
|
log.LogInfof("RcOnlyForNotSSD: %v -> %v", rc.remoteCacheOnlyForNotSSD, view.RemoteCacheOnlyForNotSSD)
|
|
rc.remoteCacheOnlyForNotSSD = view.RemoteCacheOnlyForNotSSD
|
|
}
|
|
|
|
if rc.remoteCacheMultiRead != view.RemoteCacheMultiRead {
|
|
log.LogInfof("RcFollowerRead: %v -> %v", rc.remoteCacheMultiRead, view.RemoteCacheMultiRead)
|
|
rc.remoteCacheMultiRead = view.RemoteCacheMultiRead
|
|
}
|
|
|
|
if rc.flashNodeTimeoutCount != int32(view.FlashNodeTimeoutCount) {
|
|
log.LogInfof("RcFlashNodeTimeoutCount: %d -> %d", rc.flashNodeTimeoutCount, int32(view.FlashNodeTimeoutCount))
|
|
rc.flashNodeTimeoutCount = int32(view.FlashNodeTimeoutCount)
|
|
}
|
|
if rc.sameZoneTimeout != view.RemoteCacheSameZoneTimeout {
|
|
log.LogInfof("RcSameZoneTimeout: %d -> %d", rc.sameZoneTimeout, view.RemoteCacheSameZoneTimeout)
|
|
rc.sameZoneTimeout = view.RemoteCacheSameZoneTimeout
|
|
}
|
|
if rc.sameRegionTimeout != view.RemoteCacheSameRegionTimeout {
|
|
log.LogInfof("RcSameRegionTimeout: %d -> %d", rc.sameRegionTimeout, view.RemoteCacheSameRegionTimeout)
|
|
rc.sameRegionTimeout = view.RemoteCacheSameRegionTimeout
|
|
}
|
|
}
|
|
|
|
func (rc *RemoteCache) DoRemoteCachePrepare(c *ExtentClient) {
|
|
defer c.wg.Done()
|
|
workerWg := sync.WaitGroup{}
|
|
for range [5]struct{}{} {
|
|
workerWg.Add(1)
|
|
go func() {
|
|
defer workerWg.Done()
|
|
for {
|
|
select {
|
|
case <-c.stopCh:
|
|
return
|
|
case req := <-c.RemoteCache.PrepareCh:
|
|
c.servePrepareRequest(req)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
workerWg.Wait()
|
|
}
|
|
|
|
func (rc *RemoteCache) Init(client *ExtentClient) (err error) {
|
|
rc.lock.Lock()
|
|
defer rc.lock.Unlock()
|
|
if rc.Started {
|
|
log.LogInfof("RemoteCache already started")
|
|
return
|
|
}
|
|
|
|
log.LogDebugf("RemoteCache: Init")
|
|
fmt.Println("RemoteCache: Init")
|
|
rc.stopC = make(chan struct{})
|
|
rc.cluster = client.dataWrapper.ClusterName
|
|
rc.volname = client.extentConfig.Volume
|
|
rc.metaWrapper = client.metaWrapper
|
|
rc.flashGroups = btree.New(32)
|
|
rc.clusterEnable = client.enableRemoteCacheCluster
|
|
rc.mc = master.NewMasterClient(client.extentConfig.Masters, false)
|
|
|
|
err = rc.updateFlashGroups()
|
|
if err != nil {
|
|
log.LogDebugf("RemoteCache: Init err %v", err)
|
|
return
|
|
}
|
|
rc.conns = util.NewConnectPoolWithTimeoutAndCap(5, 500, _connIdelTimeout, 1)
|
|
rc.cacheBloom = bloom.New(BloomBits, BloomHashNum)
|
|
rc.wg.Add(1)
|
|
go rc.refresh()
|
|
|
|
rc.PrepareCh = make(chan *PrepareRemoteCacheRequest, 1024)
|
|
client.wg.Add(1)
|
|
go rc.DoRemoteCachePrepare(client)
|
|
rc.Started = true
|
|
|
|
log.LogDebugf("Init: NewRemoteCache sucess")
|
|
return
|
|
}
|
|
|
|
func (rc *RemoteCache) Read(ctx context.Context, fg *FlashGroup, inode uint64, req *CacheReadRequest) (read int, err error) {
|
|
var (
|
|
conn *net.TCPConn
|
|
moved bool
|
|
addr string
|
|
reqPacket *Packet
|
|
)
|
|
bgTime := stat.BeginStat()
|
|
defer func() {
|
|
forceClose := err != nil && !proto.IsFlashNodeLimitError(err)
|
|
rc.conns.PutConnect(conn, forceClose)
|
|
|
|
parts := strings.Split(addr, ":")
|
|
if len(parts) > 0 {
|
|
stat.EndStat(fmt.Sprintf("flashNode:%v", parts[0]), err, bgTime, 1)
|
|
}
|
|
stat.EndStat("flashNode", err, bgTime, 1)
|
|
}()
|
|
for {
|
|
addr = fg.getFlashHost()
|
|
if addr == "" {
|
|
err = fmt.Errorf("getFlashHost failed: cannot find any available host")
|
|
log.LogWarnf("FlashGroup Read failed: fg(%v) err(%v)", fg, err)
|
|
return
|
|
}
|
|
reqPacket = NewFlashCachePacket(inode, proto.OpFlashNodeCacheRead)
|
|
if err = reqPacket.MarshalDataPb(&req.CacheReadRequest); 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
|
|
}
|
|
if read, err = rc.getReadReply(conn, reqPacket, req); err != nil {
|
|
// TODO: may try other replica in future
|
|
if proto.IsFlashNodeLimitError(err) {
|
|
break
|
|
}
|
|
log.LogWarnf("FlashGroup Read: getReadReply from 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 getReadReply from addr(%v) failure err(%v)", addr, err)
|
|
continue
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
func (rc *RemoteCache) getReadReply(conn *net.TCPConn, reqPacket *Packet, req *CacheReadRequest) (readBytes int, err error) {
|
|
for readBytes < int(req.Size_) {
|
|
replyPacket := NewFlashCacheReply()
|
|
start := time.Now()
|
|
err = replyPacket.ReadFromConnExt(conn, int(rc.ReadTimeout))
|
|
if err != nil {
|
|
log.LogWarnf("getReadReply: failed to read from connect, req(%v) readBytes(%v) err(%v) cost %v ReadTimeout %v",
|
|
reqPacket, readBytes, err, time.Since(start).String(), rc.ReadTimeout)
|
|
return
|
|
}
|
|
if replyPacket.ResultCode != proto.OpOk {
|
|
err = fmt.Errorf("%v", string(replyPacket.Data))
|
|
if !proto.IsFlashNodeLimitError(err) {
|
|
log.LogWarnf("getReadReply: ResultCode NOK, req(%v) reply(%v) ResultCode(%v)", reqPacket, replyPacket, replyPacket.ResultCode)
|
|
}
|
|
return
|
|
}
|
|
expectCrc := crc32.ChecksumIEEE(replyPacket.Data[:replyPacket.Size])
|
|
if replyPacket.CRC != expectCrc {
|
|
err = fmt.Errorf("inconsistent CRC, expect(%v) reply(%v)", expectCrc, replyPacket.CRC)
|
|
log.LogWarnf("getReadReply: req(%v) err(%v)", req, err)
|
|
return
|
|
}
|
|
copy(req.Data[readBytes:], replyPacket.Data)
|
|
readBytes += int(replyPacket.Size)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (rc *RemoteCache) Prepare(ctx context.Context, fg *FlashGroup, inode uint64, req *proto.CachePrepareRequest) (err error) {
|
|
var (
|
|
conn *net.TCPConn
|
|
moved bool
|
|
)
|
|
addr := fg.getFlashHost()
|
|
if addr == "" {
|
|
err = fmt.Errorf("getFlashHost failed: can not find host")
|
|
log.LogWarnf("FlashGroup prepare failed: err(%v)", err)
|
|
return
|
|
}
|
|
reqPacket := NewFlashCachePacket(inode, proto.OpFlashNodeCachePrepare)
|
|
if err = reqPacket.MarshalDataPb(req); err != nil {
|
|
log.LogWarnf("FlashGroup Prepare: failed to MarshalData (%v), err(%v)", req, err)
|
|
return
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
moved = fg.moveToUnknownRank(addr, err, rc.flashNodeTimeoutCount)
|
|
}
|
|
}()
|
|
if conn, err = rc.conns.GetConnect(addr); err != nil {
|
|
log.LogWarnf("FlashGroup prepare: get connection to curr addr failed, addr(%v) reqPacket(%v) err(%v)", addr, req, err)
|
|
return
|
|
}
|
|
defer func() {
|
|
rc.conns.PutConnect(conn, err != nil)
|
|
}()
|
|
|
|
if err = reqPacket.WriteToConn(conn); err != nil {
|
|
log.LogWarnf("FlashGroup Prepare: failed to write to addr(%v) err(%v)", addr, err)
|
|
return
|
|
}
|
|
|
|
replyPacket := NewFlashCacheReply()
|
|
if err = replyPacket.ReadFromConnExt(conn, int(rc.ReadTimeout)); err != nil {
|
|
log.LogWarnf("FlashGroup Prepare: failed to ReadFromConn, replyPacket(%v), fg host(%v) moved(%v), err(%v)", replyPacket, addr, moved, err)
|
|
return
|
|
}
|
|
if replyPacket.ResultCode != proto.OpOk {
|
|
log.LogWarnf("FlashGroup Prepare: ResultCode NOK, replyPacket(%v), fg host(%v), ResultCode(%v)", replyPacket, addr, replyPacket.ResultCode)
|
|
err = fmt.Errorf("ResultCode NOK (%v)", replyPacket.ResultCode)
|
|
return
|
|
}
|
|
log.LogDebugf("FlashGroup Prepare successful: flashGroup(%v) addr(%v) CachePrepareRequest(%v) reqPacket(%v) replyPacket(%v) err(%v) moved(%v)", fg, addr, req, reqPacket, replyPacket, err, moved)
|
|
return
|
|
}
|
|
|
|
func (rc *RemoteCache) Stop() {
|
|
rc.stopOnce.Do(func() {
|
|
rc.Started = false
|
|
close(rc.stopC)
|
|
rc.conns.Close()
|
|
rc.wg.Wait()
|
|
})
|
|
}
|
|
|
|
func (rc *RemoteCache) GetRemoteCacheBloom() *bloom.BloomFilter {
|
|
log.LogDebugf("GetRemoteCacheBloom. cacheBloom %v", rc.cacheBloom)
|
|
return rc.cacheBloom
|
|
}
|
|
|
|
func (rc *RemoteCache) ResetPathToBloom(cachePath string) bool {
|
|
if cachePath == "" {
|
|
cachePath = "/"
|
|
}
|
|
res := true
|
|
rc.cacheBloom.ClearAll()
|
|
for _, path := range strings.Split(cachePath, cachePathSeparator) {
|
|
path = strings.TrimSpace(path)
|
|
if len(path) == 0 {
|
|
continue
|
|
}
|
|
if ino, err := rc.getPathInode(path); err != nil {
|
|
log.LogWarnf("RemoteCache: lookup cachePath %s err: %v", path, err)
|
|
res = false
|
|
continue
|
|
} else {
|
|
n := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(n, ino)
|
|
rc.cacheBloom.Add(n)
|
|
log.LogDebugf("RemoteCache: add path %s, inode %d to bloomFilter", path, ino)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (rc *RemoteCache) getPathInode(path string) (ino uint64, err error) {
|
|
ino = proto.RootIno
|
|
if path == "/" {
|
|
return ino, nil
|
|
}
|
|
if path != "" && path != "/" {
|
|
dirs := strings.Split(path, "/")
|
|
var childIno uint64
|
|
for _, dir := range dirs {
|
|
if dir == "/" || dir == "" {
|
|
continue
|
|
}
|
|
childIno, _, err = rc.metaWrapper.Lookup_ll(ino, dir)
|
|
if err != nil {
|
|
ino = 0
|
|
return
|
|
}
|
|
ino = childIno
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (rc *RemoteCache) refresh() {
|
|
defer rc.wg.Done()
|
|
for {
|
|
err := rc.refreshWithRecover()
|
|
if err == nil {
|
|
log.LogInfof("refresh: exit")
|
|
break
|
|
}
|
|
log.LogErrorf("refresh: err(%v) try next update", err)
|
|
}
|
|
}
|
|
|
|
func (rc *RemoteCache) refreshWithRecover() (panicErr error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.LogErrorf("refreshFlashNode panic: err(%v) stack(%v)", r, string(debug.Stack()))
|
|
msg := fmt.Sprintf("refreshFlashNode panic: err(%v)", r)
|
|
panicErr = errors.New(msg)
|
|
}
|
|
}()
|
|
|
|
refreshView := time.NewTicker(RefreshFlashNodesInterval)
|
|
defer refreshView.Stop()
|
|
|
|
refreshLatency := time.NewTimer(0)
|
|
defer refreshLatency.Stop()
|
|
|
|
var err error
|
|
for {
|
|
select {
|
|
case <-rc.stopC:
|
|
log.LogInfof("refreshWithRecover: remote stop")
|
|
return
|
|
case <-refreshView.C:
|
|
if err = rc.updateFlashGroups(); err != nil {
|
|
log.LogErrorf("updateFlashGroups err: %v", err)
|
|
}
|
|
case <-refreshLatency.C:
|
|
rc.refreshHostLatency()
|
|
refreshLatency.Reset(RefreshHostLatencyInterval)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (rc *RemoteCache) updateFlashGroups() (err error) {
|
|
var (
|
|
fgv proto.FlashGroupView
|
|
newFlashGroups = btree.New(32)
|
|
)
|
|
if fgv, err = rc.mc.AdminAPI().ClientFlashGroups(); err != nil {
|
|
log.LogWarnf("updateFlashGroups: err(%v)", err)
|
|
return
|
|
}
|
|
log.LogDebugf("updateFlashGroups. get flashGroupView [%v]", fgv)
|
|
rc.clusterEnable(fgv.Enable && len(fgv.FlashGroups) != 0)
|
|
if !fgv.Enable {
|
|
rc.flashGroups = newFlashGroups
|
|
return
|
|
}
|
|
|
|
for _, fg := range fgv.FlashGroups {
|
|
newAdded := make([]string, 0)
|
|
for _, host := range fg.Hosts {
|
|
if _, ok := rc.hostLatency.Load(host); !ok {
|
|
newAdded = append(newAdded, host)
|
|
}
|
|
}
|
|
log.LogDebugf("updateFlashGroups: fgID(%v) newAdded hosts: %v", fg.ID, newAdded)
|
|
|
|
rc.updateHostLatency(newAdded)
|
|
sortedHosts := rc.ClassifyHostsByAvgDelay(fg.ID, fg.Hosts)
|
|
|
|
flashGroup := NewFlashGroup(fg, sortedHosts)
|
|
for _, slot := range fg.Slot {
|
|
slotItem := &SlotItem{
|
|
slot: slot,
|
|
FlashGroup: flashGroup,
|
|
}
|
|
newFlashGroups.ReplaceOrInsert(slotItem)
|
|
}
|
|
}
|
|
rc.flashGroups = newFlashGroups
|
|
|
|
return
|
|
}
|
|
|
|
func (rc *RemoteCache) ClassifyHostsByAvgDelay(fgID uint64, hosts []string) (sortedHosts map[ZoneRankType][]string) {
|
|
sortedHosts = make(map[ZoneRankType][]string)
|
|
sameZoneTimeout := time.Duration(rc.sameZoneTimeout) * time.Microsecond
|
|
sameRegionTimeout := time.Duration(rc.sameRegionTimeout) * time.Millisecond
|
|
for _, host := range hosts {
|
|
avgTime := time.Duration(0)
|
|
v, ok := rc.hostLatency.Load(host)
|
|
if ok {
|
|
avgTime = v.(time.Duration)
|
|
}
|
|
if avgTime <= time.Duration(0) {
|
|
sortedHosts[UnknownZoneRank] = append(sortedHosts[UnknownZoneRank], host)
|
|
auditlog.LogOpMsg("ClassifyHostsByAvgDelay", fmt.Sprintf("add host %v to unknown region", host), nil)
|
|
} else if avgTime <= sameZoneTimeout {
|
|
sortedHosts[SameZoneRank] = append(sortedHosts[SameZoneRank], host)
|
|
} else if avgTime <= sameRegionTimeout {
|
|
sortedHosts[SameRegionRank] = append(sortedHosts[SameRegionRank], host)
|
|
} else {
|
|
sortedHosts[CrossRegionRank] = append(sortedHosts[CrossRegionRank], host)
|
|
auditlog.LogOpMsg("ClassifyHostsByAvgDelay", fmt.Sprintf("add host %v to cross region by time %v", host, avgTime.String()), nil)
|
|
}
|
|
}
|
|
log.LogInfof("ClassifyHostsByAvgDelay: fgID(%v) sortedHost:%v", fgID, sortedHosts)
|
|
return sortedHosts
|
|
}
|
|
|
|
func (rc *RemoteCache) resetFlashNodeTimeoutCount() {
|
|
fgSet := make([]uint64, 0)
|
|
|
|
isInSet := func(fg uint64, fgSet []uint64) bool {
|
|
for _, v := range fgSet {
|
|
if v == fg {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
rangeFunc := func(i btree.Item) bool {
|
|
item := i.(*SlotItem)
|
|
fg := item.FlashGroup
|
|
|
|
if isInSet(fg.ID, fgSet) {
|
|
return true
|
|
}
|
|
fgSet = append(fgSet, fg.ID)
|
|
fg.hostLock.Lock()
|
|
for addr := range fg.hostTimeoutCount {
|
|
if fg.hostTimeoutCount[addr] == 0 {
|
|
continue
|
|
}
|
|
// Check if the timeout host ping was successful
|
|
// if it was successful and within the sameZone and sameRegion ranges, reset the host timeout count to 0
|
|
v, ok := rc.hostLatency.Load(addr)
|
|
if ok {
|
|
avgTime := v.(time.Duration)
|
|
if avgTime > 0 && avgTime < time.Millisecond*time.Duration(rc.sameRegionTimeout) {
|
|
log.LogDebugf("resetFlashNodeTimeoutCount fgId(%v) flashnode(%v) from %v to 0",
|
|
fg.ID, addr, fg.hostTimeoutCount[addr])
|
|
fg.hostTimeoutCount[addr] = 0
|
|
}
|
|
}
|
|
}
|
|
fg.hostLock.Unlock()
|
|
return true
|
|
}
|
|
rc.rangeFlashGroups(nil, rangeFunc)
|
|
}
|
|
|
|
func (rc *RemoteCache) refreshHostLatency() {
|
|
hosts := rc.getFlashHostsMap()
|
|
|
|
needPings := make([]string, 0)
|
|
rc.hostLatency.Range(func(key, value interface{}) bool {
|
|
host := key.(string)
|
|
if _, exist := hosts[host]; !exist {
|
|
rc.hostLatency.Delete(host)
|
|
log.LogInfof("remove flashNode(%v)", host)
|
|
} else {
|
|
needPings = append(needPings, host)
|
|
}
|
|
return true
|
|
})
|
|
rc.updateHostLatency(needPings)
|
|
log.LogDebugf("updateHostLatencyByLatency: needPings(%v)", len(needPings))
|
|
}
|
|
|
|
func (rc *RemoteCache) updateHostLatency(hosts []string) {
|
|
for _, host := range hosts {
|
|
avgRtt, err := iputil.PingWithTimeout(strings.Split(host, ":")[0], pingCount, time.Millisecond*time.Duration(rc.ReadTimeout))
|
|
if err == nil {
|
|
v, _ := rc.AddressPingMap.LoadOrStore(host, &AddressPingStats{})
|
|
aps := v.(*AddressPingStats)
|
|
aps.Add(avgRtt)
|
|
rc.hostLatency.Store(host, aps.Average())
|
|
log.LogInfof("updateHostLatency: host(%v) avgRtt(%v)", host, avgRtt.String())
|
|
} else {
|
|
rangeFunc := func(i btree.Item) bool {
|
|
item := i.(*SlotItem)
|
|
fg := item.FlashGroup
|
|
fg.hostLock.Lock()
|
|
defer fg.hostLock.Unlock()
|
|
for _, h := range fg.Hosts {
|
|
if h == host {
|
|
fg.hostTimeoutCount[host]++
|
|
if fg.hostTimeoutCount[host] >= rc.flashNodeTimeoutCount {
|
|
rc.hostLatency.Delete(host)
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
rc.rangeFlashGroups(nil, rangeFunc)
|
|
log.LogWarnf("updateHostLatency: host(%v) err(%v)", host, err)
|
|
}
|
|
}
|
|
rc.resetFlashNodeTimeoutCount()
|
|
}
|
|
|
|
func (rc *RemoteCache) GetFlashGroupBySlot(slot uint32) (*FlashGroup, uint32) {
|
|
var item *SlotItem
|
|
|
|
pivot := &SlotItem{slot: slot}
|
|
rangeFunc := func(i btree.Item) bool {
|
|
item = i.(*SlotItem)
|
|
return false
|
|
}
|
|
rc.rangeFlashGroups(pivot, rangeFunc)
|
|
|
|
if item == nil {
|
|
return rc.getMinFlashGroup()
|
|
}
|
|
return item.FlashGroup, item.slot
|
|
}
|
|
|
|
func (rc *RemoteCache) getFlashHostsMap() map[string]bool {
|
|
allHosts := make(map[string]bool)
|
|
|
|
rangeFunc := func(i btree.Item) bool {
|
|
fgItem := i.(*SlotItem)
|
|
for _, host := range fgItem.FlashGroup.Hosts {
|
|
allHosts[host] = true
|
|
}
|
|
return true
|
|
}
|
|
rc.rangeFlashGroups(nil, rangeFunc)
|
|
|
|
return allHosts
|
|
}
|
|
|
|
type CacheReadRequest struct {
|
|
proto.CacheReadRequest
|
|
Data []byte
|
|
}
|
|
|
|
func (rc *RemoteCache) rangeFlashGroups(pivot *SlotItem, rangeFunc func(item btree.Item) bool) {
|
|
flashGroups := rc.flashGroups
|
|
|
|
if pivot == nil {
|
|
flashGroups.Ascend(rangeFunc)
|
|
} else {
|
|
flashGroups.AscendGreaterOrEqual(pivot, rangeFunc)
|
|
}
|
|
}
|
|
|
|
func (rc *RemoteCache) getMinFlashGroup() (*FlashGroup, uint32) {
|
|
flashGroups := rc.flashGroups
|
|
|
|
if flashGroups.Len() > 0 {
|
|
item := flashGroups.Min().(*SlotItem)
|
|
if item != nil {
|
|
return item.FlashGroup, item.slot
|
|
}
|
|
}
|
|
return nil, 0
|
|
}
|