github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/cache/lfucache/lfucache.go (about)

     1  // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package lfucache
    16  
    17  import (
    18  	"runtime/debug"
    19  	"sync"
    20  	"sync/atomic"
    21  	"time"
    22  
    23  	"github.com/zuoyebang/bitalosdb/internal/base"
    24  	"github.com/zuoyebang/bitalosdb/internal/cache"
    25  	"github.com/zuoyebang/bitalosdb/internal/hash"
    26  	"github.com/zuoyebang/bitalosdb/internal/options"
    27  )
    28  
    29  const (
    30  	compationWorkerNum int = 4
    31  	runCompactInterval int = 120
    32  )
    33  
    34  var _ cache.ICache = (*LfuCache)(nil)
    35  
    36  func New(opts *options.CacheOptions) cache.ICache {
    37  	return NewLfuCache(opts)
    38  }
    39  
    40  type LfuCache struct {
    41  	maxSize      uint64
    42  	shardNum     uint32
    43  	launchTime   int64
    44  	closed       atomic.Bool
    45  	shards       []*shard
    46  	logger       base.Logger
    47  	workerClosed chan struct{}
    48  	workerNum    int
    49  	workerWg     sync.WaitGroup
    50  	workerParams []chan *workerParam
    51  }
    52  
    53  func NewLfuCache(opts *options.CacheOptions) *LfuCache {
    54  	size := uint64(opts.Size)
    55  	shardNum := uint32(opts.Shards)
    56  	maxSize := size / uint64(shardNum)
    57  	memSize := int(maxSize) / 2
    58  
    59  	mc := &LfuCache{
    60  		logger:       opts.Logger,
    61  		maxSize:      size,
    62  		shardNum:     shardNum,
    63  		launchTime:   time.Now().Unix(),
    64  		shards:       make([]*shard, shardNum),
    65  		workerNum:    compationWorkerNum,
    66  		workerClosed: make(chan struct{}),
    67  	}
    68  
    69  	for i := range mc.shards {
    70  		mc.shards[i] = newCache(mc, i, memSize, maxSize)
    71  	}
    72  
    73  	mc.runCompactionTask()
    74  	return mc
    75  }
    76  
    77  func (lfc *LfuCache) bucketByHash(khash uint32) *shard {
    78  	if lfc.shardNum == 1 {
    79  		return lfc.shards[0]
    80  	}
    81  	return lfc.shards[khash%lfc.shardNum]
    82  }
    83  
    84  func (lfc *LfuCache) ExistAndDelete(key []byte, khash uint32) error {
    85  	sd := lfc.bucketByHash(khash)
    86  	if sd.exist(key) {
    87  		return sd.delete(key)
    88  	}
    89  
    90  	return nil
    91  }
    92  
    93  func (lfc *LfuCache) Set(key, value []byte, khash uint32) error {
    94  	return lfc.bucketByHash(khash).set(key, value)
    95  }
    96  
    97  func (lfc *LfuCache) Get(key []byte, khash uint32) ([]byte, func(), bool) {
    98  	return lfc.bucketByHash(khash).get(key)
    99  }
   100  
   101  func (lfc *LfuCache) GetKeyHash(key []byte) uint32 {
   102  	return hash.Crc32(key)
   103  }
   104  
   105  func (lfc *LfuCache) Delete(key []byte, khash uint32) error {
   106  	return lfc.bucketByHash(khash).delete(key)
   107  }
   108  
   109  func (lfc *LfuCache) Close() {
   110  	lfc.closed.Store(true)
   111  	lfc.closeCompactionTask()
   112  
   113  	for i := range lfc.shards {
   114  		lfc.shards[i].close()
   115  	}
   116  
   117  	lfc.logger.Infof("lfucache closed")
   118  }
   119  
   120  func (lfc *LfuCache) isClosed() bool {
   121  	return lfc.closed.Load()
   122  }
   123  
   124  func (lfc *LfuCache) runCompactionTask() {
   125  	lfc.workerParams = make([]chan *workerParam, lfc.workerNum)
   126  
   127  	for i := 0; i < lfc.workerNum; i++ {
   128  		lfc.workerParams[i] = make(chan *workerParam)
   129  		go lfc.runCompactionWorker(i)
   130  	}
   131  
   132  	lfc.workerWg.Add(1)
   133  	go func() {
   134  		defer lfc.workerWg.Done()
   135  
   136  		interval := time.Duration(runCompactInterval)
   137  		jobId := 0
   138  		ticker := time.NewTicker(interval * time.Second)
   139  		defer ticker.Stop()
   140  
   141  		for {
   142  			select {
   143  			case <-lfc.workerClosed:
   144  				return
   145  			case <-ticker.C:
   146  				if lfc.isClosed() {
   147  					return
   148  				}
   149  
   150  				jobId++
   151  				lfc.logger.Infof("lfucache run compact task start scheduleCompact:%d", jobId)
   152  				lfc.runCompaction(compactTypeMemFlush)
   153  				lfc.logger.Infof("lfucache run compact task end scheduleCompact:%d", jobId)
   154  			}
   155  		}
   156  	}()
   157  }
   158  
   159  func (lfc *LfuCache) closeCompactionTask() {
   160  	close(lfc.workerClosed)
   161  	lfc.runCompaction(compactTypeClosed)
   162  	for i := 0; i < lfc.workerNum; i++ {
   163  		close(lfc.workerParams[i])
   164  	}
   165  	lfc.workerWg.Wait()
   166  }
   167  
   168  type workerParam struct {
   169  	sid   int
   170  	ctype int
   171  	wg    *sync.WaitGroup
   172  }
   173  
   174  func (lfc *LfuCache) runCompactionWorker(workId int) {
   175  	lfc.workerWg.Add(1)
   176  	go func(wid int) {
   177  		defer func() {
   178  			lfc.workerWg.Done()
   179  			if r := recover(); r != nil {
   180  				lfc.logger.Errorf("lfucache compaction worker run panic err:%v panic:%s", r, string(debug.Stack()))
   181  				lfc.runCompactionWorker(wid)
   182  			}
   183  		}()
   184  
   185  		for {
   186  			p, ok := <-lfc.workerParams[wid]
   187  			if !ok {
   188  				return
   189  			}
   190  			lfc.shards[p.sid].compact(p.ctype)
   191  			p.wg.Done()
   192  		}
   193  	}(workId)
   194  }
   195  
   196  func (lfc *LfuCache) runCompaction(ctype int) {
   197  	wg := &sync.WaitGroup{}
   198  
   199  	for i := range lfc.shards {
   200  		wid := i % lfc.workerNum
   201  		p := &workerParam{
   202  			sid:   i,
   203  			ctype: ctype,
   204  			wg:    wg,
   205  		}
   206  		wg.Add(1)
   207  		lfc.workerParams[wid] <- p
   208  	}
   209  
   210  	wg.Wait()
   211  }