From 84a84829dddc14f3761d57e52c81593bf3cd7a14 Mon Sep 17 00:00:00 2001 From: sykline93 Date: Fri, 7 Oct 2022 15:33:43 +0800 Subject: [PATCH] feat: use lru cache --- .gitignore | 1 + README.md | 52 ++++++++++- gokv.go | 258 +++++++++++++++++++++++++++++++++++++++------------ gokv_test.go | 84 +++++++++-------- 4 files changed, 295 insertions(+), 100 deletions(-) diff --git a/.gitignore b/.gitignore index 66fd13c..474488e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # Dependency directories (remove the comment below to include it) # vendor/ +.idea diff --git a/README.md b/README.md index d521a74..973f02f 100644 --- a/README.md +++ b/README.md @@ -1 +1,51 @@ -# gokv \ No newline at end of file +# GoKV +GoKV is a key-value storage in the memory, it is simple and fast. + +## Requirements +Go1.18+ + +## Installation +```bash +go get -u github.com/skyline93/gokv +``` + +## Example +```go +package main + +import ( + "github.com/skyline93/gokv" +) + +func main(){ + cache := gokv.New(5) + cache.Put("key1", "value1") + key2 := cache.PutWithKey("value2") // auto generate key + + v1 := cache.Get("key1") + v2 := cache.Get(key2) +} +``` + +```go +package main + +import ( + "time" + + "github.com/skyline93/gokv" +) + +func main(){ + cache := gokv.New(5) + cache.Put("key1", "value1", 2) // value expires after 2 second + key2 := cache.PutWithKey("value2", 5) // value expires after 5 second + + + time.Sleep(time.Second * 2) + v1 := cache.Get("key1") // v1 is nil + + time.Sleep(time.Second * 3) + v2 := cache.Get(key2) // v2 is nil +} +``` diff --git a/gokv.go b/gokv.go index 16d2646..893e60b 100644 --- a/gokv.go +++ b/gokv.go @@ -1,109 +1,247 @@ package gokv import ( + "fmt" + uuid "github.com/satori/go.uuid" + "log" "sync" "time" - - uuid "github.com/satori/go.uuid" ) +type Node struct { + next *Node + prev *Node + + key interface{} +} + +type List struct { + head *Node + tail *Node +} + +func (l *List) Insert(key interface{}) { + node := &Node{key: key} + + if l.head == nil { + l.head = node + l.tail = node + return + } + + t := l.tail + t.next = node + node.prev = t + l.tail = node +} + +func (l *List) get(key interface{}) *Node { + n := l.head + + if n.key == key { + return n + } + + for n.next != nil { + if n.key == key { + return n + } + + n = n.next + } + + return nil +} + +func (l *List) Delete(key interface{}) { + n := l.get(key) + + if n == nil { + return + } + + if n.prev == nil && n.next == nil { + l.head = nil + l.tail = nil + return + } + + if n.prev == nil { + l.head = n.next + n.next.prev = nil + return + } + + if n.next == nil { + l.tail = n.prev + n.prev.next = nil + return + } + + next := n.next + prev := n.prev + + next.prev = prev + prev.next = next +} + +func (l *List) Head() (key interface{}) { + if l.head == nil { + return nil + } + + return l.head.key +} + +func (l *List) All() { + n := l.head + + if n == nil { + return + } + + fmt.Printf("n: %s ", n.key) + for n.next != nil { + n = n.next + fmt.Printf("n: %s ", n.key) + } +} + type Value struct { - V interface{} - Ttl int + v interface{} + + ttl int createTime time.Time } -func NewValue(v interface{}) *Value { +func NewValue(v interface{}, ttl int) *Value { return &Value{ - V: v, - Ttl: 60 * 60 * 2, + v: v, + ttl: ttl, createTime: time.Now(), } } func (v *Value) expiration() time.Time { - return v.createTime.Add(time.Second * time.Duration(v.Ttl)) + if v.ttl < 0 { + return v.createTime.Add(time.Second * time.Duration(999999999)) + } + + return v.createTime.Add(time.Second * time.Duration(v.ttl)) } func (v *Value) IsExpired() bool { return time.Now().After(v.expiration()) } -type KV struct { - m map[string]*Value - sync.RWMutex +type Cache struct { + sync.Mutex + index *List + kv map[string]*Value + len int + + gcChan chan interface{} } -func New() *KV { - return &KV{ - m: make(map[string]*Value), +func New(len int) *Cache { + c := &Cache{ + index: new(List), + kv: make(map[string]*Value, len), + len: len, + + gcChan: make(chan interface{}, 100), } + + go func() { + for { + select { + case g := <-c.gcChan: + // TODO + log.Printf("gc: %v\n", g) + } + } + }() + + return c } -func (kv *KV) Get(k string) interface{} { - kv.RLock() - v, ok := kv.m[k] - kv.RUnlock() +func (c *Cache) collect() { + k := c.index.Head() + c.index.Delete(k) - if !ok { - return nil - } + g := struct { + K string + V interface{} + }{K: k.(string), V: c.kv[k.(string)]} - if v.IsExpired() { - kv.Lock() - delete(kv.m, k) - kv.Unlock() + delete(c.kv, k.(string)) - return nil - } + c.gcChan <- g +} + +func (c *Cache) reset(key string) { + c.index.Delete(key) + c.index.Insert(key) +} - return v.V +func (c *Cache) delete(key string) { + c.index.Delete(key) + delete(c.kv, key) } -func (kv *KV) Put(k string, v interface{}, args ...interface{}) bool { - value := NewValue(v) - for i, v := range args { +func (c *Cache) Delete(key string) { + c.Lock() + defer c.Unlock() + + c.delete(key) +} + +func (c *Cache) put(key string, value interface{}, opts ...interface{}) { + ttl := -1 + + for i, opt := range opts { switch i { case 0: - ttl, ok := v.(int) - if !ok { - return false - } - value.Ttl = ttl - default: - return false + ttl = opt.(int) } } - kv.Lock() - defer kv.Unlock() + c.Lock() + defer c.Unlock() - kv.m[k] = value + if len(c.kv) == c.len { + c.collect() + } - return true + c.kv[key] = NewValue(value, ttl) + c.index.Insert(key) } -func (kv *KV) PutWithUuid(v interface{}, args ...interface{}) (key string, ok bool) { - value := NewValue(v) - for i, v := range args { - switch i { - case 0: - ttl, ok := v.(int) - if !ok { - return "", false - } - value.Ttl = ttl - default: - return "", false - } - } +func (c *Cache) Put(key string, value interface{}, opts ...interface{}) { + c.put(key, value, opts...) +} +func (c *Cache) PutWithKey(value interface{}, opts ...interface{}) (key string) { key = uuid.NewV4().String() - kv.Lock() - defer kv.Unlock() + c.put(key, value, opts...) + return +} + +func (c *Cache) Get(key string) interface{} { + c.Lock() + defer c.Unlock() + + value, ok := c.kv[key] + if !ok { + return nil + } - kv.m[key] = value + if value.IsExpired() { + c.delete(key) + return nil + } - return key, true + c.reset(key) + return value.v } diff --git a/gokv_test.go b/gokv_test.go index 0ebd15b..956d47e 100644 --- a/gokv_test.go +++ b/gokv_test.go @@ -1,57 +1,63 @@ -package gokv_test +package gokv import ( "testing" - - "github.com/skyline93/gokv" + "time" ) -func TestPutAndGet(t *testing.T) { - kv := gokv.New() +func TestPut(t *testing.T) { + cache := New(3) + cache.Put("1", "a") + cache.Put("2", "b") + cache.Put("3", "c") + + if v := cache.Get("1"); v != "a" { + t.FailNow() + } + + if cache.index.tail.key != "1" { + t.FailNow() + } + + if v := cache.Get("2"); v != "b" { + t.FailNow() + } - tests := []struct { - arg1 string - arg2 interface{} - expected interface{} - }{ - {"key1", 123, 123}, - {"key1", "123", "123"}, - {"key2", struct{ v1 string }{"v1"}, struct{ v1 string }{"v1"}}, + if cache.index.tail.key != "2" { + t.FailNow() } - for _, i := range tests { - if ok := kv.Put(i.arg1, i.arg2); !ok { - t.Fatal("put error") - } + if v := cache.Get("3"); v != "c" { + t.FailNow() + } - output := kv.Get(i.arg1) - if output != i.expected { - t.Errorf("Output %q not equal to expected %q", output, i.expected) - } + cache.Put("4", "d") + if cache.index.head.key != "2" { + t.FailNow() } } -func TestPutWithUuidAndGet(t *testing.T) { - kv := gokv.New() +func TestPutWithKey(t *testing.T) { + cache := New(2) - tests := []struct { - arg interface{} - expected interface{} - }{ - {123, 123}, - {"123", "123"}, - {struct{ v1 string }{"v1"}, struct{ v1 string }{"v1"}}, + k := cache.PutWithKey("a") + if k == "" { + t.FailNow() } +} - for _, i := range tests { - k, ok := kv.PutWithUuid(i.arg) - if !ok { - t.Fatal("put error") - } +func TestTTL(t *testing.T) { + cache := New(2) + + cache.Put("1", "a", 2) + time.Sleep(time.Second * 1) + + if v := cache.Get("1"); v == nil { + t.FailNow() + } - output := kv.Get(k) - if output != i.expected { - t.Errorf("Output %q not equal to expected %q", output, i.expected) - } + time.Sleep(time.Second * 1) + if v := cache.Get("1"); v != nil { + t.FailNow() } }