Ecache
🦄【轻量级本地内存缓存】🤏代码少于300行⌚️30s接入🚀高性能、极简设计、并发安全🌈支持LRU 和 LRU-2模式 🦖支持分布式一致性 [ecache] Extremely easy, ultra fast, concurrency-safe and support distributed consistency. Similar to bigcache, cachego, freecache, gcache, gocache, groupcache, lrucache.
Install / Use
/learn @orca-zhang/EcacheREADME
🦄 ecache
<p align="center"> <a href="#"> <img src="https://github.com/orca-zhang/ecache/raw/master/doc/logo.svg"> </a> </p> <p align="center"> <a href="/go.mod#L3" alt="go version"> <img src="https://img.shields.io/badge/go%20version-%3E=1.11-brightgreen?style=flat"/> </a> <a href="https://goreportcard.com/badge/github.com/orca-zhang/ecache" alt="goreport"> <img src="https://goreportcard.com/badge/github.com/orca-zhang/ecache"> </a> <a href="https://orca-zhang.semaphoreci.com/projects/ecache" alt="buiding status"> <img src="https://orca-zhang.semaphoreci.com/badges/ecache.svg?style=shields"> </a> <a href="https://codecov.io/gh/orca-zhang/ecache" alt="codecov"> <img src="https://codecov.io/gh/orca-zhang/ecache/branch/master/graph/badge.svg?token=F6LQbADKkq"/> </a> <a href="https://github.com/orca-zhang/ecache/blob/master/LICENSE" alt="license MIT"> <img src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat"> </a> <a href="https://app.fossa.com/projects/git%2Bgithub.com%2Forca-zhang%2Fcache?ref=badge_shield" alt="FOSSA Status"> <img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2Forca-zhang%2Fcache.svg?type=shield"/> </a> <a href="https://benchplus.github.io/gocache/dev/bench/" alt="continuous benchmark"> <img src="https://img.shields.io/badge/benchmark-click--me-brightgreen.svg?style=flat"/> </a> </p> <p align="center">一款极简设计、高性能、并发安全、支持分布式一致性的轻量级内存缓存</p>特性
基准性能
:snail: 代表很慢, :airplane: 代表快, :rocket: 代表非常快
<table style="text-align: center"> <tr> <td></td> <td><a href="https://github.com/allegro/bigcache">bigcache</a></td> <td><a href="https://github.com/FishGoddess/cachego">cachego</a></td> <td><a href="https://github.com/orca-zhang/ecache"><strong>ecache🌟</strong></a></td> <td><a href="https://github.com/coocood/freecache">freecache</a></td> <td><a href="https://github.com/bluele/gcache">gcache</a></td> <td><a href="https://github.com/patrickmn/go-cache">gocache</a></td> </tr> <tr> <td>PutInt</td> <td>:airplane:</td> <td></td> <td>:rocket:</td> <td>:rocket:</td> <td>:airplane:</td> <td>:airplane:</td> </tr> <tr> <td>GetInt</td> <td>:airplane:</td> <td>:airplane:</td> <td>:rocket:</td> <td></td> <td>:airplane:</td> <td>:airplane:</td> </tr> <tr> <td>Put1K</td> <td>:airplane:</td> <td>:airplane:</td> <td>:rocket:</td> <td>:rocket:</td> <td>:rocket:</td> <td>:airplane:</td> </tr> <tr> <td>Put1M</td> <td>:snail:</td> <td></td> <td>:rocket:</td> <td>:snail:</td> <td>:airplane:</td> <td>:airplane:</td> </tr> <tr> <td>PutTinyObject</td> <td>:airplane:</td> <td></td> <td>:rocket:</td> <td>:rocket:</td> <td>:airplane:</td> <td></td> </tr> <tr> <td>ChangeOutAllInt</td> <td>:airplane:</td> <td></td> <td>:rocket:</td> <td>:rocket:</td> <td>:airplane:</td> <td>:airplane:</td> </tr> <tr> <td>HeavyReadInt</td> <td>:rocket:</td> <td>:rocket:</td> <td>:rocket:</td> <td></td> <td></td> <td>:rocket:</td> </tr> <tr> <td>HeavyReadIntGC</td> <td>:airplane:</td> <td>:rocket:</td> <td>:rocket:</td> <td></td> <td>:airplane:</td> <td>:airplane:</td> </tr> <tr> <td>HeavyWriteInt</td> <td>:rocket:</td> <td>:airplane:</td> <td>:rocket:</td> <td>:rocket:</td> <td></td> <td>:airplane:</td> </tr> <tr> <td>HeavyWriteIntGC</td> <td>:rocket:</td> <td></td> <td>:airplane:</td> <td>:airplane:</td> <td></td> <td></td> </tr> <tr> <td>HeavyWrite1K</td> <td>:snail:</td> <td>:airplane:</td> <td>:rocket:</td> <td>:rocket:</td> <td></td> <td>:airplane:</td> </tr> <tr> <td>HeavyWrite1KGC</td> <td>:snail:</td> <td>:airplane:</td> <td>:rocket:</td> <td>:rocket:</td> <td></td> <td>:airplane:</td> </tr> <tr> <td>HeavyMixedInt</td> <td>:rocket:</td> <td>:airplane:</td> <td>:rocket:</td> <td></td> <td>:airplane:</td> <td>:rocket:</td> </tr> <tr> <td colspan="7"> <a href="https://github.com/FishGoddess/cachego"><strong>FishGoddess/cachego</strong></a> 和 <a href="https://github.com/patrickmn/go-cache"><strong>patrickmn/go-cache</strong></a> 是简单的map+过期时间的实现,所以没有命中率测试 </td> </tr> <tr> <td colspan="7"> <a href="https://github.com/kpango/gache"><strong>kpango/gache</strong></a> & <a href="https://github.com/hlts2/gocache"><strong>hlts2/gocache</strong></a> 性能表现不是很好,所以从列表中剔除 </td> </tr> <tr> <td colspan="7"> <a href="https://github.com/patrickmn/go-cache"><strong>patrickmn/go-cache</strong></a> 是FIFO模式,其他的库都是LRU模式 </td> </tr> </table>👁️🗨️点我看用例 👁️🗨️点我看结果 (除了缓存命中率数值越低越好)

