├── .gitignore ├── doc ├── img.png └── README.md ├── go.mod ├── options.go ├── impl_bench_test.go ├── example_test.go ├── sds_test.go ├── single_flight_bench_test.go ├── cache.go ├── go.sum ├── regulation.go ├── sds.go ├── single_flight_test.go ├── single_flight.go ├── impl_test.go └── impl.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | cmd -------------------------------------------------------------------------------- /doc/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongofs/scache/HEAD/doc/img.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mongofs/Scache 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/smartystreets/goconvey v1.7.2 7 | go.uber.org/atomic v1.9.0 8 | ) 9 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # scache 2 | 一个基于golang的内存缓存, 目前在api优化过程中,发现存在很大部分工程师不会编写分布式锁,或者说不会用redis 3 | 构建一个分布式锁,当然使用redis的分布式锁天然也存在一些问题,导致锁并不是特别可靠。我就有想法编写一个基于内存 4 | 的scache提供单机版本的锁,以插件形式引入到程序本地去,整体请求流程如下,当然server和cache交互都是在一个程序 5 | 中的。 6 | ![img.png](img.png) 7 | 8 | 可以知道的是在同一时间可能存在多个请求打到数据库,但是这个取决于每台服务器的策略,整体1000个请求,同时两个请求 9 | 到数据库也是可以容忍的。 10 | 11 | 除此之外,还有一个需求是部分的内容很多工程师喜欢存到内存,在使用golang开发的时候这种情况特别明显,所以在这种情况 12 | 下,万一某个部分没有处理好,导致内存泄露以及其他不可预测的情况是业务不友好的,所以必须要在这个基础上实现一个内存 13 | 管理的功能。 -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache -------------------------------------------------------------------------------- /impl_bench_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import ( 17 | "fmt" 18 | "go.uber.org/atomic" 19 | "testing" 20 | "time" 21 | ) 22 | 23 | 24 | func BenchmarkCacheImpl_GetKeyWitchRegistered(b *testing.B) { 25 | ca := New(5000,time.Duration( 10)*time.Second, func(key string, value Value) { 26 | fmt.Println("删除 key",key,value) 27 | }) 28 | var testKey = "steven" 29 | var counter = atomic.Int32{} 30 | 31 | ca.Register(testKey,3, func() (Value, error) { 32 | counter.Inc() 33 | time.Sleep(1 * time.Second) 34 | return StringValue("steven is gooo guy"),nil 35 | }) 36 | b.ResetTimer() 37 | for i := 0; i < b.N; i++ { 38 | go func() { 39 | v,err := ca.Get(testKey) 40 | if err != nil { 41 | b.Fatal() 42 | } 43 | fmt.Println(v) 44 | }() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache_test 15 | 16 | import ( 17 | "fmt" 18 | "github.com/mongofs/Scache" 19 | "time" 20 | ) 21 | 22 | func ExampleCacheImpl_Register() { 23 | cache := Scache.New(200 * 1204 * 1024 , 3* time.Second,func(key string, value Scache.Value) { 24 | fmt.Println("delete the ",key ,value) 25 | }) 26 | 27 | cache.Register("testKey",0, func() (Scache.Value, error) { 28 | // do select db or some action slow 29 | time.Sleep(2 *time.Second) 30 | 31 | // store [] byte value 32 | return Scache.ByteValue([]byte("steven is handsome")),nil 33 | }) 34 | 35 | if val ,err := cache.Get("testKey");err !=nil { 36 | panic(err) 37 | }else { 38 | fmt .Println(string(val.(*Scache.DefaultByteValue).Value())) 39 | } 40 | // output : "steven is handsome" 41 | } 42 | -------------------------------------------------------------------------------- /sds_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import ( 17 | "fmt" 18 | . "github.com/smartystreets/goconvey/convey" 19 | "testing" 20 | "unsafe" 21 | ) 22 | 23 | func TestSds_Calculation(t *testing.T) { 24 | Convey("test sds size ", t, func() { 25 | s := &sds{ 26 | key: "123", // 16字节 27 | expire: 123241, // 8字节 28 | st: 1, // 1个字节 29 | Value: StringValue("you"), // 16字节 30 | } 31 | // key : 32 | // expire : int64 8字节 33 | // st : uint8 ,1个字节 34 | // value : 35 | fmt.Println(s.Calculation()) 36 | fmt.Println(len(s.key)) 37 | fmt.Println(unsafe.Sizeof(s.expire)) 38 | fmt.Println(unsafe.Sizeof(s.st)) 39 | fmt.Println(len("abc")) 40 | fmt.Println(unsafe.Sizeof([]string{"1", "2"})) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /single_flight_bench_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import ( 17 | "fmt" 18 | "go.uber.org/atomic" 19 | "math/rand" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | var benchCall = func() (Value, error) { 25 | fmt.Println("走到这里来了") 26 | time.Sleep(1 * time.Second) 27 | return StringValue("steven"), nil 28 | } 29 | 30 | // goos: darwin 31 | // goarch: amd64 32 | // pkg: Scache 33 | // cpu: Intel(R) Core(TM) i5-8500B CPU @ 3.00GHz 34 | // BenchmarkDefaultSingleFlight_Get 35 | // BenchmarkDefaultSingleFlight_Get-6 503554 2070 ns/op 36 | func BenchmarkDefaultSingleFlight_Get(b *testing.B) { 37 | ob := NewSingleFlight(10) 38 | in := atomic.Int32{} 39 | for i := 0; i < b.N; i++ { 40 | // 模拟100个请求,这些请求开始时间不一定,在10秒内的均匀分布,理想状况是每秒10个请求 41 | // 后台请求耗时在1秒左右,那么此时预计慢请求应该在10个以下,总请求数不变, 42 | go func() { 43 | in.Inc() 44 | time.Sleep(time.Duration(rand.Intn(10)) * time.Second) 45 | _, _, err := ob.Get("10", benchCall) 46 | if err != nil { 47 | b.Fatal(err) 48 | return 49 | } 50 | }() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | type Cache interface { 17 | // 获取到一个值,key值,当key不存在的时候返回为空,所以在get一个不存在的值的时候除了判断 18 | // err !=nil && value !=nil ,此时才算真正获取到值 19 | Get(key string) (value Value, err error) 20 | 21 | // 设置一个值到缓存当中,这里值得注意的是,如果设置一个值,它的优先级是高于register 的方法 22 | Set(key string, value Value) error 23 | 24 | // 防止覆盖,建议内存中优先使用这个函数,防止key值之间的覆盖 25 | SetNX(key string, value Value) error 26 | 27 | // 覆盖已经存在的某个key,如果key不存在,就返回,否则就覆盖 28 | SetEX(key string, value Value) error 29 | 30 | // 接管Error方法 31 | SetErrorHandler(handler func(...interface{})) 32 | 33 | // 设置一个值,并为这个值设置一个过期时间 34 | SetWithTTL(key string, content Value, ttl int) error 35 | 36 | // 删除一个key值,并未真正删除,只是将key值进行标注为不可访问,get获取是获取不到的,但是 37 | // 会回调删除方法,真正删除的时候是在内存占用超过80% 的时候 38 | Del(key string) 39 | 40 | // 过期某个值 41 | Expire(key string, ttl int) 42 | 43 | // 提前将规则注册到cache中,regulation 之间是不能覆盖,否则就会报错 44 | Register(regulation string, expire int, f /* slow way func */ func() (Value, error)) 45 | 46 | // 注册一个CornJob 47 | RegisterCron(regulation string,flushInterval int ,f /* slow way func */ func() (Value, error)) 48 | } 49 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 5 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 6 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 7 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 11 | github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 12 | github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 13 | github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 16 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 17 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 18 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 19 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 20 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 21 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 22 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 23 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 24 | -------------------------------------------------------------------------------- /regulation.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import "sync" 17 | 18 | // regulation 是用于管理注册用户的狗子函数,本想起名字为hook,但是感觉regulation比较不错 19 | type regular struct { 20 | call func() (Value, error) 21 | expire int 22 | } 23 | 24 | type RegularManger interface { 25 | Register(regulation string, expire int, call func() (Value, error)) 26 | 27 | // 第二个参数理解起来有点困难,因为RegularManager 是并发安全的,所以同一时间打入Get方法 28 | // 的流量势必会非常多,假设100个流量都进入了Get,在上层调用的时候不应该将所有的请求拿到的 29 | // 值都去存储起来,那么在这么多流量选择一个的时候,就可以选择slowPath 返回的路径作为存储 30 | Get(regulation string) (Value, bool/* is slow way */, int /* expire time */, error) 31 | } 32 | 33 | type defaultRegularManger struct { 34 | rw *sync.RWMutex 35 | set map[string]*regular 36 | singleFlight SingleFlight 37 | } 38 | 39 | func NewRegularManager() RegularManger { 40 | return &defaultRegularManger{ 41 | rw: &sync.RWMutex{}, 42 | set: map[string]*regular{}, 43 | singleFlight: NewSingleFlight(20), 44 | } 45 | } 46 | 47 | func (r *defaultRegularManger) Register(regulation string, expire int, call func() (Value, error)) { 48 | r.rw.Lock() 49 | defer r.rw.Unlock() 50 | if _, ok := r.set[regulation]; ok { 51 | panic( ErrRegulationAlreadyExist) 52 | } 53 | r.set[regulation] = ®ular{ 54 | call: call, 55 | expire: expire, 56 | } 57 | } 58 | 59 | func (r *defaultRegularManger) Get(regulation string) (Value, bool,int , error) { 60 | r.rw.RLock() 61 | v, ok := r.set[regulation] 62 | r.rw.RUnlock() 63 | if ok { 64 | val ,slow ,err := r.singleFlight.Get(regulation, v.call) 65 | if err != nil { 66 | return nil, false, 0, err 67 | } 68 | return val,slow,v.expire,nil 69 | } 70 | return nil, false, 0, nil 71 | } 72 | -------------------------------------------------------------------------------- /sds.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import ( 17 | "sync" 18 | "time" 19 | ) 20 | 21 | // sds is a short of simple dynamic string 22 | 23 | type SDSStatus uint8 24 | 25 | const ( 26 | SDSStatusDelete SDSStatus = iota + 1 // the key is not exist 27 | SDSStatusNormal 28 | ) 29 | 30 | var sdsPool = sync.Pool{New: func() interface{} { 31 | return &sds{ 32 | key: "", 33 | expire: 0, 34 | st: SDSStatusNormal, 35 | Value: nil, 36 | } 37 | }} 38 | 39 | type sds struct { 40 | key string // key 41 | expire int64 // 过期时间 42 | 43 | st SDSStatus // 当前的key的状态 44 | Value Value 45 | } 46 | 47 | func NewSDS(key string, value Value, expire int) *sds { 48 | sd := sdsPool.Get().(*sds) 49 | if expire > 0 { 50 | sd.expire = time.Now().Unix() + int64(expire) 51 | } 52 | sd.key = key 53 | sd.Value = value 54 | return sd 55 | } 56 | func (s *sds) Status() SDSStatus { 57 | return s.st 58 | } 59 | 60 | func (s *sds) Delete() { 61 | s.st = SDSStatusDelete 62 | } 63 | 64 | func (s *sds) Destroy() { 65 | s.key = "" 66 | s.expire = 0 67 | s.st = SDSStatusNormal 68 | s.Value = nil 69 | 70 | sdsPool.Put(s) 71 | } 72 | 73 | func (s *sds) ReUse() { 74 | s.st = SDSStatusNormal 75 | } 76 | 77 | //Calculation 这里 计算只计算sds的key值 + value值的大小 78 | func (s *sds) Calculation() int { 79 | return len(s.key) + s.Value.Len() 80 | } 81 | 82 | type Value interface { 83 | Len() int 84 | } 85 | 86 | // ==========================================defaultValue byte======================================== 87 | 88 | type DefaultByteValue struct { 89 | cot []byte 90 | } 91 | 92 | func ByteValue(cot []byte) Value{ 93 | return &DefaultByteValue{cot:cot} 94 | } 95 | 96 | func (d *DefaultByteValue) Len() int { 97 | return len(d.cot) 98 | } 99 | 100 | func (d *DefaultByteValue) Value() []byte { 101 | return d.cot 102 | } 103 | 104 | // ==========================================defaultValue String======================================== 105 | 106 | type DefaultStringValue struct { 107 | cot string 108 | } 109 | 110 | func StringValue(cot string) Value{ 111 | return &DefaultStringValue{cot:cot} 112 | } 113 | 114 | func (d *DefaultStringValue) Len() int { 115 | return len(d.cot) 116 | } 117 | 118 | func (d *DefaultStringValue) Value() string { 119 | return d.cot 120 | } 121 | -------------------------------------------------------------------------------- /single_flight_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import ( 17 | "fmt" 18 | . "github.com/smartystreets/goconvey/convey" 19 | "go.uber.org/atomic" 20 | "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | var mockerCaller = func() (Value, error) { 26 | incr.Inc() 27 | time.Sleep(2 * time.Second) 28 | // select * from list 29 | return StringValue("steven "), nil 30 | } 31 | 32 | var incr atomic.Int32 33 | 34 | func TestDefaultSingleFlight_Get(t *testing.T) { 35 | Convey("测试是否存在流量漏洞", t, func() { 36 | ob := NewSingleFlight(10) 37 | Convey("测试多个并发请求,是否会存在穿透", func() { 38 | var parallel = 5 39 | in := atomic.Int32{} 40 | for i := 0; i < parallel; i++ { 41 | go func() { 42 | _, _, err := ob.Get("10", mockerCaller) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | in.Inc() 47 | }() 48 | } 49 | time.Sleep(3 * time.Second) 50 | So(in.Load(), ShouldEqual, parallel) 51 | So(incr.Load(), ShouldEqual, 1) 52 | }) 53 | }) 54 | } 55 | 56 | var qukCaller = func() (Value, error) { 57 | incr.Inc() 58 | fmt.Println("进入了注册函数找中") 59 | defer func() { 60 | fmt.Println("出去了注册函数找中") 61 | }() 62 | time.Sleep(1 * time.Second) 63 | return StringValue("steven "), nil 64 | } 65 | 66 | func TestDefaultSingleFlight_GetWithManyReq(t *testing.T) { 67 | Convey("测试是否存在流量漏洞", t, func() { 68 | ob := NewSingleFlight(10) 69 | var parallel = 1000 70 | in := atomic.Int32{} 71 | for i := 0; i < parallel; i++ { 72 | // 模拟100个请求,这些请求开始时间不一定,在10秒内的均匀分布,理想状况是每秒10个请求 73 | // 后台请求耗时在1秒左右,那么此时预计慢请求应该在10个以下,总请求数不变, 74 | go func() { 75 | in.Inc() 76 | time.Sleep(time.Duration(rand.Intn(10)) * time.Second) 77 | fmt.Println("调用Caller") 78 | _, _, err := ob.Get("10", qukCaller) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | }() 83 | } 84 | time.Sleep(11 * time.Second) 85 | So(in.Load(), ShouldEqual, parallel) 86 | So(incr.Load(), ShouldBeLessThanOrEqualTo, 10) 87 | }) 88 | } 89 | 90 | var timeOutCaller = func() (Value, error) { 91 | incr.Inc() 92 | time.Sleep(3 * time.Second) 93 | // select * from list 94 | return StringValue("steven "), nil 95 | } 96 | 97 | func TestDefaultSingleFlight_GetTimeOut(t *testing.T) { 98 | Convey("测试超时是否正确的错误处理", t, func() { 99 | ob := NewSingleFlight(2) 100 | var parallel = 5 101 | in := atomic.Int32{} 102 | for i := 0; i < parallel; i++ { 103 | go func() { 104 | in.Inc() 105 | _, _, err := ob.Get("10", timeOutCaller) 106 | if err != ErrSlowCallIsTimeOut { 107 | t.Fatal("出现错误", err) 108 | } 109 | }() 110 | } 111 | time.Sleep(3 * time.Second) 112 | So(incr.Load(), ShouldEqual, 1) 113 | So(in.Load(), ShouldEqual, parallel) 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /single_flight.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | var ( 24 | ErrTopicNotExist = errors.New("sCache : topic not exist ") 25 | ErrSlowCallIsTimeOut = errors.New("sCache : slow call timeout") 26 | ) 27 | 28 | type Element struct { 29 | value Value 30 | err error 31 | } 32 | 33 | type Topic struct { 34 | // 保护notify的用户切片集合 35 | rw sync.RWMutex 36 | 37 | // notify 是订阅了这个主题的所有请求的集合 38 | notify []chan *Element 39 | 40 | // OnCall 观察者慢函数,所有订阅对象其实就是为了获取到这个方法的返回 41 | OnCall func() (Value, error) 42 | } 43 | 44 | func (o *Topic) publish(value *Element) { 45 | if len(o.notify) == 0 { 46 | return 47 | } 48 | o.rw.Lock() 49 | for _, v := range o.notify { 50 | tev := v 51 | tev <- value 52 | } 53 | o.rw.Unlock() 54 | } 55 | 56 | func (o *Topic) subscribe(notify chan *Element) { 57 | o.rw.Lock() 58 | o.notify = append(o.notify, notify) 59 | o.rw.Unlock() 60 | } 61 | 62 | type SingleFlight interface { 63 | Get(regulation string, slowWay func() (Value, error)) (Value, bool, error) 64 | } 65 | 66 | // 这里是一个分布式锁的快捷实现 , short for distributed lock 67 | type defaultSingleFlight struct { 68 | rw *sync.RWMutex 69 | 70 | // 这里是一个观察者,用户可以订阅,内部调用push 方法,用户请求到内部大概率会被 71 | // 阻塞到observer 当中,最好使 72 | obs map[string]*Topic 73 | 74 | // 超时时间,如果存在update方法特别慢,超过了expireTime的最大等待时间,那么 75 | // 就会返回超时错误 76 | MaxWaitTime int 77 | } 78 | 79 | func NewSingleFlight(maxWaitTime int) SingleFlight { 80 | l := &defaultSingleFlight{ 81 | rw: &sync.RWMutex{}, 82 | obs: map[string]*Topic{}, 83 | MaxWaitTime: maxWaitTime, 84 | } 85 | return l 86 | } 87 | 88 | func (l *defaultSingleFlight) notify(topicName string, no *Element) { 89 | l.rw.Lock() 90 | defer l.rw.Unlock() 91 | // 将这步操作从最后放在第一排 92 | v, ok := l.obs[topicName] 93 | if ok { 94 | v.publish(no) 95 | } 96 | delete(l.obs, topicName) 97 | } 98 | 99 | func (l *defaultSingleFlight) setNx(topicName string, slow func() (Value, error)) (chan *Element, error) { 100 | l.rw.Lock() 101 | defer l.rw.Unlock() 102 | if val, ok := l.obs[topicName]; ok { 103 | ch := make(chan *Element, 1) 104 | val.subscribe(ch) 105 | return ch, nil 106 | } else { 107 | // 将内容注册到topic管理中心中 108 | ob := &Topic{ 109 | notify: []chan *Element{}, 110 | OnCall: slow, 111 | } 112 | l.obs[topicName] = ob 113 | return nil, nil 114 | } 115 | } 116 | 117 | func (l *defaultSingleFlight) slowWay(topicName string, slow func() (Value, error)) (Value, bool, error) { 118 | v, err := l.call(slow) 119 | if err != nil { 120 | return nil, false, err 121 | } 122 | 123 | niy := &Element{ 124 | value: v, 125 | err: err, 126 | } 127 | l.notify(topicName, niy) 128 | return v, true, err 129 | } 130 | 131 | func (l *defaultSingleFlight) call(slow func() (Value, error)) (Value, error) { 132 | t := time.NewTicker(time.Duration(l.MaxWaitTime) * time.Second) 133 | ch := make(chan *Element) 134 | go func() { 135 | defer func() { 136 | if err := recover(); err != nil { 137 | // todo 记录好err 138 | fmt.Println(err) 139 | } 140 | }() 141 | // 2 .执行慢操作,此操作不占用锁 142 | val, err := slow() 143 | niy := &Element{ 144 | value: val, 145 | err: err, 146 | } 147 | ch <- niy 148 | }() 149 | select { 150 | case <-t.C: 151 | return nil, ErrSlowCallIsTimeOut 152 | case data := <-ch: 153 | return data.value, data.err 154 | } 155 | } 156 | 157 | func (l *defaultSingleFlight) Get(topicName string, slow func() (Value, error)) (Value, bool /* is slow path*/, error) { 158 | if topicName == "" || slow == nil { 159 | return nil, false, ErrInValidParam 160 | } 161 | 162 | // 对标志位进行加锁操作,标志位设置成功,就进入slowWay 如果标志位设置失败就进入 163 | // waitWay, 这里需要注意临界值的判断,所以对资源颗粒度相对较小,要求比较高 164 | ch, err := l.setNx(topicName, slow) 165 | if err != nil { 166 | return nil, false, err 167 | } 168 | if ch != nil { 169 | data := <-ch 170 | return data.value, false, data.err 171 | } 172 | return l.slowWay(topicName, slow) 173 | } 174 | -------------------------------------------------------------------------------- /impl_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 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 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package Scache 15 | 16 | import ( 17 | "fmt" 18 | . "github.com/smartystreets/goconvey/convey" 19 | "go.uber.org/atomic" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func TestCacheImpl_Set(t *testing.T) { 25 | var k1, k2, k3 string = "ky1", "ky2", "ky3" 26 | var v1, v2, v3 = "hah", "ddd", "ccc" 27 | ca := New(50, 10*time.Second, nil) 28 | ca.Set(k1, StringValue(v1)) 29 | ca.Set(k2, StringValue(v2)) 30 | ca.Set(k3, StringValue(v3)) 31 | Convey("test set value ", t, func() { 32 | Convey("test for get key from cache ", func() { 33 | expect := []string{"hah", "ddd", "ccc"} 34 | give := []string{"ky1", "ky2", "ky3"} 35 | for k, v := range give { 36 | res, err := ca.Get(v) 37 | if err != nil { 38 | t.Fatalf("Call Set func failed, give %v , expect %v,but get nil ", v, expect[k]) 39 | } 40 | So(res.(*DefaultStringValue).Value(), ShouldEqual, expect[k]) 41 | } 42 | }) 43 | 44 | Convey("test for set value repeat ", func() { 45 | ca.Set(k1, StringValue("cccc")) 46 | res, err := ca.Get(k1) 47 | if err != nil { 48 | t.Fatalf("Call Set func failed, give %v , expect %v,but get nil ", k1, "cccc") 49 | } 50 | So(res.(*DefaultStringValue).Value(), ShouldEqual, "cccc") 51 | }) 52 | 53 | Convey("test for set nil value ", func() { 54 | ca.Set("k5", nil) 55 | ca.Set("", nil) 56 | _, err := ca.Get("k5") 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | _, err = ca.Get("") 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | }) 65 | 66 | Convey("test for set a bigger than capacity value ", func() { 67 | err := ca.Set("haha", StringValue("i am a bigger data witch is bigger than maxBytes setting")) 68 | if err == nil { 69 | t.Fatalf("Call Set func failed, give a big-Value , expect err,but get nil ") 70 | } 71 | So(err, ShouldEqual, ErrValueIsBiggerThanMaxByte) 72 | }) 73 | }) 74 | } 75 | 76 | func TestCacheImpl_Get(t *testing.T) { 77 | Convey("test get key from cache", t, func() { 78 | ca := New(5000, 10*time.Second, nil) 79 | ca.Set("key1", StringValue("im good man")) 80 | ca.Set("key2", StringValue("im good man1")) 81 | ca.Set("key3", StringValue("im good man2")) 82 | Convey("test get key witch existed ", func() { 83 | res, err := ca.Get("key1") 84 | if err != nil { 85 | t.Fatalf("Call Set func failed, give %v , expect %v,but get nil ", "key1", "im good man") 86 | } 87 | So(res.(*DefaultStringValue).Value(), ShouldEqual, "im good man") 88 | }) 89 | Convey("test get a not exist key ", func() { 90 | val, err := ca.Get("key4") 91 | if err !=nil { 92 | t.Fatal(err) 93 | } 94 | So(val, ShouldEqual, nil) 95 | }) 96 | }) 97 | } 98 | 99 | func TestCacheImpl_Del(t *testing.T) { 100 | Convey("test del key from cache", t, func() { 101 | var keys []string 102 | oncaller := func(key string, value Value) { 103 | keys = append(keys, key) 104 | } 105 | ca := New(5000, 10*time.Second, oncaller) 106 | ca.Set("key1", StringValue("im good man")) 107 | ca.Set("key2", StringValue("im good man1")) 108 | ca.Set("key3", StringValue("im good man2")) 109 | Convey("test del key1 ", func() { 110 | ca.Del("key1") 111 | So(keys, ShouldResemble,[]string{"key1"}) 112 | val,err := ca.Get("key1") 113 | if err != nil { 114 | t.Fatal(err) 115 | } 116 | So(val, ShouldBeNil) 117 | }) 118 | }) 119 | } 120 | 121 | func TestCacheImpl_Expire(t *testing.T) { 122 | Convey("test expire key from cache ", t, func() { 123 | cache := New(200*1204*1024, 10*time.Second, func(key string, value Value) { 124 | fmt.Println("delete the ", key, value) 125 | }) 126 | cache.Set("userA", StringValue("你好")) 127 | cache.Expire("userA", 3) 128 | v, err := cache.Get("userA") 129 | if err !=nil { 130 | t.Fatal(err) 131 | } 132 | So(v, ShouldEqual,StringValue("你好")) 133 | time.Sleep(4 * time.Second) 134 | val, err := cache.Get("userA") 135 | if err !=nil { 136 | t.Fatal(err) 137 | } 138 | So(val, ShouldBeNil) 139 | }) 140 | } 141 | 142 | func TestCacheImpl_Register(t *testing.T) { 143 | Convey("test expire key from cache ", t, func() { 144 | var in atomic.Int32 145 | cache := New(200*1204*1024, 10*time.Second, func(key string, value Value) { 146 | fmt.Println("delete the ", key, value) 147 | }) 148 | cache.Register("testKey", 3, func() (Value, error) { 149 | in.Inc() 150 | return StringValue("steven"), nil 151 | }) 152 | 153 | Convey("实际请求次数应该小于4次 ", func() { 154 | // 进行查询 155 | for i := 0; i < 10; i++ { 156 | _,err :=cache.Get("testKey") 157 | if err !=nil { 158 | t.Fatal(err) 159 | } 160 | time.Sleep(1 * time.Second) 161 | } 162 | So(in.Load(),ShouldBeLessThanOrEqualTo,4) 163 | }) 164 | }) 165 | } 166 | 167 | func TestCacheImpl_RegisterCron(t *testing.T) { 168 | Convey("测试注册定时器 ", t, func() { 169 | var in atomic.Int32 170 | cache := New(200*1204*1024, 10*time.Second, func(key string, value Value) { 171 | fmt.Println("delete the ", key, value) 172 | }) 173 | cache.RegisterCron("testKey", 3, func() (Value, error) { 174 | in.Inc() 175 | t := time.Now().Unix() 176 | fmt.Printf("time : %v , 进入定时器\r\n",t) 177 | return StringValue(fmt.Sprintf("steven %v",t)), nil 178 | }) 179 | 180 | Convey("实际请求次数应该小于4次 ", func() { 181 | // 进行查询 182 | for i := 0; i < 1000; i++ { 183 | time.Sleep(1 * time.Second) 184 | v ,err :=cache.Get("testKey") 185 | if err !=nil { 186 | t.Fatal(err) 187 | } 188 | if v == nil { 189 | fmt.Println(v) 190 | continue 191 | } 192 | fmt.Println(v.(*DefaultStringValue).Value()) 193 | } 194 | So(in.Load(),ShouldBeLessThanOrEqualTo,4) 195 | }) 196 | }) 197 | } -------------------------------------------------------------------------------- /impl.go: -------------------------------------------------------------------------------- 1 | package Scache 2 | 3 | import ( 4 | "container/list" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | var ( 13 | ErrValueIsBiggerThanMaxByte = errors.New("sCache : value size is bigger than maxBytes ") 14 | ErrInValidParam = errors.New("sCache : the param is invalid") 15 | ErrNotifyNotExist = errors.New("sCache : notify is not exist") 16 | ErrTopicAlreadyExist = errors.New("sCache : Topic already exist") 17 | ErrBadConvertParamToCall = errors.New("sCache : Can't Convert Param item[0] to Call") 18 | 19 | ErrRegulationAlreadyExist = errors.New("sCache : regulation already exist ") 20 | 21 | ErrKeyAlreadyExist = errors.New("sCache : key already exist ") 22 | ErrKeyNotExist = errors.New("sCache : key is not exist ") 23 | ) 24 | 25 | type cacheImpl struct { 26 | rw sync.RWMutex 27 | maxBytes int64 28 | nBytes int64 29 | ll *list.List 30 | interval time.Duration 31 | cache map[string]*list.Element 32 | 33 | // 当某个key被删除的时候的回调函数 34 | OnCaller func(key string, v Value) 35 | 36 | // 程序内部发生一些定时器调用错误,注册函数调用错误的时候会调用此函数 37 | // 实现此方法的时候需要判断param的length 38 | // attention: 39 | // 当参数大于两个的时候,第一个参数是描述,第二个参数是详细的描述(包含recover的panic ) 40 | // 当参数为一个的时候就是error消息 41 | // 当参数为nil的时候可以不处理 42 | OnError func(...interface{}) 43 | 44 | // singleFlight 管理器, 45 | regularManger RegularManger 46 | } 47 | 48 | func New(maxByte int64, clearInterval time.Duration, clearCall func(key string, value Value)) Cache { 49 | c := &cacheImpl{ 50 | maxBytes: maxByte, 51 | nBytes: 0, 52 | ll: list.New(), 53 | interval: clearInterval, 54 | cache: make(map[string]*list.Element), 55 | OnError: func(i ...interface{}) { 56 | fmt.Println(i) 57 | }, 58 | OnCaller: clearCall, 59 | regularManger: NewRegularManager(), 60 | } 61 | c.clear() 62 | return c 63 | } 64 | 65 | func (c *cacheImpl) SetErrorHandler(handler func(...interface{})) { 66 | c.OnError = handler 67 | } 68 | 69 | func (c *cacheImpl) Get(key string) (Value, error) { 70 | return c.get(key) 71 | } 72 | 73 | func (c *cacheImpl) Set(key string, value Value) error { 74 | if value == nil || key == "" { 75 | return ErrInValidParam 76 | } 77 | return c.set(key, value, 0) 78 | } 79 | 80 | func (c *cacheImpl) SetNX(key string, value Value) error { 81 | if value == nil || key == "" { 82 | return ErrInValidParam 83 | } 84 | return c.setNx(key, value) 85 | } 86 | 87 | func (c *cacheImpl) SetEX(key string, value Value) error { 88 | if value == nil || key == "" { 89 | return ErrInValidParam 90 | } 91 | return c.setNx(key, value) 92 | } 93 | 94 | func (c *cacheImpl) Del(key string) { 95 | c.del(key, false) 96 | } 97 | 98 | func (c *cacheImpl) Expire(key string, ttl int) { 99 | if ttl <= 0 || key == "" { 100 | return 101 | } 102 | c.expire(key, ttl) 103 | } 104 | 105 | func (c *cacheImpl) SetWithTTL(key string, value Value, ttl int) error { 106 | if value == nil || key == "" { 107 | return ErrInValidParam 108 | } 109 | return c.set(key, value, ttl) 110 | } 111 | 112 | func (c *cacheImpl) Register(regulation string, expire int, f /* slow way func */ func() (Value, error)) { 113 | if regulation == "" || f == nil || expire < 0 { 114 | panic(ErrInValidParam) 115 | } 116 | c.regularManger.Register(regulation, expire, f) 117 | } 118 | 119 | func (c *cacheImpl) RegisterCron(regulation string, flushInterval int, f /* slow way func */ func() (Value, error)) { 120 | if regulation == "" || f == nil || flushInterval < 0 { 121 | panic(ErrInValidParam) 122 | } 123 | err := c.ticker(regulation, flushInterval, f) 124 | if err != nil { 125 | panic(err) 126 | } 127 | } 128 | 129 | // =============================================concurrency safe ========================================= 130 | 131 | func (c *cacheImpl) ticker(regulation string, flushInterval int, f /* slow way func */ func() (Value, error)) error { 132 | if flushInterval <= 0 || regulation == "" { 133 | return ErrInValidParam 134 | } 135 | val, _, _, err := c.regularManger.Get(regulation) 136 | if err != nil { 137 | return err 138 | } 139 | if val != nil { 140 | return ErrKeyAlreadyExist 141 | } 142 | t := time.NewTicker(time.Duration(flushInterval) * time.Second) 143 | go func() { 144 | defer func() { 145 | // issue error #9 146 | if err := recover(); err != nil { 147 | c.OnError(fmt.Sprintf("regulation is %s", regulation), err) 148 | } 149 | }() 150 | for { 151 | v, er := f() 152 | if er != nil { 153 | if c.OnError != nil { 154 | c.OnError(er) 155 | } 156 | } else { 157 | c.set(regulation, v, 0) 158 | } 159 | 160 | <-t.C 161 | } 162 | }() 163 | return nil 164 | } 165 | 166 | func (c *cacheImpl) get(key string) (Value, error) { 167 | // 去获取值是否存在于map中且状态不为过期状态 168 | if val, ok := c.getDetection(key); ok { 169 | return val, nil 170 | } 171 | // 如果key 不存在cache中, 去查询regulation查看是否存在key 172 | val, shouldSave, expire, err := c.regularManger.Get(key) 173 | if err != nil { 174 | return nil, err 175 | } 176 | if shouldSave { 177 | if err = c.set(key, val, expire); err != nil { 178 | return nil, err 179 | } 180 | } 181 | return val, nil 182 | } 183 | 184 | // expire 拥有两个值0 ,大于0 185 | // 1. 当值为0 的时候表示用不过期 186 | // 2. 当值为大于0的时候表示,过期时间表示: time_now + expire 187 | func (c *cacheImpl) set(key string, value Value, expire int) error { 188 | c.rw.Lock() 189 | defer c.rw.Unlock() 190 | if int64(value.Len()) > c.maxBytes { 191 | return ErrValueIsBiggerThanMaxByte 192 | } 193 | if ele, ok := c.getElem(key); ok { 194 | // 如果说这个值存在于Element,有两种情况: 195 | // 1. 这个值存在 ,但是已经过期 196 | // 2. 这个值正常 197 | kv := ele.Value.(*sds) 198 | oldLen := kv.Value.Len() 199 | kv.ReUse() 200 | if expire > 0 { 201 | kv.expire = int64(expire) + time.Now().Unix() 202 | } else { 203 | kv.expire = 0 204 | } 205 | kv.Value = value 206 | 207 | //当oldLen小于value.Len()的时候,相减变成负数,此时c.nBytes就有可能等于负数 208 | newLen := int64(oldLen - value.Len()) 209 | if newLen < 0 { 210 | c.nBytes += int64(math.Abs(float64(newLen))) 211 | } else { 212 | c.nBytes -= newLen 213 | if c.nBytes < 0 { 214 | c.nBytes = 0 215 | } 216 | } 217 | //c.nBytes += int64(oldLen - value.Len()) 218 | } else { 219 | // 创建新的sds结构体 220 | newSds := NewSDS(key, value, expire) 221 | eles := c.ll.PushFront(newSds) 222 | c.cache[key] = eles 223 | c.nBytes += int64(newSds.Calculation()) 224 | } 225 | var freeBytes, freeElems int64 226 | for c.maxBytes != 0 && c.maxBytes < c.nBytes { 227 | freeBytes += c.removeOldest() 228 | freeElems++ 229 | } 230 | if freeBytes != 0 { 231 | fmt.Printf(" sCache : Garbage.Collection.removeOldest, free bytes %v ,free Element %v \n\r", freeBytes, freeElems) 232 | } 233 | return nil 234 | } 235 | 236 | func (c *cacheImpl) del(key string, del bool) { 237 | c.rw.Lock() 238 | defer c.rw.Unlock() 239 | if del { 240 | c.RealDel() 241 | return 242 | } 243 | if v, ok := c.getElem(key); ok { 244 | s := v.Value.(*sds) 245 | c.fakeDel(s) 246 | } 247 | return 248 | } 249 | 250 | func (c *cacheImpl) expire(key string, ttl int) { 251 | c.rw.Lock() 252 | defer c.rw.Unlock() 253 | if v, ok := c.getElem(key); ok { 254 | v.Value.(*sds).expire = time.Now().Unix() + int64(ttl) 255 | } 256 | } 257 | 258 | func (c *cacheImpl) clear() { 259 | go func() { 260 | fmt.Printf("sCache : start the backend goroutine , intarvel is %v\n\r", c.interval) 261 | for { 262 | time.Sleep(c.interval) 263 | sin := time.Now() 264 | counter, free := c.RealDel() 265 | escape := time.Since(sin) 266 | if counter > 0 { 267 | fmt.Printf("sCache : clear once spend %v , clear %v element ,clear memory %v byte \n\r", escape, counter, free) 268 | } 269 | } 270 | }() 271 | } 272 | 273 | func (c *cacheImpl) getDetection(key string) (Value, bool) { 274 | c.rw.RLock() 275 | defer c.rw.RUnlock() 276 | if ele, ok := c.getElem(key); ok { 277 | 278 | // flushKey 在读取elem的时候判断key值过期了没有,这里会出现一个问题 279 | // 如果某个值一直没被访问只能依靠lru进行淘汰,这里是需要改进的一个地方 280 | // todo 设置一个阈值,超过这个阈值的时候主动开启扫描过期的值,并清除掉 281 | 282 | s := ele.Value.(*sds) 283 | // 1. 这个key 标记为被删除,如果被标记删除了直接返回 284 | if s.Status() == SDSStatusDelete { 285 | return nil, false 286 | } 287 | // 2.查看是否过期,如果过期了,将key标注一下更新为过期 288 | if s.expire != 0 && s.expire < time.Now().Unix() { 289 | // todo 此时将值进行标准为删除 290 | // 第一个准则是存储的所有的内容都先不能删除,进行内存复用 291 | // 但是先进行回调删除方法,让用户感知 292 | // 内部内存是对用户不可见的,所以不需要告诉用户 293 | 294 | c.fakeDel(s) 295 | return nil, false 296 | } 297 | 298 | // 当key值存在的时候,需要将值的访问记录进行更新, 299 | c.ll.MoveToFront(ele) 300 | return s.Value, true 301 | } 302 | return nil, false 303 | } 304 | 305 | func (c *cacheImpl) setNx(key string, value Value) error { 306 | if _, ok := c.getDetection(key); ok { 307 | return ErrKeyAlreadyExist 308 | } 309 | return c.set(key, value, 0) 310 | } 311 | 312 | func (c *cacheImpl) setEx(key string, value Value) error { 313 | if _, ok := c.getDetection(key); !ok { 314 | return ErrKeyNotExist 315 | } 316 | c.rw.Lock() 317 | defer c.rw.Unlock() 318 | return c.set(key, value, 0) 319 | } 320 | 321 | // realDel For testing ,请勿直接调用 真删除,将标记出来的内容删除, 这是一个On操作,需要在后台线程上进行操作 322 | // 删除,是一个On操作,需要判断所有element 的内容是否为delete 323 | // 1. 需要将内容删除 324 | // 2. 需要释放空间 325 | // 3. 将元素释放 326 | // todo 待优化 327 | func (c *cacheImpl) RealDel() (int, int) { 328 | c.rw.Lock() 329 | defer c.rw.Unlock() 330 | fmt.Printf("sCache : RealDel current element counter %v ,current size %v ,max cap %v \n\r", len(c.cache), c.nBytes, c.maxBytes) 331 | counter := 0 332 | free := 0 333 | for k, v := range c.cache { 334 | tev := v 335 | sd := tev.Value.(*sds) 336 | st := sd.Status() 337 | if st == SDSStatusDelete { 338 | c.ll.Remove(v) 339 | counter++ 340 | delete(c.cache, k) 341 | freeCount := sd.Calculation() 342 | c.nBytes -= int64(freeCount) 343 | free += freeCount 344 | sd.Destroy() 345 | } 346 | 347 | if sd.expire != 0 && sd.expire < time.Now().Unix() && st == SDSStatusNormal { 348 | sd.Delete() 349 | } 350 | } 351 | return counter, free 352 | } 353 | 354 | // =============================================concurrency not safe ========================================= 355 | 356 | // removeOldest 直接真删除, 357 | // todo 之前的版本存在一个问题,realDeal后,实际上没有删除掉链表节点 358 | func (c *cacheImpl) removeOldest() (freeByte int64) { 359 | ele := c.ll.Back() 360 | if ele != nil { 361 | // 删除 链表节点 362 | c.ll.Remove(ele) 363 | kv := ele.Value.(*sds) 364 | delete(c.cache, kv.key) 365 | freeByte = int64(kv.Calculation()) 366 | c.nBytes -= freeByte 367 | if c.OnCaller != nil { 368 | c.OnCaller(kv.key, kv.Value) 369 | } 370 | } 371 | return 372 | } 373 | 374 | // getElem 并发不安全,需要加锁操作 375 | func (c *cacheImpl) getElem(key string) (*list.Element, bool) { 376 | if ele, ok := c.cache[key]; ok { 377 | return ele, true 378 | } 379 | return nil, false 380 | } 381 | 382 | // fakeDel 假删除,将内容标记为删除 383 | func (c *cacheImpl) fakeDel(sd *sds) { 384 | sd.Delete() 385 | if c.OnCaller != nil { 386 | c.OnCaller(sd.key, sd.Value) 387 | } 388 | } 389 | --------------------------------------------------------------------------------