Skip to content

Commit 468afb8

Browse files
committed
feat: Support memory store
1 parent d15841a commit 468afb8

12 files changed

+320
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor

Gopkg.lock

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Gopkg.toml example
2+
#
3+
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
4+
# for detailed Gopkg.toml documentation.
5+
#
6+
# required = ["github.com/user/thing/cmd/thing"]
7+
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
8+
#
9+
# [[constraint]]
10+
# name = "github.com/user/project"
11+
# version = "1.0.0"
12+
#
13+
# [[constraint]]
14+
# name = "github.com/user/project2"
15+
# branch = "dev"
16+
# source = "github.com/myfork/project2"
17+
#
18+
# [[override]]
19+
# name = "github.com/x/y"
20+
# version = "2.4.0"
21+
#
22+
# [prune]
23+
# non-go = false
24+
# go-tests = true
25+
# unused-packages = true
26+
27+
28+
[[constraint]]
29+
name = "github.com/xiaojiaoyu100/curlew"
30+
version = "0.0.1"
31+
32+
[prune]
33+
unused-packages = true

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Roc
2+
3+
Roc is a key-value memory cache.
4+
5+
## Feature
6+
7+
* Volatile LRU
8+
* Quick GC
9+
10+
## Usage
11+
12+

bucket.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package roc
2+
3+
import (
4+
"container/list"
5+
"sync"
6+
"time"
7+
)
8+
9+
type Bucket struct {
10+
guard sync.Mutex
11+
objs *list.List
12+
coll map[string]*list.Element
13+
}
14+
15+
func NewBucket() (*Bucket, error) {
16+
b := new(Bucket)
17+
return b, nil
18+
}
19+
20+
func (b *Bucket) Get(key string) ([]byte, error) {
21+
b.guard.Lock()
22+
defer b.guard.Unlock()
23+
element, hit := b.coll[key]
24+
if !hit {
25+
return nil, ErrMiss
26+
}
27+
u, ok := element.Value.(*Unit)
28+
if !ok {
29+
return nil, ErrMiss
30+
}
31+
32+
if u.Expire() {
33+
return nil, ErrMiss
34+
}
35+
b.objs.MoveToFront(element)
36+
return u.Data, nil
37+
}
38+
39+
func (b *Bucket) Set(key string, data []byte, d time.Duration) error {
40+
b.guard.Lock()
41+
defer b.guard.Unlock()
42+
element, hit := b.coll[key]
43+
if !hit {
44+
unit := new(Unit)
45+
unit.Key = key
46+
unit.Data = data
47+
unit.ExpirationTime = time.Now().UTC().Add(d)
48+
e := b.objs.PushFront(unit)
49+
b.coll[key] = e
50+
return nil
51+
}
52+
b.objs.MoveToFront(element)
53+
unit, ok := element.Value.(*Unit)
54+
if !ok {
55+
return nil
56+
}
57+
unit.Data = data
58+
unit.ExpirationTime = time.Now().UTC().Add(d)
59+
return nil
60+
}
61+
62+
func (b *Bucket) Del(key string) {
63+
b.guard.Lock()
64+
defer b.guard.Unlock()
65+
element, hit := b.coll[key]
66+
if !hit {
67+
return
68+
}
69+
b.del(key, element)
70+
}
71+
72+
func (b *Bucket) del(key string, e *list.Element) {
73+
b.objs.Remove(e)
74+
delete(b.coll, key)
75+
}
76+
77+
func (b *Bucket) gc() {
78+
b.guard.Lock()
79+
defer b.guard.Unlock()
80+
81+
e := b.objs.Back()
82+
for e != nil {
83+
unit := e.Value.(*Unit)
84+
if unit.Expire() {
85+
b.del(unit.Key, e)
86+
}
87+
e = e.Prev()
88+
}
89+
}

