168 lines
3.4 KiB
Go
168 lines
3.4 KiB
Go
|
|
package cache
|
||
|
|
|
||
|
|
import (
|
||
|
|
"math"
|
||
|
|
"runtime"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/cespare/xxhash/v2"
|
||
|
|
)
|
||
|
|
|
||
|
|
type LRUStriped struct {
|
||
|
|
buckets []*LRU
|
||
|
|
name string
|
||
|
|
}
|
||
|
|
|
||
|
|
type LRUStripedOptions struct {
|
||
|
|
Size int
|
||
|
|
DefaultExpiry time.Duration
|
||
|
|
Name string
|
||
|
|
Buckets int
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewLRUStriped(opts *LRUStripedOptions) *LRUStriped {
|
||
|
|
if opts.Buckets <= 0 {
|
||
|
|
opts.Buckets = runtime.NumCPU()
|
||
|
|
}
|
||
|
|
if opts.Buckets < 4 {
|
||
|
|
opts.Buckets = 4
|
||
|
|
}
|
||
|
|
if opts.Size < opts.Buckets {
|
||
|
|
opts.Size = opts.Buckets * 100
|
||
|
|
}
|
||
|
|
|
||
|
|
opts.Size += int(math.Ceil(float64(opts.Size) * 10.0 / 100.0))
|
||
|
|
bucketSize := (opts.Size / opts.Buckets) + (opts.Size % opts.Buckets)
|
||
|
|
|
||
|
|
buckets := make([]*LRU, opts.Buckets)
|
||
|
|
for i := 0; i < opts.Buckets; i++ {
|
||
|
|
buckets[i] = NewLRU(&LRUOptions{
|
||
|
|
Size: bucketSize,
|
||
|
|
DefaultExpiry: opts.DefaultExpiry,
|
||
|
|
Name: opts.Name,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
return &LRUStriped{
|
||
|
|
buckets: buckets,
|
||
|
|
name: opts.Name,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) hashKey(key string) uint64 {
|
||
|
|
return xxhash.Sum64String(key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) keyBucket(key string) *LRU {
|
||
|
|
return l.buckets[l.hashKey(key)%uint64(len(l.buckets))]
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Set(key string, value any, ttl time.Duration) {
|
||
|
|
l.keyBucket(key).Set(key, value, ttl)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Get(key string) (any, bool) {
|
||
|
|
return l.keyBucket(key).Get(key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Delete(key string) {
|
||
|
|
l.keyBucket(key).Delete(key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) DeleteByPrefix(prefix string) {
|
||
|
|
for _, bucket := range l.buckets {
|
||
|
|
bucket.DeleteByPrefix(prefix)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Clear() {
|
||
|
|
for _, bucket := range l.buckets {
|
||
|
|
bucket.Clear()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Exists(key string) bool {
|
||
|
|
return l.keyBucket(key).Exists(key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Increment(key string) int64 {
|
||
|
|
return l.keyBucket(key).Increment(key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) IncrementBy(key string, value int64) int64 {
|
||
|
|
return l.keyBucket(key).IncrementBy(key, value)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Len() int {
|
||
|
|
var total int
|
||
|
|
for _, bucket := range l.buckets {
|
||
|
|
total += bucket.Len()
|
||
|
|
}
|
||
|
|
return total
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Name() string {
|
||
|
|
return l.name
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) DeleteMulti(keys []string) error {
|
||
|
|
var err error
|
||
|
|
for _, key := range keys {
|
||
|
|
l.Delete(key)
|
||
|
|
}
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) GetMulti(keys []string, values []any) []error {
|
||
|
|
errs := make([]error, len(values))
|
||
|
|
for i, key := range keys {
|
||
|
|
if val, ok := l.Get(key); ok {
|
||
|
|
values[i] = val
|
||
|
|
} else {
|
||
|
|
errs[i] = ErrKeyNotFound
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return errs
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) SetMulti(items map[string]any, ttl time.Duration) error {
|
||
|
|
for key, value := range items {
|
||
|
|
l.Set(key, value, ttl)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *LRUStriped) Keys() []string {
|
||
|
|
keys := make([]string, 0)
|
||
|
|
for _, bucket := range l.buckets {
|
||
|
|
bucket.lock.RLock()
|
||
|
|
for key, ent := range bucket.items {
|
||
|
|
e := ent.Value.(*lruEntry)
|
||
|
|
if e.generation == bucket.currentGeneration && (e.expires.IsZero() || !time.Now().After(e.expires)) {
|
||
|
|
keys = append(keys, key)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bucket.lock.RUnlock()
|
||
|
|
}
|
||
|
|
return keys
|
||
|
|
}
|
||
|
|
|
||
|
|
type LRUStripedInterface interface {
|
||
|
|
Set(key string, value any, ttl time.Duration)
|
||
|
|
Get(key string) (any, bool)
|
||
|
|
Delete(key string)
|
||
|
|
DeleteByPrefix(prefix string)
|
||
|
|
Clear()
|
||
|
|
Exists(key string) bool
|
||
|
|
Increment(key string) int64
|
||
|
|
IncrementBy(key string, value int64) int64
|
||
|
|
Len() int
|
||
|
|
Name() string
|
||
|
|
DeleteMulti(keys []string) error
|
||
|
|
GetMulti(keys []string, values []any) []error
|
||
|
|
SetMulti(items map[string]any, ttl time.Duration) error
|
||
|
|
Keys() []string
|
||
|
|
}
|
||
|
|
|
||
|
|
var _ LRUStripedInterface = (*LRUStriped)(nil)
|