feat(client): "client support aheadread"

Signed-off-by: yanbin027 <yanbin027@ke.com>
This commit is contained in:
yanbin027 2025-02-13 15:33:31 +08:00 committed by leonrayang
parent c918d5cfce
commit f25c5b9cc7
12 changed files with 593 additions and 14 deletions

View File

@ -250,6 +250,11 @@ func NewSuper(opt *proto.MountOptions) (s *Super, err error) {
OnGetInodeInfo: s.InodeGet,
BcacheOnlyForNotSSD: opt.BcacheOnlyForNotSSD,
AheadReadEnable: opt.AheadReadEnable,
AheadReadTotalMem: opt.AheadReadTotalMem,
AheadReadBlockTimeOut: opt.AheadReadBlockTimeOut,
AheadReadWindowCnt: opt.AheadReadWindowCnt,
}
s.ec, err = stream.NewExtentClient(extentConfig)

View File

@ -970,6 +970,26 @@ func parseMountOption(cfg *config.Config) (*proto.MountOptions, error) {
opt.DisableMountSubtype = GlobalMountOptions[proto.DisableMountSubtype].GetBool()
opt.StreamRetryTimeout = int(GlobalMountOptions[proto.StreamRetryTimeOut].GetInt64())
opt.AheadReadEnable = GlobalMountOptions[proto.AheadReadEnable].GetBool()
if opt.AheadReadEnable {
var (
total uint64
used uint64
available int64
)
opt.AheadReadBlockTimeOut = int(GlobalMountOptions[proto.AheadReadBlockTimeOut].GetInt64())
opt.AheadReadWindowCnt = int(GlobalMountOptions[proto.AheadReadWindowCnt].GetInt64())
opt.AheadReadTotalMem = GlobalMountOptions[proto.AheadReadTotalMemGB].GetInt64() * util.GB
total, used, err = util.GetMemInfo()
if err != nil {
return nil, err
}
available = int64((total - used) / 2)
if available < opt.AheadReadTotalMem {
opt.AheadReadTotalMem = available
fmt.Printf("available ahead read mem: %v\n", available)
}
}
if opt.MountPoint == "" || opt.Volname == "" || opt.Owner == "" || opt.Master == "" {
return nil, errors.New(fmt.Sprintf("invalid config file: lack of mandatory fields, mountPoint(%v), volName(%v), owner(%v), masterAddr(%v)", opt.MountPoint, opt.Volname, opt.Owner, opt.Master))
}

View File

@ -588,24 +588,17 @@ func (dp *DataPartition) NormalExtentRepairRead(p repl.PacketInterface, connect
err = nil
reply := makeRspPacket(p.GetReqID(), p.GetPartitionID(), p.GetExtentID())
reply.SetStartT(p.GetStartT())
currReadSize := uint32(util.Min(int(needReplySize), int(dp.GetRepairBlockSize())))
if currReadSize == util.RepairReadBlockSize {
var currReadSize uint32
if currReadSize == util.RepairReadBlockSize || currReadSize == util.BlockSize || currReadSize == util.CacheReadBlockSize {
var data []byte
data, err = proto.Buffers.Get(util.RepairReadBlockSize)
if err != nil {
log.LogErrorf("[NormalExtentRepairRead] dp(%v) failed to get repair data, err(%v)", dp.partitionID, err)
return
}
reply.SetData(data)
} else if currReadSize == util.BlockSize {
var data []byte
data, err = proto.Buffers.Get(util.BlockSize)
data, err = proto.Buffers.Get(int(currReadSize))
if err != nil {
log.LogErrorf("[NormalExtentRepairRead] dp(%v) failed to get repair data, err(%v)", dp.partitionID, err)
return
}
reply.SetData(data)
} else {
currReadSize = uint32(util.Min(int(needReplySize), int(dp.GetRepairBlockSize())))
reply.SetData(bytespool.Alloc(int(currReadSize)))
}
if !shallDegrade {
@ -644,7 +637,7 @@ func (dp *DataPartition) NormalExtentRepairRead(p repl.PacketInterface, connect
}
needReplySize -= currReadSize
offset += int64(currReadSize)
if currReadSize == util.ReadBlockSize || currReadSize == util.RepairReadBlockSize {
if currReadSize == util.ReadBlockSize || currReadSize == util.RepairReadBlockSize || currReadSize == util.CacheReadBlockSize {
proto.Buffers.Put(reply.GetData())
} else {
bytespool.Free(reply.GetData())

View File

@ -0,0 +1,33 @@
# 预读支持副本模式不支持EC
修改内核预读参数(适用于内核未做优化情况)
``` bash
vi /etc/udev/rules.d/99-bdi.rules
ACTION=="add", SUBSYSTEM=="bdi", RUN+="/usr/local/bin/bdi_add_script %k"
sudo udevadm control --reload-rules
vi /usr/local/bin/bdi_add_script
#!/bin/bash
device=$1
echo "`date '+%F %T'` FUSE device added: $device" >> /tmp/fuse.log
if [[ "$device" =~ "0:" ]];then
echo 2048 > /sys/devices/virtual/bdi/$device/read_ahead_kb
fi
chmod +x /usr/local/bin/bdi_add_script
```
配置文件参数含义如下表示:
参数 | 类型 | 含义 | 必需 |
|--------------|-------|-------------------------------------|----|
| aheadReadEnable | bool | 是否开启预读 | 是 |
| aheadReadTotalMemGB | int64 | 预读占用内存(默认10:GB)若不够10G则占用当前可用内存的50% | 否 |
| aheadReadBlockTimeOut | int64 | 缓存块未命中的回收时间(默认3s) | 否 |
| aheadReadWindowCnt | int64 | 缓存滑动窗口的大小(默认8) | 否 |
![img.png](img.png)
::: tip 提示:
1、开启预读会占用一定的客户端内存对于客户端内存限制高的场景可以调节aheadReadTotalMemGB的大小但是性能会有一定的衰减
2、预读只对大于4M的文件有效如果和bcache同时开启会优先执行预读
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

View File

@ -79,6 +79,11 @@ const (
StreamRetryTimeOut
BufferChanSize
BcacheOnlyForNotSSD
// aheadread
AheadReadEnable
AheadReadTotalMemGB
AheadReadBlockTimeOut
AheadReadWindowCnt
MaxMountOption
)
@ -177,6 +182,11 @@ func InitMountOptions(opts []MountOption) {
opts[StreamRetryTimeOut] = MountOption{"streamRetryTimeout", "max stream retry timeout, s", "", int64(0)}
opts[BcacheOnlyForNotSSD] = MountOption{"enableBcacheOnlyForNotSSD", "Enable block cache only for not ssd", "", false}
opts[AheadReadEnable] = MountOption{"aheadReadEnable", "enable ahead read", "", false}
opts[AheadReadTotalMemGB] = MountOption{"aheadReadTotalMemGB", "ahead read total mem(GB)", "", int64(10)}
opts[AheadReadBlockTimeOut] = MountOption{"aheadReadBlockTimeOut", "ahead read block expiration time", "", int64(3)}
opts[AheadReadWindowCnt] = MountOption{"aheadReadWindowCnt", "ahead read window block count", "", int64(8)}
for i := 0; i < MaxMountOption; i++ {
flag.StringVar(&opts[i].cmdlineValue, opts[i].keyword, "", opts[i].description)
}
@ -350,4 +360,9 @@ type MountOptions struct {
VolStorageClass uint32
VolAllowedStorageClass []uint32
VolCacheDpStorageClass uint32
AheadReadEnable bool
AheadReadTotalMem int64
AheadReadBlockTimeOut int
AheadReadWindowCnt int
}

View File

@ -146,6 +146,11 @@ type ExtentConfig struct {
OnGetInodeInfo GetInodeInfoFunc
BcacheOnlyForNotSSD bool
AheadReadEnable bool
AheadReadTotalMem int64
AheadReadBlockTimeOut int
AheadReadWindowCnt int
}
type MultiVerMgr struct {
@ -191,6 +196,7 @@ type ExtentClient struct {
getInodeInfo GetInodeInfoFunc
bcacheOnlyForNotSSD bool
InnerReq bool
AheadRead *AheadReadCache
}
func (client *ExtentClient) UidIsLimited(uid uint32) bool {
@ -344,6 +350,7 @@ retry:
}
client.readLimiter = rate.NewLimiter(readLimit, defaultReadLimitBurst)
client.writeLimiter = rate.NewLimiter(writeLimit, defaultWriteLimitBurst)
client.AheadRead = NewAheadReadCache(config.AheadReadEnable, config.AheadReadTotalMem, config.AheadReadBlockTimeOut, config.AheadReadWindowCnt)
if config.MaxStreamerLimit <= 0 {
client.disableMetaCache = true

View File

@ -0,0 +1,437 @@
// Copyright 2025 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 (
"fmt"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/cubefs/cubefs/proto"
"github.com/cubefs/cubefs/sdk/data/wrapper"
"github.com/cubefs/cubefs/util"
"github.com/cubefs/cubefs/util/btree"
"github.com/cubefs/cubefs/util/log"
)
type AheadReadCache struct {
enable bool
blockTimeOut int
winCnt int
availableBlockC chan *AheadReadBlock
availableBlockCnt int64
totalBlockCnt int64
stopC chan interface{}
blockCache *sync.Map
}
type AheadReadBlock struct {
inode uint64
partitionId uint64
extentId uint64
offset uint64
size uint64
data []byte
time int64
}
type AheadReadTask struct {
p *Packet
dnHosts []string
time time.Time
req *ExtentRequest
cacheSize int
}
type AheadReadWindow struct {
taskC chan *AheadReadTask
curTaskMap map[string]interface{}
curTaskMutex sync.RWMutex
cache *AheadReadCache
streamer *Streamer
canAheadRead bool
}
func NewAheadReadCache(enable bool, totalMem int64, blockTimeOut, winCnt int) *AheadReadCache {
if !enable {
return nil
}
arc := &AheadReadCache{
enable: enable,
blockTimeOut: blockTimeOut,
winCnt: winCnt,
stopC: make(chan interface{}),
blockCache: new(sync.Map),
totalBlockCnt: totalMem / util.CacheReadBlockSize,
}
atomic.StoreInt64(&arc.availableBlockCnt, arc.totalBlockCnt)
arc.availableBlockC = make(chan *AheadReadBlock, arc.availableBlockCnt)
for i := int64(0); i < arc.totalBlockCnt; i++ {
block := &AheadReadBlock{
data: make([]byte, util.CacheReadBlockSize),
}
arc.availableBlockC <- block
}
log.LogInfof("aheadRead enable(%v) totalMem(%v) availableBlockCnt(%v) winCnt(%v)", enable, totalMem, arc.availableBlockCnt, winCnt)
go arc.checkBlockTimeOut()
return arc
}
func (arc *AheadReadCache) getAheadReadBlock() (block *AheadReadBlock, err error) {
select {
case block = <-arc.availableBlockC:
atomic.AddInt64(&arc.availableBlockCnt, -1)
default:
err = fmt.Errorf("availableBlockC is nil")
}
return
}
func (arc *AheadReadCache) putAheadReadBlock(block *AheadReadBlock) {
arc.availableBlockC <- block
atomic.AddInt64(&arc.availableBlockCnt, 1)
}
func (arc *AheadReadCache) checkBlockTimeOut() {
blockTimer := time.NewTimer(time.Second)
printTicker := time.NewTicker(time.Second * 10)
for {
select {
case <-arc.stopC:
blockTimer.Stop()
printTicker.Stop()
return
case <-blockTimer.C:
blockTimer.Stop()
arc.doCheckBlockTimeOut()
blockTimer.Reset(time.Second)
case <-printTicker.C:
log.LogInfof("totalAheadReadBlockCnt(%v) curAvailableCnt(%v)", arc.totalBlockCnt, atomic.LoadInt64(&arc.availableBlockCnt))
}
}
}
func (arc *AheadReadCache) doCheckBlockTimeOut() {
curTime := time.Now().Unix()
arc.blockCache.Range(func(key, value interface{}) bool {
bv := value.(*AheadReadBlock)
if curTime-bv.time > int64(arc.blockTimeOut) {
arc.blockCache.Delete(key)
arc.putAheadReadBlock(bv)
}
return true
})
}
func (arc *AheadReadCache) Stop() {
close(arc.stopC)
close(arc.availableBlockC)
arc.blockCache.Range(func(key, value interface{}) bool {
arc.blockCache.Delete(key)
return true
})
}
func NewAheadReadWindow(arc *AheadReadCache, s *Streamer) *AheadReadWindow {
arw := &AheadReadWindow{
taskC: make(chan *AheadReadTask, arc.winCnt),
curTaskMap: make(map[string]interface{}),
cache: arc,
streamer: s,
}
go arw.backgroundAheadReadTask()
return arw
}
func (arw *AheadReadWindow) backgroundAheadReadTask() {
for task := range arw.taskC {
go arw.doTask(task)
}
}
func (arw *AheadReadWindow) doTask(task *AheadReadTask) {
var (
err error
hosts = task.dnHosts
readBytes int
)
key := fmt.Sprintf("%v-%v-%v-%v", task.p.inode, task.p.PartitionID, task.p.ExtentID, task.p.ExtentOffset)
if arw.putTaskIfNotExist(key) {
return
}
defer func() {
arw.deleteTask(key)
log.LogDebugf("aheadRead step: fetch, key(%v) size(%v) err(%v) %v", key, task.cacheSize, err, time.Since(task.time))
}()
if _, ok := arw.cache.blockCache.Load(key); ok {
return
}
cacheBlock, err := arw.cache.getAheadReadBlock()
if err != nil {
return
}
cacheBlock.inode = task.p.inode
cacheBlock.partitionId = task.p.PartitionID
cacheBlock.extentId = task.p.ExtentID
cacheBlock.offset = uint64(task.p.ExtentOffset)
cacheBlock.size = uint64(task.cacheSize)
log.LogDebugf("aheadRead doTask key(%v) size(%v)", key, task.p.Size)
for _, host := range hosts {
err = sendToNode(host, task.p, func(conn *net.TCPConn) (error, bool) {
readBytes = 0
for readBytes < task.cacheSize {
rp := NewReply(task.p.ReqID, task.p.PartitionID, task.p.ExtentID)
bufSize := util.Min(util.CacheReadBlockSize, task.cacheSize-readBytes)
rp.Data = cacheBlock.data[readBytes : readBytes+bufSize]
if e := rp.readFromConn(conn, proto.ReadDeadlineTime); e != nil {
return e, false
}
if rp.ResultCode != proto.OpOk {
return fmt.Errorf("resultCode(%x) not ok", rp.ResultCode), false
}
readBytes += int(rp.Size)
}
return nil, false
})
if err != nil && strings.Contains(err.Error(), "timeout") {
arw.canAheadRead = false
arw.streamer.aheadReadEnable = false
break
}
if err == nil {
cacheBlock.time = time.Now().Unix()
arw.cache.blockCache.Store(key, cacheBlock)
arw.canAheadRead = true
return
}
}
arw.cache.putAheadReadBlock(cacheBlock)
log.LogErrorf("aheadRead doTask inode(%v) offset(%v) size(%v) err(%v)", cacheBlock.inode, task.p.ExtentOffset, task.cacheSize, err)
}
func (arw *AheadReadWindow) putTaskIfNotExist(key string) (exist bool) {
arw.curTaskMutex.Lock()
defer arw.curTaskMutex.Unlock()
if _, exist = arw.curTaskMap[key]; exist {
return
}
arw.curTaskMap[key] = struct{}{}
return
}
func (arw *AheadReadWindow) deleteTask(key string) {
arw.curTaskMutex.Lock()
defer arw.curTaskMutex.Unlock()
delete(arw.curTaskMap, key)
}
func (arw *AheadReadWindow) addNextTask(offset int, dnHosts []string, req *ExtentRequest, stTime time.Time) {
id := offset/util.CacheReadBlockSize + arw.cache.winCnt
remainSize := int(req.ExtentKey.Size) - id*util.CacheReadBlockSize
if remainSize <= 0 {
arw.doMultiAheadRead(0, 0, req, dnHosts, stTime)
return
}
// log.LogDebugf("addNextTask ek(%v) remainSize(%v) %v-%v-%v-%v", req.ExtentKey, arw.streamer.inode, req.ExtentKey.PartitionId, req.ExtentKey.ExtentId, id*util.CacheReadBlockSize)
task := arw.getAheadReadTask(dnHosts, req, id, util.Min(remainSize, util.CacheReadBlockSize))
if task == nil {
return
}
task.time = stTime
arw.taskC <- task
}
func (arw *AheadReadWindow) doMultiAheadRead(offset, remainSize int, req *ExtentRequest, dnHosts []string, startTime time.Time) {
if len(arw.curTaskMap) > 0 && !arw.canAheadRead {
return
}
winCnt := 1
if arw.canAheadRead {
winCnt = arw.cache.winCnt
}
curReq := &ExtentRequest{
FileOffset: req.FileOffset,
Size: req.Size,
ExtentKey: req.ExtentKey,
}
id := 0
for i := offset / util.CacheReadBlockSize; i < winCnt; i++ {
if remainSize <= 0 {
id = 0
var (
dp *wrapper.DataPartition
err error
)
curReq.ExtentKey = arw.streamer.getNextExtent(int(curReq.ExtentKey.FileOffset))
if curReq.ExtentKey == nil {
return
}
if dp, err = arw.streamer.client.dataWrapper.GetDataPartition(curReq.ExtentKey.PartitionId); err != nil {
continue
}
dnHosts = dp.Hosts
remainSize = int(curReq.ExtentKey.Size)
}
size := util.Min(remainSize, util.CacheReadBlockSize)
task := arw.getAheadReadTask(dnHosts, curReq, id, size)
if task != nil {
task.time = startTime
arw.taskC <- task
}
remainSize -= size
id++
}
}
func (arw *AheadReadWindow) getAheadReadTask(dnHosts []string, req *ExtentRequest, id int, size int) *AheadReadTask {
cacheOffset := int(id) * util.CacheReadBlockSize
p := NewReadPacket(req.ExtentKey, cacheOffset, size, arw.streamer.inode, req.FileOffset, true)
task := &AheadReadTask{
p: p,
dnHosts: getRandomHostById(dnHosts, id),
req: req,
cacheSize: size,
}
return task
}
func (arw *AheadReadWindow) evictCacheBlock(req *ExtentRequest) {
offset := req.FileOffset - int(req.ExtentKey.FileOffset) + int(req.ExtentKey.ExtentOffset)
cacheOffset := offset / util.CacheReadBlockSize * util.CacheReadBlockSize
key := fmt.Sprintf("%v-%v-%v-%v", arw.streamer.inode, req.ExtentKey.PartitionId, req.ExtentKey.ExtentId, cacheOffset)
value, ok := arw.cache.blockCache.LoadAndDelete(key)
if ok {
bv := value.(*AheadReadBlock)
arw.cache.putAheadReadBlock(bv)
}
}
func (s *Streamer) getNextExtent(offset int) (ek *proto.ExtentKey) {
s.extents.RLock()
defer s.extents.RUnlock()
s.extents.root.Ascend(func(i btree.Item) bool {
e := i.(*proto.ExtentKey)
if e.Size < util.CacheReadBlockSize {
return true
}
if e.FileOffset > uint64(offset) {
ek = e
return false
}
return true
})
return
}
func (s *Streamer) aheadRead(req *ExtentRequest) (readSize int, err error) {
var (
offset int
cacheOffset int
cacheBlock *AheadReadBlock
step string
dp *wrapper.DataPartition
remainSize int
key string
)
startTime := time.Now()
defer func() {
log.LogDebugf("aheadRead step: %v, inode(%v) key(%v) FileOffset(%v) reqSize(%v) readSize(%v) err(%v) %v", step, s.inode, key, req.FileOffset, req.Size, readSize, err, time.Since(startTime))
}()
if dp, err = s.client.dataWrapper.GetDataPartition(req.ExtentKey.PartitionId); err != nil {
return
}
offset = req.FileOffset - int(req.ExtentKey.FileOffset) + int(req.ExtentKey.ExtentOffset)
cacheOffset = offset / util.CacheReadBlockSize * util.CacheReadBlockSize
remainSize = int(req.ExtentKey.Size) - cacheOffset
if remainSize < 0 {
remainSize = 0
}
needSize := req.Size - readSize
for needSize > 0 {
key = fmt.Sprintf("%v-%v-%v-%v", s.inode, req.ExtentKey.PartitionId, req.ExtentKey.ExtentId, cacheOffset)
val, ok := s.aheadReadWindow.cache.blockCache.Load(key)
if !ok {
break
}
step = "hit"
cacheBlock = val.(*AheadReadBlock)
cacheBlock.time = startTime.Unix()
log.LogDebugf("aheadRead cache hit inode(%v) FileOffset(%v) offset(%v) size(%v) cacheBlockOffset(%v) cacheBlockSize(%v) %v", s.inode, req.FileOffset, offset, needSize, cacheBlock.offset, cacheBlock.size, time.Since(startTime))
if cacheBlock.offset <= uint64(offset) {
if (cacheBlock.offset + cacheBlock.size) > uint64(offset+needSize) {
copy(req.Data[readSize:req.Size], cacheBlock.data[offset-int(cacheBlock.offset):offset-int(cacheBlock.offset)+needSize])
readSize += needSize
return
} else {
go s.aheadReadWindow.addNextTask(offset, dp.Hosts, req, startTime)
if offset-int(cacheBlock.offset) > int(cacheBlock.size) {
return
}
copy(req.Data, cacheBlock.data[offset-int(cacheBlock.offset):int(cacheBlock.size)])
readSize += int(cacheBlock.size) + int(cacheBlock.offset) - offset
offset += readSize
cacheOffset = offset
needSize -= readSize
if needSize <= 0 {
return
}
continue
}
}
break
}
step = "pass"
go s.aheadReadWindow.doMultiAheadRead(offset, remainSize, req, dp.Hosts, startTime)
return
}
func sendToNode(host string, p *Packet, getReply GetReplyFunc) (err error) {
var conn *net.TCPConn
if conn, err = StreamConnPool.GetConnect(host); err != nil {
return
}
defer StreamConnPool.PutConnect(conn, err != nil)
if err = p.WriteToConn(conn); err != nil {
return
}
err, _ = getReply(conn)
return
}
func getRandomHostById(hosts []string, id int) []string {
if len(hosts) == 0 {
return nil
}
hIdx := id % len(hosts)
rhosts := make([]string, len(hosts))
for i := 0; i < len(hosts); i++ {
if hIdx >= len(hosts) {
hIdx = 0
}
rhosts[i] = hosts[hIdx]
hIdx++
}
return rhosts
}

View File

@ -57,7 +57,9 @@ type Streamer struct {
isCache bool
openForWrite bool
rdonly bool
rdonly bool
aheadReadEnable bool
aheadReadWindow *AheadReadWindow
}
type bcacheKey struct {
@ -90,6 +92,10 @@ func NewStreamer(client *ExtentClient, inode uint64, openForWrite, isCache bool)
s.setError()
}
}
if client.AheadRead != nil {
s.aheadReadEnable = client.AheadRead.enable
s.aheadReadWindow = NewAheadReadWindow(client.AheadRead, s)
}
go s.server()
go s.asyncBlockCache()
return s
@ -203,7 +209,15 @@ func (s *Streamer) read(data []byte, offset int, size int, storageClass uint32)
total += req.Size
log.LogDebugf("Stream read hole: ino(%v) req(%v) total(%v)", s.inode, req, total)
} else {
log.LogDebugf("Stream read: ino(%v) req(%v) s.needBCache(%v) s.client.bcacheEnable(%v)", s.inode, req, s.needBCache, s.client.bcacheEnable)
log.LogDebugf("Stream read: ino(%v) req(%v) s.needBCache(%v) s.client.bcacheEnable(%v) aheadReadEnable(%v)", s.inode, req, s.needBCache, s.client.bcacheEnable, s.aheadReadEnable)
if s.aheadReadEnable && req.ExtentKey.Size > util.CacheReadBlockSize {
readBytes, err = s.aheadRead(req)
if err == nil && readBytes == req.Size {
total += readBytes
continue
}
log.LogDebugf("aheadRead inode(%v) FileOffset(%v) readBytes(%v) reqSize(%v) err(%v)", s.inode, req.FileOffset, readBytes, req.Size, err)
}
if s.needBCache {
bcacheMetric := exporter.NewCounter("fileReadL1Cache")
bcacheMetric.AddWithLabels(1, map[string]string{exporter.Vol: s.client.volumeName})

View File

@ -409,6 +409,9 @@ begin:
log.LogDebugf("action[streamer.write] ino %v doOverWriteByAppend extent key (%v)", s.inode, req.ExtentKey)
writeSize, _, err, _ = s.doOverWriteByAppend(req, direct, storageClass, isMigration)
}
if s.aheadReadEnable {
s.aheadReadWindow.evictCacheBlock(req)
}
if s.client.bcacheEnable {
cacheKey := util.GenerateKey(s.client.volumeName, s.inode, uint64(req.FileOffset))
go s.client.evictBcache(cacheKey)
@ -1031,6 +1034,9 @@ func (s *Streamer) open() {
func (s *Streamer) release() error {
s.refcnt--
if s.client.AheadRead != nil {
s.aheadReadEnable = s.client.AheadRead.enable
}
err := s.closeOpenHandler()
if err != nil {
s.abort()

View File

@ -46,6 +46,7 @@ var (
HeadVerBuffersTotalLimit int64
HeadProtoVerBuffersTotalLimit int64
RepairBuffersTotalLimit int64
cacheBuffersTotalLimit int64
)
var (
@ -55,6 +56,7 @@ var (
headVerBuffersCount int64
headProtoVerBuffersCount int64
repairBuffersCount int64
cacheBuffersCount int64
)
var (
@ -63,6 +65,7 @@ var (
headBufVerAllocId uint64
headBufProtoVerAllocId uint64
repairBufAllocId uint64
cacheBufAllocId uint64
)
var (
@ -71,6 +74,7 @@ var (
headBufVerFreeId uint64
headBufProtoVerFreeId uint64
repairBufFreeId uint64
cacheBufFreeId uint64
)
var (
@ -80,6 +84,7 @@ var (
headVerBuffersRateLimit = rate.NewLimiter(rate.Limit(16), 16)
headProtoVerBuffersRateLimit = rate.NewLimiter(rate.Limit(16), 16)
repairBuffersRateLimit = rate.NewLimiter(rate.Limit(16), 16)
cacheBuffersRateLimit = rate.NewLimiter(rate.Limit(16), 16)
)
func NewTinyBufferPool() *sync.Pool {
@ -143,6 +148,18 @@ func NewRepiarBufferPool() *sync.Pool {
}
}
func NewCacheBufferPool() *sync.Pool {
return &sync.Pool{
New: func() interface{} {
if cacheBuffersTotalLimit != InvalidLimit && atomic.LoadInt64(&cacheBuffersCount) >= cacheBuffersTotalLimit {
ctx := context.Background()
cacheBuffersRateLimit.Wait(ctx)
}
return make([]byte, util.CacheReadBlockSize)
},
}
}
// BufferPool defines the struct of a buffered pool with 4 objects.
type BufferPool struct {
headPools []chan []byte
@ -150,12 +167,14 @@ type BufferPool struct {
headProtoVerPools []chan []byte
normalPools []chan []byte
repairPools []chan []byte
cachePools []chan []byte
tinyPool *sync.Pool
headPool *sync.Pool
normalPool *sync.Pool
headVerPool *sync.Pool
headProtoVerPool *sync.Pool
repairPool *sync.Pool
cachePool *sync.Pool
}
const slotCnt = 16
@ -168,12 +187,14 @@ func NewBufferPool() (bufferP *BufferPool) {
bufferP.headVerPools = make([]chan []byte, slotCnt)
bufferP.headProtoVerPools = make([]chan []byte, slotCnt)
bufferP.repairPools = make([]chan []byte, slotCnt)
bufferP.cachePools = make([]chan []byte, slotCnt)
for i := 0; i < int(slotCnt); i++ {
bufferP.headPools[i] = make(chan []byte, HeaderBufferPoolSize/slotCnt)
bufferP.headVerPools[i] = make(chan []byte, HeaderBufferPoolSize/slotCnt)
bufferP.headProtoVerPools[i] = make(chan []byte, HeaderBufferPoolSize/slotCnt)
bufferP.normalPools[i] = make(chan []byte, HeaderBufferPoolSize/slotCnt)
bufferP.repairPools[i] = make(chan []byte, HeaderBufferPoolSize/slotCnt)
bufferP.cachePools[i] = make(chan []byte, HeaderBufferPoolSize/slotCnt)
}
bufferP.tinyPool = NewTinyBufferPool()
bufferP.headPool = NewHeadBufferPool()
@ -181,6 +202,7 @@ func NewBufferPool() (bufferP *BufferPool) {
bufferP.headProtoVerPool = NewHeadProtoVerBufferPool()
bufferP.normalPool = NewNormalBufferPool()
bufferP.repairPool = NewRepiarBufferPool()
bufferP.cachePool = NewCacheBufferPool()
return bufferP
}
@ -247,6 +269,15 @@ func (bufferP *BufferPool) getTiny() (data []byte) {
return bufferP.tinyPool.Get().([]byte)
}
func (bufferP *BufferPool) getCache(id uint64) (data []byte) {
select {
case data = <-bufferP.cachePools[id%slotCnt]:
return
default:
return bufferP.cachePool.Get().([]byte)
}
}
// Get returns the data based on the given size. Different size corresponds to different object in the pool.
func (bufferP *BufferPool) Get(size int) (data []byte, err error) {
if size == util.PacketHeaderSize {
@ -272,6 +303,10 @@ func (bufferP *BufferPool) Get(size int) (data []byte, err error) {
} else if size == util.DefaultTinySizeLimit {
atomic.AddInt64(&tinyBuffersCount, 1)
return bufferP.getTiny(), nil
} else if size == util.CacheReadBlockSize {
atomic.AddInt64(&cacheBuffersCount, 1)
id := atomic.AddUint64(&cacheBufAllocId, 1)
return bufferP.getCache(id), nil
}
return nil, fmt.Errorf("can only support 45 or 65536 bytes")
}
@ -321,6 +356,15 @@ func (bufferP *BufferPool) putRepair(index int, data []byte) {
}
}
func (bufferP *BufferPool) putCache(index int, data []byte) {
select {
case bufferP.cachePools[index] <- data:
return
default:
bufferP.cachePool.Put(data) // nolint: staticcheck
}
}
// Put puts the given data into the buffer pool.
func (bufferP *BufferPool) Put(data []byte) {
if data == nil {
@ -350,5 +394,9 @@ func (bufferP *BufferPool) Put(data []byte) {
} else if size == util.DefaultTinySizeLimit {
bufferP.tinyPool.Put(data) // nolint: staticcheck
atomic.AddInt64(&tinyBuffersCount, -1)
} else if size == util.CacheReadBlockSize {
atomic.AddInt64(&cacheBuffersCount, -1)
id := atomic.AddUint64(&cacheBufFreeId, 1)
bufferP.putCache(int(id%slotCnt), data)
}
}

View File

@ -42,6 +42,7 @@ const (
BlockSize = 65536 * 2
ReadBlockSize = BlockSize
RepairReadBlockSize = 512 * util.KB
CacheReadBlockSize = 4 * MB
PerBlockCrcSize = 4
ExtentSize = BlockCount * BlockSize
BlockHeaderSize = 4096