gc pause测试结果 代码由
bigcache提供(数值越低越好)
目前正在生产环境大流量验证中
- [
已验证]公众号后台(几百QPS):用户信息、订单信息、配置信息 - [
已验证]推送系统(几万QPS):可调整系统配置、信息去重、固定信息缓存 - [
已验证]评论系统(几万QPS):用户信息、分布式一致性组件
如何使用
引入包(预计5秒)
import (
"time"
"github.com/orca-zhang/ecache"
)
定义实例(预计5秒)
可以放置在任意位置(全局也可以),建议就近定义
var c = ecache.NewLRUCache(16, 200, 10 * time.Second)
设置缓存(预计5秒)
c.Put("uid1", o) // `o`可以是任意变量,一般是对象指针,存放固定的信息,比如`*UserInfo`
查询缓存(预计5秒)
if v, ok := c.Get("uid1"); ok {
return v.(*UserInfo) // 不用类型断言,咱们自己控制类型
}
// 如果内存缓存没有查询到,下面再回源查redis/db
删除缓存(预计5秒)
在信息发生变化的地方
c.Del("uid1")
下载包(预计5秒)
非go modules模式:
sh>go get -u github.com/orca-zhang/ecache
go modules模式:
sh>go mod tidy && go mod download
运行吧
🎉 完美搞定 🚀 性能直接提升X倍!
sh>go run <你的main.go文件>
参数说明
NewLRUCache- 第一个参数是桶的个数,用来分散锁的粒度,每个桶都会使用独立的锁,最大值为65535,支持65536个实例
- 不用担心,随意设置一个就好,
ecache会找一个合适的数字便于后面掩码计算
- 不用担心,随意设置一个就好,
- 第二个参数是每个桶所能容纳的item个数上限,最大值为65535
- 意味着
ecache全部写满的情况下,应该有第一个参数 X 第二个参数个item,最多能支持存储42亿个item
- 意味着
- [
可选]第三个参数是每个item的过期时间ecache使用内部计时器提升性能,默认100ms精度,每秒校准- 不传或者传
0,代表永久有效
- 第一个参数是桶的个数,用来分散锁的粒度,每个桶都会使用独立的锁,最大值为65535,支持65536个实例
最佳实践
- 支持任意类型的值
- 提供
Put/PutInt64/PutBytes三种方法,适应不同场景,需要与Get/GetInt64/GetBytes配对使用(后两种方法GC开销较小) - 复杂对象优先存放指针(注意⚠️一旦放进去不要再修改其字段,即使再拿出来也是,item有可能被其他人同时访问)
- 如果需要修改,解决方案:取出字段每个单独赋值,或者用copier做一次深拷贝后在副本上修改
- 也可以存放对象(相对于直接存对象指针性能差一些,因为拿出去有拷贝)
- 缓存的对象尽可能越往业务上层越大越好(节省内存拼装和组织时间)
- 提供
- 如果不想因为类似遍历的请求把热数据刷掉,可以改用
LRU-2模式,可能有很少的损耗(💬 什么是LRU-2)LRU2和LRU的大小设置分别为1/4和3/4效果较好
- 一个实例可以存储多种类型的对象,试试key格式化的时候加上前缀,用冒号分割
- 并发访问量大的场景,试试
256、1024个桶,甚至更多 - 可以当作缓冲队列用于合并更新以减少刷盘次数(数据可以重建或容忍断电丢失的情况下)
- 具体使用方式是挂载
Inspector监听驱逐事件 - 终末或定时调用
Walk将数据刷到存储
- 具体使用方式是挂载
特别场景
整型键、整型值和字节数组
// 整型键
c.Put(strconv.FormatInt(d, 10), o) // d为`int64`类型
// 整型值
c.PutInt64("uid1", int64(1))
if d, ok := c.GetInt64("uid1"); ok {
// d为`int64`类型的1
}
// 字节数组
c.PutBytes("uid1", b)// b为`[]byte`类型
if b, ok := c.GetBytes("uid1"); ok {
// b为`[]byte`类型
}
LRU-2模式
- 💬 什么是LRU-2
直接在
NewLRUCache()后面跟.LRU2(<num>)就好,参数<num>代表LRU-2热队列的item上限个数(每个桶)
var c = ecache.NewLRUCache(16, 200, 10 * time.Second).LRU2(1024)
空缓存哨兵(不存在的对象不用再回源)
// 设置的时候直接给`nil`就好
c.Put("uid1", nil)
// 读取的时候,也和正常差不多
if v, ok := c.Get("uid1"); ok {
if v == nil { // 注意⚠️这里需要判断是不是空缓存哨兵
return nil // 是空缓存哨兵,那就返回没有信息或者也可以让`uid1`不出现在待回源列表里
}
return v.(*UserInfo)
}
// 如果内存缓存没有查询到,下面再回源查redis/db
需要修改部分数据,且用对象指针方式存储时
比如,我们从
ecache中获取了*UserInfo类型的用户信息缓存v,需要修改其状态字段
import (
"github.com/jinzhu/copier"
)
o := &UserInfo{}
copier.Copy(o, v) // 从`v`复制到`o`
o.Status = 1 // 修改副本的字段
注入监听器
// inspector - 可以用来做统计或者缓冲队列等
// `action`:PUT, `status`: evicted=-1, updated=0, added=1
// `action`:GET, `status`: miss=0, hit=1
// `action`:DEL, `status`: miss=0, hit=1
// `iface`/`bytes`只有在`status`不为0或者`action`为PUT时才不为nil
type inspector func(action int, key string, iface *interface{}, bytes []byte, status int)
- 使用方式
cache.Inspect(func(action int, key string, iface *interface{}, bytes []byte, status int) {
// TODO: 实现你想做的事情
// 监听器会根据注入顺序依次执行
// 注意⚠️如果有耗时操作,尽量另开channel保证不阻塞当前协程
// - 如何获取正确的值 -
// - `Put`: `*iface`
// - `PutBytes`: `bytes`
// - `PutInt64`: `ecache.ToInt64(bytes)`
})
遍历所有元素
// 只会遍历缓存中存在且未过期的项
cache.Walk(func(key string, iface *interface{}, bytes []byte, expireAt int64) bool {
// `key`是值,`iface`/`bytes`是值,`expireAt`是过期时间
// - 如何获取正确的值 -
// - `Put`: `*iface`
// - `PutBytes`: `bytes`
// - `PutInt64`: `ecache.ToInt64(bytes)`
return true // 是否继续遍历
})
统计缓存使用情况
实现超级简单,注入inspector后,每个操作只多了一次原子操作,具体看代码
引入stats包
import (
"github.com/orca-zhang/ecache/stats"
)
绑定缓存实例
名称为自定义的池子名称,内部会按名称聚合
注意⚠️绑定可以放在全局
var _ = stats.Bind("user", c)
var _ = stats.Bind("user", c0, c1, c2)
var _ = stats.Bind("token", caches...)
获取统计信息
stats.Stats().Range(func(k, v interface{}) bool {
fmt.Printf("stats: %s %+v\n", k, v) // k是池子名称,v是(*stats.StatsNode)类型
// 其中统计了各种事件的次数,使用`HitRate`方法可以获得缓存命中率
return true
})
分布式一致性组件
- 💬 原理说明