cache.go

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package roc
2+
3+
import (
4+
"container/list"
5+
"context"
6+
"github.com/xiaojiaoyu100/curlew"
7+
"time"
8+
)
9+
10+
type Cache struct {
11+
GCInterval time.Duration
12+
BucketNum int
13+
buckets []*Bucket
14+
dispatcher *curlew.Dispatcher
15+
close chan struct{}
16+
}
17+
18+
func New(setters ...Setter) (*Cache, error) {
19+
var err error
20+
c := new(Cache)
21+
c.GCInterval = 60 * time.Second
22+
c.BucketNum = 128
23+
24+
for _, setter := range setters {
25+
if err := setter(c); err != nil {
26+
return nil, err
27+
}
28+
}
29+
30+
if !isPowerOfTwo(c.BucketNum) {
31+
return nil, ErrorBucketNum
32+
}
33+
c.buckets = make([]*Bucket, 0, c.BucketNum)
34+
for idx := 0; idx < c.BucketNum; idx++ {
35+
bucket, err := NewBucket()
36+
if err != nil {
37+
return nil, err
38+
}
39+
bucket.coll = make(map[string]*list.Element)
40+
bucket.objs = list.New()
41+
c.buckets = append(c.buckets, bucket)
42+
}
43+
44+
c.dispatcher, err = curlew.New()
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
c.close = make(chan struct{})
50+
c.gc()
51+
return c, nil
52+
}
53+
54+
func (c *Cache) gc() {
55+
go func() {
56+
ticker := time.NewTicker(c.GCInterval)
57+
defer ticker.Stop()
58+
for {
59+
select {
60+
case <-ticker.C:
61+
{
62+
for _, bucket := range c.buckets {
63+
j := curlew.NewJob()
64+
j.Fn = func(_ context.Context, _ interface{}) error {
65+
bucket.gc()
66+
return nil
67+
}
68+
c.dispatcher.SubmitAsync(j)
69+
}
70+
}
71+
case <-c.close:
72+
return
73+
}
74+
}
75+
}()
76+
}
77+
78+
func (c *Cache) Get(key string) ([]byte, error) {
79+
idx, err := c.hashIndex(key)
80+
if err != nil {
81+
return nil, err
82+
}
83+
return c.buckets[idx].Get(key)
84+
}
85+
86+
func (c *Cache) Set(key string, value []byte, duration time.Duration) error {
87+
idx, err := c.hashIndex(key)
88+
if err != nil {
89+
return err
90+
}
91+
return c.buckets[idx].Set(key, value, duration)
92+
}
93+
94+
func (c *Cache) Del(key string) error {
95+
idx, err := c.hashIndex(key)
96+
if err != nil {
97+
return err
98+
}
99+
c.buckets[idx].Del(key)
100+
return nil
101+
}

error.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package roc
2+
3+
type Error string
4+
5+
func (e Error) Error() string {
6+
return string(e)
7+
}
8+
9+
const (
10+
ErrMiss = Error("cache miss")
11+
ErrorBucketNum = Error("bucket num must be the power of two")
12+
)

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/xiaojiaoyu100/roc
2+
3+
go 1.12
4+
5+
require github.com/xiaojiaoyu100/curlew v0.0.1

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/xiaojiaoyu100/curlew v0.0.1 h1:Jk7j+Hx+ZZn+dIy8hUlcoiahr8ni0JIxRD1n7R/N4tE=
2+
github.com/xiaojiaoyu100/curlew v0.0.1/go.mod h1:LL7ujI8S+sxh1ihoP6Z86o3OSwWLtW943vtc4hF1A9k=

hash.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package roc
2+
3+
import "hash/fnv"
4+
5+
func (c *Cache) hashIndex(key string) (int, error) {
6+
hash := fnv.New64a()
7+
_, err := hash.Write([]byte(key))
8+
if err != nil {
9+
return 0, err
10+
}
11+
return int(hash.Sum64() & uint64((c.BucketNum - 1))), nil
12+
}

setter.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package roc
2+
3+
import (
4+
"time"
5+
)
6+
7+
type Setter func(c *Cache) error
8+
9+
func isPowerOfTwo(num int) bool {
10+
return (num != 0) && ((num & (num - 1)) == 0)
11+
}
12+
13+
func WithBucketNum(num int) Setter {
14+
return func(c *Cache) error {
15+
c.BucketNum = num
16+
return nil
17+
}
18+
}
19+
20+
func WithGCInterval(interval time.Duration) Setter {
21+
return func(c *Cache) error {
22+
c.GCInterval = interval
23+
return nil
24+
}
25+
}

unit.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package roc
2+
3+
import "time"
4+
5+
type Unit struct {
6+
Key string
7+
Data []byte
8+
ExpirationTime time.Time
9+
}
10+
11+
func (u *Unit) Expire() bool {
12+
return u.ExpirationTime.Before(time.Now().UTC())
13+
}

0 commit comments

Comments
 (0)