diff --git a/README.md b/README.md new file mode 100644 index 0000000..83cb08a --- /dev/null +++ b/README.md @@ -0,0 +1,236 @@ +# craw + +- 数据缓存包 + +## 1. 功能 + +实现了数据的二级缓存,使用K-V结构存储,用于缓存网络访问后的数据以便于二次访问,可用于redis,mysql等数据的二级缓存: + +
+ +
+ + +## 2. 使用 + +```go + import ( + "github.com/wonderivan/craw" + ) + + // 使用者需要实现自定义的CrawInterface包含的四个方法才可以进行craw初始化 + type benchCraw struct { + } + + func (this *benchCraw) Init() error { + // 创建缓存时,在创建完成后会执行用户自定义的初始化函数,如果不需要初始化其他项,可以直接返回nil + return nil + } + + func (this *benchCraw) CustomGet(key string) (data interface{}, expired time.Duration, err error) { + // 当调用craw获取远端数据时,内部会调用该方式实现,用户需要指定缓存数据过期时间,0为马上过期 + return key, -1, nil + } + + func (this *benchCraw) CustomSet(key string, data interface{}) error { + // 当调用craw设置远端数据时,内部会调用该方式实现,不需要设置,可以直接返回nil + return nil + } + + func (this *benchCraw) Destroy() { + // 销毁缓存时,会执行用户自定义的销毁方法,如果不需要销毁其他项,该方法可以为空 + } + + + // 创建缓存,使用默认配置 + craw := NewCraw("mytest1", new(benchCraw)) + // 使用完后销毁缓存 + defer craw.Destroy() + + // 设置缓存数据 key:"name",value:"Lily", 过期时间-1,不过期 + handler.SetCraw("name","Lily", -1) + + // 获取缓存数据 + handler.GetData("name") + + // 获取缓存数据命中率 + handler.HitRate() + +``` + +## 3. 方法列表 +```go +// 创建一个craw +// 参数 crawName:缓存名称,config缓存配置, dispose缓存数据处理方法 +func NewCraw(crawName string, dispose CrawInterface, config ...string) *Craw + +// config格式 +{ + "low": 858993459, // 缓存压缩最低阈值限制,默认为800 MB + "high": 1073741824, // 缓存触发清理最高阈值,默认为1 GB,当缓存数据大于1G时,开始清理,详见LruCache说明 + "interval": 3600 // 缓存有效数据检查间隔,默认为1天 +} + +// 销毁craw +func (dc *Craw) Destroy() + +// 获取缓存数据,如果不存在则从远端获取数据并更新到craw然后返回 +// +// 参数 key:要查找的craw数据的key +// 返回值 interface{}:找到的数据 error:成功为nil +func (dc *Craw) GetData(key string) (interface{}, error) + +// 强制从远端获取数据,并更新到craw +// +// 参数 key:要查找的远端数据的key +// 返回值 interface{}:更新到craw的数据 +// 返回值 成功为nil +func (dc *Craw) UpdateData(key string) (data interface{}, err error) + +// 删除craw缓存数据 +// +// 参数 key:要查找的远端数据的key +// 参数可选 delay:延迟删除数据的时间 单位(s) +// 返回值 成功为nil +func (dc *Craw) DeleteData(key string, delay ...time.Duration) (err error) + +// 保存数据到远端,并删除craw中已有的缓存值 +// +// 参数 key:要保存到远端的数据的key data:要保存到远端的数据 +// 返回值 error:成功为nil +func (dc *Craw) SetData(key string, data interface{}) (err error) + +// 清空craw的所有数据 +func (dc *Craw) ClearAll() error + +// 清除craw中所有包含前缀prefix的key的数据 +func (dc *Craw) ClearPrefixKeys(Prefix string) error + +// 获取当前craw缓存命中率 +// +// 返回值 float64:计算的结果,XX.XXXXX% +func (dc *Craw) HitRate() + +// 获取当前craw缓存命中率并重置命中率为0 +// +// 返回值 float64:计算的结果,XX.XXXXX% +func (dc *Craw) ResetHitRate() float64 + +// 设置craw缓存数据,不更新远端 +// +// 参数 key:要保存的数据的key data:要保存的数据,expired要保存的数据的过期时间,<0不过期 +// 返回值 error:成功为nil +func (dc *Craw) SetCraw(key string, data interface{}, expired time.Duration) error + +// 查询craw中指定的key是否存在 +func (dc *Craw) IsExist(key string) (bool, error) +``` + +## 4. 性能测试 + +针对当前设计的二级缓存进行精简的K-V读写压力测试 +测试分为1W,5W,10W,50W,100W,200W, +分别设置命中率为0%,10%,30%,50%,70%,100%进行读写测试, +然后统计总用时,每条读写耗时,每秒读写条数 + +### 4.1 性能图示 + +
+读取缓存性能测试曲线图 + +
+ +
+写入缓存性能测试曲线图 + +
+ +
+读取缓存性能测试柱状图 + +
+ +
+写入缓存性能测试柱状图 + +
+ +### 4.2 实测数据 + +测试量|读取命中率|总用时|每条读取耗时|每秒读取条数 +|-|-|-|-|-| +|10000|0.00%|8.681537ms|868.15ns/op|1151869.77op| +|10000|10.00%|6.025206ms|602.52ns/op|1659694.29op| +|10000|30.00%|5.374784ms|537.48ns/op|1860539.88op| +|10000|50.00%|3.643583ms|364.36ns/op|2744551.17op| +|10000|70.00%|2.648619ms|264.86ns/op|3775552.47op| +|10000|100.00%|1.210079ms|121.01ns/op|8263923.26op| +|50000|0.00%|45.364929ms|907.30ns/op|1102173.00op| +|50000|10.00%|38.790983ms|775.82ns/op|1288959.34op| +|50000|30.00%|29.191049ms|583.82ns/op|1712853.83op| +|50000|50.00%|24.245481ms|484.91ns/op|2062239.97op| +|50000|70.00%|13.359667ms|267.19ns/op|3742608.26op| +|50000|100.00%|6.47295ms|129.46ns/op|7724453.30op| +|100000|0.00%|96.2213ms|962.21ns/op|1039270.93op| +|100000|10.00%|81.877599ms|818.78ns/op|1221335.28op| +|100000|30.00%|68.601832ms|686.02ns/op|1457687.02op| +|100000|50.00%|52.831959ms|528.32ns/op|1892793.72op| +|100000|70.00%|28.859204ms|288.59ns/op|3465099.04op| +|100000|100.00%|14.512613ms|145.13ns/op|6890557.89op| +|500000|0.00%|580.435456ms|1160.87ns/op|861422.22op| +|500000|10.00%|550.468097ms|1100.94ns/op|908317.85op| +|500000|30.00%|419.168104ms|838.34ns/op|1192838.85op| +|500000|50.00%|312.806598ms|625.61ns/op|1598431.76op| +|500000|70.00%|259.572573ms|519.15ns/op|1926243.57op| +|500000|100.00%|89.384522ms|178.77ns/op|5593809.63op| +|1000000|0.00%|1.163256084s|1163.26ns/op|859655.94op| +|1000000|10.00%|1.070822941s|1070.82ns/op|933861.20op| +|1000000|30.00%|851.79008ms|851.79ns/op|1173998.18op| +|1000000|50.00%|647.7622ms|647.76ns/op|1543776.40op| +|1000000|70.00%|578.194375ms|578.19ns/op|1729522.19op| +|1000000|100.00%|195.846202ms|195.85ns/op|5106047.45op| +|2000000|0.00%|2.290312946s|1145.16ns/op|873243.11op| +|2000000|10.00%|2.440874727s|1220.44ns/op|819378.39op| +|2000000|30.00%|1.938333138s|969.17ns/op|1031814.38op| +|2000000|50.00%|1.50874304s|754.37ns/op|1325606.78op| +|2000000|70.00%|1.248193013s|624.10ns/op|1602316.29op| +|2000000|100.00%|424.174077ms|212.09ns/op|4715045.33op| + +测试量|写入命中率|总用时|每条写入耗时|每秒写入条数 +|-|-|-|-|-| +|10000|0.00%|2.84051ms|284.05ns/op|3520494.56op| +|10000|10.00%|2.72975ms|272.98ns/op|3663339.13op| +|10000|30.00%|2.409758ms|240.98ns/op|4149794.29op| +|10000|50.00%|2.157198ms|215.72ns/op|4635643.09op| +|10000|70.00%|1.676161ms|167.62ns/op|5966014.00op| +|10000|100.00%|1.272334ms|127.23ns/op|7859571.46op| +|50000|0.00%|19.780616ms|395.61ns/op|2527727.14op| +|50000|10.00%|21.111112ms|422.22ns/op|2368420.95op| +|50000|30.00%|18.702363ms|374.05ns/op|2673458.96op| +|50000|50.00%|15.233516ms|304.67ns/op|3282236.35op| +|50000|70.00%|10.959622ms|219.19ns/op|4562201.14op| +|50000|100.00%|8.132663ms|162.65ns/op|6148047.69op| +|100000|0.00%|40.368559ms|403.69ns/op|2477175.37op| +|100000|10.00%|41.886854ms|418.87ns/op|2387383.88op| +|100000|30.00%|39.782839ms|397.83ns/op|2513646.65op| +|100000|50.00%|39.339891ms|393.40ns/op|2541949.09op| +|100000|70.00%|36.249307ms|362.49ns/op|2758673.43op| +|100000|100.00%|16.909974ms|169.10ns/op|5913669.65op| +|500000|0.00%|290.534367ms|581.07ns/op|1720966.80op| +|500000|10.00%|294.119801ms|588.24ns/op|1699987.55op| +|500000|30.00%|301.406549ms|602.81ns/op|1658888.97op| +|500000|50.00%|240.751022ms|481.50ns/op|2076834.38op| +|500000|70.00%|232.707867ms|465.42ns/op|2148616.66op| +|500000|100.00%|103.425529ms|206.85ns/op|4834396.35op| +|1000000|0.00%|751.04895ms|751.05ns/op|1331471.14op| +|1000000|10.00%|763.607503ms|763.61ns/op|1309573.30op| +|1000000|30.00%|706.018802ms|706.02ns/op|1416392.87op| +|1000000|50.00%|466.665309ms|466.67ns/op|2142863.38op| +|1000000|70.00%|494.240314ms|494.24ns/op|2023307.23op| +|1000000|100.00%|215.737465ms|215.74ns/op|4635263.51op| +|2000000|0.00%|1.378425505s|689.21ns/op|1450930.78op| +|2000000|10.00%|1.321529037s|660.76ns/op|1513398.45op| +|2000000|30.00%|1.314644183s|657.32ns/op|1521324.19op| +|2000000|50.00%|882.820403ms|441.41ns/op|2265466.45op| +|2000000|70.00%|1.010007853s|505.00ns/op|1980182.62op| +|2000000|100.00%|468.810806ms|234.41ns/op|4266113.27op| diff --git a/batchget_test.go b/batchget_test.go new file mode 100644 index 0000000..43dbd4e --- /dev/null +++ b/batchget_test.go @@ -0,0 +1,213 @@ +package craw + +import ( + "fmt" + "log" + "testing" + "time" + + "github.com/wonderivan/logger" +) + +func init() { + logger.SetLogger(`{"Console": {"level": "WARN"}}`) +} + +type benchCraw struct { +} + +func (this *benchCraw) Init() error { + return nil +} + +func (this *benchCraw) CustomGet(key string) (data interface{}, expired time.Duration, err error) { + return key, -1, nil +} + +func (this *benchCraw) CustomSet(key string, data interface{}) error { + return nil +} + +func (this *benchCraw) Destroy() { + +} + +func TestGetByOrder(t *testing.T) { + handler := NewCraw("mytest", new(benchCraw)) + defer handler.Destroy() + + total := 2000000 + keys := make([]string, total) + fmt.Printf("fill cache use %d test data\n", total) + for i := 0; i < total; i++ { + keys[i] = fmt.Sprintf("key%d", i) + handler.SetCraw(keys[i], keys[i], -1) + } + + start := time.Now() + for i := 0; i < total; i++ { + _, err := handler.GetData(keys[i]) + if err != nil { + log.Fatalf("GetData err:%s", err) + } + } + interval := time.Since(start) + + fmt.Printf("test %d use %v, average:%.2fns/op,%.2fop/s \n", + total, interval, float64(interval)/float64(total), float64(total)/float64(interval)*1000000000) +} + +var outputType int +var outputGraph map[string][]string + +func TestStatisticsTable(t *testing.T) { + outputType = 1 + hitRate := []int{0, 1, 3, 5, 7, 10} + fmt.Println("测试量|读取命中率|总用时|每条读取耗时|每秒读取条数") + fmt.Println("|-|-|-|-|-|") + ExecStatisticsGet(10000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(50000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(100000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(500000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(1000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(2000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + fmt.Println() + + fmt.Println("测试量|写入命中率|总用时|每条写入耗时|每秒写入条数") + fmt.Println("|-|-|-|-|-|") + ExecStatisticsSet(10000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(50000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(100000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(500000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(1000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(2000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + fmt.Println() +} + +func TestStatisticsGraph(t *testing.T) { + outputType = 2 + outputGraph = make(map[string][]string) + hitRate := []int{0, 1, 3, 5, 7, 10} + ExecStatisticsGet(10000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(50000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(100000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(500000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(1000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsGet(2000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + + fmt.Println("读取命中率\t1W\t5W\t10W\t50W\t100W\t200W") + for _, hit := range hitRate { + k := fmt.Sprintf("%.2f%%", float32(hit)/10*100) + v := outputGraph[k] + fmt.Printf("%s", k) + for _, v1 := range v { + fmt.Printf("\t%s", v1) + } + fmt.Println() + } + fmt.Println() + + outputGraph = make(map[string][]string) + + ExecStatisticsSet(10000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(50000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(100000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(500000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(1000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + ExecStatisticsSet(2000000, hitRate) // 0%,10%,30%,50%,70%,100%命中率 + fmt.Println("写入命中率\t1W\t5W\t10W\t50W\t100W\t200W") + for _, hit := range hitRate { + k := fmt.Sprintf("%.2f%%", float32(hit)/10*100) + v := outputGraph[k] + fmt.Printf("%s", k) + for _, v1 := range v { + fmt.Printf("\t%s", v1) + } + fmt.Println() + } + fmt.Println() +} + +func ExecStatisticsGet(total int, hits []int) { + for _, hit := range hits { + ExecStatisticsTestGet(total, hit) + } +} + +func ExecStatisticsSet(total int, hits []int) { + for _, hit := range hits { + ExecStatisticsTestSet(total, hit) + } +} + +// hit : 0-10 => 0%-100% +func ExecStatisticsTestGet(total, hit int) { + handler := NewCraw("mytest1", new(benchCraw)) + defer handler.Destroy() + + keys := make([]string, total) + for i := 0; i < total; i++ { + keys[i] = fmt.Sprintf("key%d", i) + if i%10 < hit { + handler.SetCraw(keys[i], keys[i], -1) + } + } + + start := time.Now() + for i := 0; i < total; i++ { + _, err := handler.GetData(keys[i]) + if err != nil { + log.Fatalf("GetData err:%s", err) + } + } + interval := time.Since(start) + hitRate := float32(hit) / 10 * 100 + if int(hitRate) != int(handler.HitRate()) { + log.Fatalf("Unexpect hitRate:%v,%v", hitRate, handler.HitRate()) + } + switch outputType { + case 1: + fmt.Printf("|%d|%.2f%%|%v|%.2fns/op|%.2fop|\n", + total, hitRate, + interval, float64(interval)/float64(total), float64(total)/float64(interval)*1000000000) + case 2: + outputGraph[fmt.Sprintf("%.2f%%", hitRate)] = append(outputGraph[fmt.Sprintf("%.2f%%", hitRate)], fmt.Sprintf("%.2f", float64(total)/float64(interval)*100000)) + fmt.Printf("|%d|%.2f%%|%.2fW|\n", + total, hitRate, float64(total)/float64(interval)*100000) + } + +} + +// hit : 0-10 => 0%-100% +func ExecStatisticsTestSet(total, hit int) { + handler := NewCraw("mytest1", new(benchCraw)) + defer handler.Destroy() + + keys := make([]string, total) + for i := 0; i < total; i++ { + keys[i] = fmt.Sprintf("key%d", i) + if i%10 < hit { + handler.SetCraw(keys[i], keys[i], -1) + } + } + + start := time.Now() + for i := 0; i < total; i++ { + err := handler.SetCraw(keys[i], keys[i], -1) + if err != nil { + log.Fatalf("SetData err:%s", err) + } + } + interval := time.Since(start) + hitRate := float32(hit) / 10 * 100 + switch outputType { + case 1: + fmt.Printf("|%d|%.2f%%|%v|%.2fns/op|%.2fop|\n", + total, hitRate, + interval, float64(interval)/float64(total), float64(total)/float64(interval)*1000000000) + case 2: + outputGraph[fmt.Sprintf("%.2f%%", hitRate)] = append(outputGraph[fmt.Sprintf("%.2f%%", hitRate)], fmt.Sprintf("%.2f", float64(total)/float64(interval)*100000)) + fmt.Printf("|%d|%.2f%%|%.2fW|\n", + total, hitRate, float64(total)/float64(interval)*100000) + } +} diff --git a/craw.go b/craw.go new file mode 100755 index 0000000..efe210d --- /dev/null +++ b/craw.go @@ -0,0 +1,227 @@ +package craw + +import ( + "errors" + "sync" + "sync/atomic" + "time" + + logger "github.com/wonderivan/logger" + lrucache "github.com/wonderivan/lrucache" +) + +// 缓存数据操作接口 +type CrawInterface interface { + // 初始化 + Init() error + // 获取远端数据的方式 + CustomGet(key string) (data interface{}, expired time.Duration, err error) + // 设置远端数据的方式,设置后会清除对应的缓存 + CustomSet(key string, data interface{}) (err error) + // 销毁 + Destroy() +} + +type customValue struct { + data interface{} + err error + wait chan bool +} + +// 获取远端数据模块 +type Craw struct { + dispose CrawInterface + keyMap map[string]*customValue + craw *lrucache.LruCache + enable bool + sync.RWMutex + + // 统计命中率 + totalAccess *uint64 // 总访问量 + staggerAccess *uint64 // 未命中的访问数 +} + +// 创建一个craw +// +// 参数 crawName:缓存名称,config缓存配置, dispose缓存数据处理方法 +func NewCraw(crawName string, dispose CrawInterface, config ...string) *Craw { + logger.Info("craw(%s) use setting:%s", crawName, config) + c := new(Craw) + c.dispose = dispose + c.keyMap = make(map[string]*customValue) + c.craw = lrucache.NewLruCache(crawName, config...) + c.totalAccess = new(uint64) + c.staggerAccess = new(uint64) + err := c.dispose.Init() + if err == nil { + c.enable = true + } + + return c +} + +// 销毁Craw +func (c *Craw) Destroy() { + c.enable = false + c.craw.Destroy() + c.dispose.Destroy() +} + +// 获取远端数据更新到缓存并返回 +// +// 参数 key:要查找的远端数据的key +// 返回值 interface{}:找到的数据 error:成功为nil +func (c *Craw) GetData(key string) (interface{}, error) { + if !c.enable { + return nil, errors.New("this craw has been destroyed.") + } + + atomic.AddUint64(c.totalAccess, 1) + + // 先查找craw + data, exist := c.craw.GetEx(key) + if exist { + return data, nil + } + + c.Lock() + value, ok := c.keyMap[key] + + if !ok { + atomic.AddUint64(c.staggerAccess, 1) + + valueOpt := &customValue{nil, nil, make(chan bool)} + c.keyMap[key] = valueOpt + c.Unlock() + // 此key没找过 + // 开始找 + var expired time.Duration + valueOpt.data, expired, valueOpt.err = c.dispose.CustomGet(key) + if valueOpt.err == nil { + if valueOpt.err == nil { + c.craw.Put(key, valueOpt.data, expired) + } + } + c.Lock() + delete(c.keyMap, key) + c.Unlock() + close(valueOpt.wait) + return valueOpt.data, valueOpt.err + } else { + c.Unlock() + // 其他线程在找同个key,阻塞 + <-value.wait + // 其他线程执行完,从map的data取 + return value.data, value.err + } +} + +// 强制从远端获取数据,并更新craw +// +// 参数 key:要查找的远端数据的key +// 返回值 interface 更新到cache的数据 +// 返回值 成功为nil +func (c *Craw) UpdateData(key string) (data interface{}, err error) { + if !c.enable { + return nil, errors.New("this craw has been destroyed.") + } + data, expired, err := c.dispose.CustomGet(key) + if err == nil && data != nil { + c.craw.Put(key, data, expired) + } + return data, err +} + +//删除craw指定数据 +// +// 参数 key:要查找的远端数据的key +// 参数可选 delay:延迟删除数据的时间 单位(s) +// 返回值 成功为nil +func (c *Craw) DeleteData(key string, delay ...time.Duration) (err error) { + if !c.enable { + return errors.New("this craw has been destroyed.") + } + if len(delay) > 0 { + return c.craw.DelayDelete(key, delay[0]) + } + return c.craw.Delete(key) + +} + +// 保存数据到远端,并删除Craw中已有的缓存值 +// +// 参数 key:要保存到远端的数据的key data:要保存到远端的数据 +// 返回值 error:成功为nil +func (c *Craw) SetData(key string, data interface{}) (err error) { + if !c.enable { + return errors.New("this craw has been destroyed.") + } + + err = c.dispose.CustomSet(key, data) + if err != nil { + c.craw.Delete(key) + } + return +} + +//清除craw的所有数据 +func (c *Craw) ClearAll() error { + if !c.enable { + return errors.New("this craw has been destroyed.") + } + c.craw.ClearAll() + return nil +} + +// 清除craw中指定的包含前缀prefix的key的数据 +func (c *Craw) ClearPrefixKeys(Prefix string) error { + if !c.enable { + return errors.New("this craw has been destroyed.") + } + c.craw.ClearPrefixKeys(Prefix) + return nil +} + +// 获取craw的缓存命中率 +// +// 返回值 float64:计算的结果,XX.XXXXX% +func (c *Craw) HitRate() float64 { + custom := atomic.LoadUint64(c.staggerAccess) + total := atomic.LoadUint64(c.totalAccess) + if total == 0 { + return 0 + } + return float64(total-custom) / float64(total) * 100 +} + +// 重置craw命中率并返回重置之前命中率 +// +// 返回值 float64:计算的结果,XX.XXXXX% +func (c *Craw) ResetHitRate() float64 { + custom := atomic.SwapUint64(c.staggerAccess, 0) + total := atomic.SwapUint64(c.totalAccess, 0) + if total == 0 { + return 0 + } + return float64(total-custom) / float64(total) * 100 +} + +// 设置craw数据,不更新远端 +// +// 参数 key:要保存的数据的key data:要保存的数据,expired要保存的数据的过期时间,<0不过期 +// 返回值 error:成功为nil +func (c *Craw) SetCraw(key string, data interface{}, expired time.Duration) error { + if !c.enable { + return errors.New("this craw has been destroyed.") + } + c.craw.Put(key, data, expired) + return nil +} + +// 查询craw中指定的key是否存在 +func (c *Craw) IsExist(key string) (bool, error) { + if !c.enable { + return false, errors.New("this craw has been destroyed.") + } + return c.craw.IsExist(key), nil +} diff --git a/craw_test.go b/craw_test.go new file mode 100755 index 0000000..9ea2bfc --- /dev/null +++ b/craw_test.go @@ -0,0 +1,113 @@ +package craw + +import ( + "fmt" + "log" + "testing" + "time" +) + +type cacheGet struct { + testV int +} + +func (this *cacheGet) Init() error { + this.testV = 0 + return nil +} + +func (this *cacheGet) CustomGet(key string) (data interface{}, expired time.Duration, err error) { + this.testV++ + return this.testV, 0, nil +} + +func (this *cacheGet) CustomSet(key string, data interface{}) error { + return nil +} + +func (this *cacheGet) Destroy() { + +} + +func TestCrawGetPut(t *testing.T) { + handler := NewCraw("mytest", new(cacheGet)) + defer handler.Destroy() + count := 0 + for count < 10 { + v, err := handler.GetData("k1") + if err != nil { + log.Fatalf("GetData err:%s", err) + } + log.Printf("GetData type(%T), value:%v", v, v) + + count = v.(int) + + err = handler.SetCraw("k1", count+1, 1) + if err != nil { + log.Fatalf("GetData err:%s", err) + } + } +} + +func TestCrawHitRate(t *testing.T) { + handler := NewCraw("mytest", new(cacheGet)) + defer handler.Destroy() + count := 0 + for count < 100000 { + key := fmt.Sprintf("key%d", count) + _, err := handler.GetData(key) + if err != nil { + log.Fatalf("GetData err:%s", err) + } + //log.Printf("GetData %s type(%T), value:%v", key, v, v) + + if count%3 == 0 { + key = fmt.Sprintf("key%d", count+1) + err = handler.SetCraw(key, count+1, 10) + if err != nil { + log.Fatalf("GetData err:%s", err) + } + //log.Printf("SetData %s value:%v", key, v) + } + count++ + } + + log.Printf("hit rate:%v", handler.HitRate()) // output: 1/3 = 33.3333% +} + +// go test -benchmem -run=^$ -bench ^BenchmarkPut$ +func BenchmarkPut(b *testing.B) { + handler := NewCraw("mytest2", new(cacheGet)) + defer handler.Destroy() + + b.ResetTimer() + b.N = 10000000 + keys := make([]string, b.N) + for i := 0; i < b.N; i++ { + keys[i] = fmt.Sprintf("key%d", i) + } + for i := 0; i < b.N; i++ { + handler.SetCraw(keys[i], keys[i], -1) + } +} + +// go test -benchmem -run=^$ -bench ^BenchmarkGet$ +func BenchmarkGet(b *testing.B) { + handler := NewCraw("mytest", new(cacheGet)) + defer handler.Destroy() + + b.N = 10000000 + keys := make([]string, b.N) + for i := 0; i < b.N; i++ { + keys[i] = fmt.Sprintf("key%d", i) + handler.SetCraw(keys[i], keys[i], -1) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := handler.GetData(keys[i]) + if err != nil { + log.Fatalf("GetData err:%s", err) + } + } +} diff --git a/images/craw-graph-get.png b/images/craw-graph-get.png new file mode 100644 index 0000000..7014ee7 Binary files /dev/null and b/images/craw-graph-get.png differ diff --git a/images/craw-graph-set.png b/images/craw-graph-set.png new file mode 100644 index 0000000..8f31ddc Binary files /dev/null and b/images/craw-graph-set.png differ diff --git a/images/craw-histogram-get.png b/images/craw-histogram-get.png new file mode 100644 index 0000000..0a1208e Binary files /dev/null and b/images/craw-histogram-get.png differ diff --git a/images/craw-histogram-set.png b/images/craw-histogram-set.png new file mode 100644 index 0000000..ddc263c Binary files /dev/null and b/images/craw-histogram-set.png differ diff --git a/images/logic.png b/images/logic.png new file mode 100644 index 0000000..b64036f Binary files /dev/null and b/images/logic.png differ