{"id":13671032,"url":"https://github.com/orca-zhang/ecache","last_synced_at":"2025-04-09T15:07:43.128Z","repository":{"id":45236076,"uuid":"439778382","full_name":"orca-zhang/ecache","owner":"orca-zhang","description":"🦄【轻量级本地内存缓存】🤏代码少于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.","archived":false,"fork":false,"pushed_at":"2025-04-03T12:33:47.000Z","size":972,"stargazers_count":249,"open_issues_count":5,"forks_count":29,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-09T15:07:33.451Z","etag":null,"topics":["bigcache","cache","cachego","ecache","freecache","gcache","go","gocache","golang","groupcache","hacktoberfest","lru","lru-cache","lrucache"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/orca-zhang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-12-19T04:55:20.000Z","updated_at":"2025-04-03T12:33:04.000Z","dependencies_parsed_at":"2023-02-10T20:30:55.820Z","dependency_job_id":"e1141b31-bec7-4562-afda-301b64f5e24f","html_url":"https://github.com/orca-zhang/ecache","commit_stats":{"total_commits":224,"total_committers":2,"mean_commits":112.0,"dds":0.004464285714285698,"last_synced_commit":"939bd37fd3234162258e276572948117362b6e68"},"previous_names":["orca-zhang/cache","orca-zhang/orcache"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orca-zhang%2Fecache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orca-zhang%2Fecache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orca-zhang%2Fecache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orca-zhang%2Fecache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/orca-zhang","download_url":"https://codeload.github.com/orca-zhang/ecache/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055284,"owners_count":21040157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bigcache","cache","cachego","ecache","freecache","gcache","go","gocache","golang","groupcache","hacktoberfest","lru","lru-cache","lrucache"],"created_at":"2024-08-02T09:00:56.404Z","updated_at":"2025-04-09T15:07:43.103Z","avatar_url":"https://github.com/orca-zhang.png","language":"Go","funding_links":["https://opencollective.com/ecache","https://opencollective.com/ecache/backer/0/website?requireActive=false","https://opencollective.com/ecache/backer/1/website?requireActive=false","https://opencollective.com/ecache/backer/2/website?requireActive=false","https://opencollective.com/ecache/backer/3/website?requireActive=false"],"categories":["Go"],"sub_categories":[],"readme":"[English README | 英文说明](README_en.md)\n\n# 🦄 ecache\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#\"\u003e\n    \u003cimg src=\"https://github.com/orca-zhang/ecache/raw/master/doc/logo.svg\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"/go.mod#L3\" alt=\"go version\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/go%20version-%3E=1.11-brightgreen?style=flat\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://goreportcard.com/badge/github.com/orca-zhang/ecache\" alt=\"goreport\"\u003e\n    \u003cimg src=\"https://goreportcard.com/badge/github.com/orca-zhang/ecache\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://orca-zhang.semaphoreci.com/projects/ecache\" alt=\"buiding status\"\u003e\n    \u003cimg src=\"https://orca-zhang.semaphoreci.com/badges/ecache.svg?style=shields\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/orca-zhang/ecache\" alt=\"codecov\"\u003e\n    \u003cimg src=\"https://codecov.io/gh/orca-zhang/ecache/branch/master/graph/badge.svg?token=F6LQbADKkq\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/orca-zhang/ecache/blob/master/LICENSE\" alt=\"license MIT\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://app.fossa.com/projects/git%2Bgithub.com%2Forca-zhang%2Fcache?ref=badge_shield\" alt=\"FOSSA Status\"\u003e\n    \u003cimg src=\"https://app.fossa.com/api/projects/git%2Bgithub.com%2Forca-zhang%2Fcache.svg?type=shield\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://benchplus.github.io/gocache/dev/bench/\" alt=\"continuous benchmark\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/benchmark-click--me-brightgreen.svg?style=flat\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e一款极简设计、高性能、并发安全、支持分布式一致性的轻量级内存缓存\u003c/p\u003e\n\n## 特性\n\n- 🤏 代码量\u003c300行、30s完成接入\n- 🚀 高性能、极简设计、并发安全\n- 🌈 支持`LRU` 和 [`LRU-2`](#LRU-2模式)两种模式\n- 🦖 额外[小组件](#分布式一致性组件)支持分布式一致性\n\n## 基准性能\n\n\u003e :snail: 代表很慢, :airplane: 代表快, :rocket: 代表非常快\n\n\u003e [👁️‍🗨️点我看用例](https://github.com/benchplus/gocache) [👁️‍🗨️点我看结果](https://benchplus.github.io/gocache/dev/bench/) （除了缓存命中率数值越低越好）\n\n\u003ctable style=\"text-align: center\"\u003e\n   \u003ctr\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://github.com/allegro/bigcache\"\u003ebigcache\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://github.com/FishGoddess/cachego\"\u003ecachego\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://github.com/orca-zhang/ecache\"\u003e\u003cstrong\u003eecache🌟\u003c/strong\u003e\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://github.com/coocood/freecache\"\u003efreecache\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://github.com/bluele/gcache\"\u003egcache\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://github.com/patrickmn/go-cache\"\u003egocache\u003c/a\u003e\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003ePutInt\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eGetInt\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003ePut1K\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003ePut1M\u003c/td\u003e\n      \u003ctd\u003e:snail:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:snail:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003ePutTinyObject\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eChangeOutAllInt\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eHeavyReadInt\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eHeavyReadIntGC\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eHeavyWriteInt\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eHeavyWriteIntGC\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eHeavyWrite1K\u003c/td\u003e\n      \u003ctd\u003e:snail:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eHeavyWrite1KGC\u003c/td\u003e\n      \u003ctd\u003e:snail:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n      \u003ctd\u003eHeavyMixedInt\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n      \u003ctd\u003e\u003c/td\u003e\n      \u003ctd\u003e:airplane:\u003c/td\u003e\n      \u003ctd\u003e:rocket:\u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n    \u003ctd colspan=\"7\"\u003e\n      \u003ca href=\"https://github.com/FishGoddess/cachego\"\u003e\u003cstrong\u003eFishGoddess/cachego\u003c/strong\u003e\u003c/a\u003e 和 \u003ca href=\"https://github.com/patrickmn/go-cache\"\u003e\u003cstrong\u003epatrickmn/go-cache\u003c/strong\u003e\u003c/a\u003e 是简单的map+过期时间的实现，所以没有命中率测试\n    \u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n    \u003ctd colspan=\"7\"\u003e\n      \u003ca href=\"https://github.com/kpango/gache\"\u003e\u003cstrong\u003ekpango/gache\u003c/strong\u003e\u003c/a\u003e \u0026 \u003ca href=\"https://github.com/hlts2/gocache\"\u003e\u003cstrong\u003ehlts2/gocache\u003c/strong\u003e\u003c/a\u003e 性能表现不是很好，所以从列表中剔除\n    \u003c/td\u003e\n   \u003c/tr\u003e\n   \u003ctr\u003e\n    \u003ctd colspan=\"7\"\u003e\n      \u003ca href=\"https://github.com/patrickmn/go-cache\"\u003e\u003cstrong\u003epatrickmn/go-cache\u003c/strong\u003e\u003c/a\u003e 是FIFO模式，其他的库都是LRU模式\n    \u003c/td\u003e\n   \u003c/tr\u003e\n\u003c/table\u003e\n\n![](https://github.com/orca-zhang/ecache/raw/master/doc/benchmark.png)\n\n\u003e gc pause测试结果 [代码由`bigcache`提供](https://github.com/allegro/bigcache-bench)（数值越低越好）\n![](https://github.com/orca-zhang/ecache/raw/master/doc/gc.png)\n\n### 目前正在生产环境大流量验证中\n- [`已验证`]公众号后台(几百QPS)：用户信息、订单信息、配置信息\n- [`已验证`]推送系统(几万QPS)：可调整系统配置、信息去重、固定信息缓存\n- [`已验证`]评论系统(几万QPS)：用户信息、分布式一致性组件\n\n## 如何使用\n\n#### 引入包（预计5秒）\n``` go\nimport (\n    \"time\"\n\n    \"github.com/orca-zhang/ecache\"\n)\n```\n\n#### 定义实例（预计5秒）\n\u003e 可以放置在任意位置（全局也可以），建议就近定义\n``` go\nvar c = ecache.NewLRUCache(16, 200, 10 * time.Second)\n```\n\n#### 设置缓存（预计5秒）\n``` go\nc.Put(\"uid1\", o) // `o`可以是任意变量，一般是对象指针，存放固定的信息，比如`*UserInfo`\n```\n\n#### 查询缓存（预计5秒）\n``` go\nif v, ok := c.Get(\"uid1\"); ok {\n    return v.(*UserInfo) // 不用类型断言，咱们自己控制类型\n}\n// 如果内存缓存没有查询到，下面再回源查redis/db\n```\n\n#### 删除缓存（预计5秒）\n\u003e 在信息发生变化的地方\n``` go\nc.Del(\"uid1\")\n```\n\n#### 下载包（预计5秒）\n\n\u003e 非go modules模式：\\\n\u003e sh\u003e  ```go get -u github.com/orca-zhang/ecache```\n\n\u003e go modules模式：\\\n\u003e sh\u003e  ```go mod tidy \u0026\u0026 go mod download```\n\n#### 运行吧\n\u003e 🎉 完美搞定 🚀 性能直接提升X倍！\\\n\u003e sh\u003e  ```go run \u003c你的main.go文件\u003e```\n\n## 参数说明\n\n- `NewLRUCache`\n  - 第一个参数是桶的个数，用来分散锁的粒度，每个桶都会使用独立的锁，最大值为65535，支持65536个实例\n    - 不用担心，随意设置一个就好，`ecache`会找一个合适的数字便于后面掩码计算\n  - 第二个参数是每个桶所能容纳的item个数上限，最大值为65535\n    - 意味着`ecache`全部写满的情况下，应该有`第一个参数 X 第二个参数`个item，最多能支持存储42亿个item\n  - \\[`可选`\\]第三个参数是每个item的过期时间\n    - `ecache`使用内部计时器提升性能，默认100ms精度，每秒校准\n    - 不传或者传`0`，代表永久有效\n\n## 最佳实践\n\n- 支持任意类型的值\n  - 提供`Put`/`PutInt64`/`PutBytes`三种方法，适应不同场景，需要与`Get`/`GetInt64`/`GetBytes`配对使用（后两种方法GC开销较小）\n  - 复杂对象优先存放指针（注意⚠️一旦放进去不要再修改其字段，即使再拿出来也是，item有可能被其他人同时访问）\n    - 如果需要修改，解决方案：取出字段每个单独赋值，或者用[copier做一次深拷贝后在副本上修改](#需要修改部分数据且用对象指针方式存储时)\n    - 也可以存放对象（相对于直接存对象指针性能差一些，因为拿出去有拷贝）\n    - 缓存的对象尽可能越往业务上层越大越好（节省内存拼装和组织时间）\n- 如果不想因为类似遍历的请求把热数据刷掉，可以改用[`LRU-2`模式](#LRU-2模式)，可能有很少的损耗（💬 [什么是LRU-2](#什么是LRU-2)）\n  - `LRU2`和`LRU`的大小设置分别为1/4和3/4效果较好\n- 一个实例可以存储多种类型的对象，试试key格式化的时候加上前缀，用冒号分割\n- 并发访问量大的场景，试试`256`、`1024`个桶，甚至更多\n- 可以当作**缓冲队列**用于合并更新以减少刷盘次数（数据可以重建或容忍断电丢失的情况下）\n  - 具体使用方式是[挂载`Inspector`](#注入监听器)监听驱逐事件\n  - 终末或定时调用[`Walk`](#遍历所有元素)将数据刷到存储\n\n## 特别场景\n\n### 整型键、整型值和字节数组\n``` go\n// 整型键\nc.Put(strconv.FormatInt(d, 10), o) // d为`int64`类型\n\n// 整型值\nc.PutInt64(\"uid1\", int64(1))\nif d, ok := c.GetInt64(\"uid1\"); ok {\n    // d为`int64`类型的1\n}\n\n// 字节数组\nc.PutBytes(\"uid1\", b)// b为`[]byte`类型\nif b, ok := c.GetBytes(\"uid1\"); ok {\n    // b为`[]byte`类型\n}\n```\n\n### LRU-2模式\n\n- 💬 [什么是LRU-2](#什么是LRU-2)\n\n\u003e 直接在`NewLRUCache()`后面跟`.LRU2(\u003cnum\u003e)`就好，参数`\u003cnum\u003e`代表`LRU-2`热队列的item上限个数（每个桶）\n``` go\nvar c = ecache.NewLRUCache(16, 200, 10 * time.Second).LRU2(1024)\n```\n\n### 空缓存哨兵（不存在的对象不用再回源）\n``` go\n// 设置的时候直接给`nil`就好\nc.Put(\"uid1\", nil)\n```\n\n``` go\n// 读取的时候，也和正常差不多\nif v, ok := c.Get(\"uid1\"); ok {\n  if v == nil { // 注意⚠️这里需要判断是不是空缓存哨兵\n    return nil  // 是空缓存哨兵，那就返回没有信息或者也可以让`uid1`不出现在待回源列表里\n  }\n  return v.(*UserInfo)\n}\n// 如果内存缓存没有查询到，下面再回源查redis/db\n```\n\n### 需要修改部分数据，且用对象指针方式存储时\n\n\u003e 比如，我们从`ecache`中获取了`*UserInfo`类型的用户信息缓存`v`，需要修改其状态字段\n``` go\nimport (\n    \"github.com/jinzhu/copier\"\n)\n```\n\n``` go\no := \u0026UserInfo{}\ncopier.Copy(o, v) // 从`v`复制到`o`\no.Status = 1      // 修改副本的字段\n```\n\n### 注入监听器\n\n``` go\n// inspector - 可以用来做统计或者缓冲队列等\n//   `action`:PUT, `status`: evicted=-1, updated=0, added=1\n//   `action`:GET, `status`: miss=0, hit=1\n//   `action`:DEL, `status`: miss=0, hit=1\n//   `iface`/`bytes`只有在`status`不为0或者`action`为PUT时才不为nil\ntype inspector func(action int, key string, iface *interface{}, bytes []byte, status int)\n```\n\n- 使用方式\n``` go\ncache.Inspect(func(action int, key string, iface *interface{}, bytes []byte, status int) {\n  // TODO: 实现你想做的事情\n  //     监听器会根据注入顺序依次执行\n  //     注意⚠️如果有耗时操作，尽量另开channel保证不阻塞当前协程\n\n  // - 如何获取正确的值 -\n  //   - `Put`:      `*iface`\n  //   - `PutBytes`: `bytes`\n  //   - `PutInt64`: `ecache.ToInt64(bytes)`\n})\n```\n\n### 遍历所有元素\n\n``` go\n  // 只会遍历缓存中存在且未过期的项\n  cache.Walk(func(key string, iface *interface{}, bytes []byte, expireAt int64) bool {\n    // `key`是值，`iface`/`bytes`是值，`expireAt`是过期时间\n\n    // - 如何获取正确的值 -\n    //   - `Put`:      `*iface`\n    //   - `PutBytes`: `bytes`\n    //   - `PutInt64`: `ecache.ToInt64(bytes)`\n    return true // 是否继续遍历\n  })\n```\n\n## 统计缓存使用情况\n\n\u003e 实现超级简单，注入inspector后，每个操作只多了一次原子操作，具体看[代码](/stats/stats.go#L34)\n\n##### 引入stats包\n``` go\nimport (\n    \"github.com/orca-zhang/ecache/stats\"\n)\n```\n\n#### 绑定缓存实例\n\u003e 名称为自定义的池子名称，内部会按名称聚合\\\n\u003e 注意⚠️绑定可以放在全局\n``` go\nvar _ = stats.Bind(\"user\", c)\nvar _ = stats.Bind(\"user\", c0, c1, c2)\nvar _ = stats.Bind(\"token\", caches...)\n```\n\n#### 获取统计信息\n``` go\nstats.Stats().Range(func(k, v interface{}) bool {\n    fmt.Printf(\"stats: %s %+v\\n\", k, v) // k是池子名称，v是(*stats.StatsNode)类型\n    // 其中统计了各种事件的次数，使用`HitRate`方法可以获得缓存命中率\n    return true\n})\n```\n\n## 分布式一致性组件\n\n- 💬 [原理说明](#分布式一致性组件原理)\n\n### 引入dist包\n``` go\nimport (\n    \"github.com/orca-zhang/ecache/dist\"\n)\n```\n\n### 绑定缓存实例\n\u003e 名称为自定义的池子名称，内部会按名称聚合\\\n\u003e 注意⚠️绑定可以放在全局，不依赖初始化\n``` go\nvar _ = dist.Bind(\"user\", c)\nvar _ = dist.Bind(\"user\", c0, c1, c2)\nvar _ = dist.Bind(\"token\", caches...)\n```\n\n### 绑定redis client\n\u003e 目前支持redigo和goredis，其他库可以自行实现dist.RedisCli接口，或者提issue给我\n\n#### go-redis v7及以下版本\n``` go\nimport (\n    \"github.com/orca-zhang/ecache/dist/goredis/v7\"\n)\n\ndist.Init(goredis.Take(redisCli)) // redisCli是*redis.RedisClient类型\ndist.Init(goredis.Take(redisCli, 100000)) // 第二个参数是channel缓冲区大小，不传默认100\n```\n\n#### go-redis v8及以上版本\n``` go\nimport (\n    \"github.com/orca-zhang/ecache/dist/goredis\"\n)\n\ndist.Init(goredis.Take(redisCli)) // redisCli是*redis.RedisClient类型\ndist.Init(goredis.Take(redisCli, 100000)) // 第二个参数是channel缓冲区大小，不传默认100\n```\n\n#### redigo\n\u003e 注意⚠️`github.com/gomodule/redigo` 要求最低版本 `go 1.14`\n``` go\nimport (\n    \"github.com/orca-zhang/ecache/dist/redigo\"\n)\n\ndist.Init(redigo.Take(pool)) // pool是*redis.Pool类型\n```\n\n#### 主动通知所有节点、所有实例删除（包括本机）\n\u003e 当db的数据发生变化或者删除时调用\\\n\u003e 发生错误时会降级成只处理本机所有实例（比如未初始化或者网络错误）\n``` go\ndist.OnDel(\"user\", \"uid1\") // user是池子名称，uid1是要删除的key\n```\n\n## 使用[`lrucache`](http://github.com/orca-zhang/lrucache)的老用户升级指导\n\n- 只需四步：\n1. 引入包 `github.com/orca-zhang/lrucache` 改为 `github.com/orca-zhang/ecache`\n2. `lrucache.NewSyncCache` 改为 `ecache.NewLRUCache`\n3. 第3个参数从默认的单位秒改为`*time.Second`\n4. `Delete`方法改为`Del`\n\n# 不希望你白来\n\n- 客官，既然来了，学点东西再走吧！\n- 我想尽力让你明白`ecache`做了啥，以及为什么要这么做\n\n## 什么是本地内存缓存\n\n---\n    L1 缓存引用 .................... 0.5 ns\n    分支错误预测 ...................... 5 ns\n    L2 缓存引用 ...................... 7 ns\n    互斥锁/解锁 ...................... 25 ns\n    主存储器引用 .................... 100 ns\n    使用 Zippy 压缩 1K 字节 ........3,000 ns =   3 µs\n    通过 1 Gbps 网络发送 2K 字节... 20,000 ns =  20 µs\n    从内存中顺序读取 1 MB ........ 250,000 ns = 250 µs\n    同一数据中心内的往返........... 500,000 ns = 0.5 ms\n    发送数据包 加州\u003c-\u003e荷兰 .... 150,000,000 ns = 150 ms\n\n- 从上表可以看出，内存访问和网络访问(同数据中心)差不多是一千到一万倍的差距！\n- 曾经遇到不止一个工程师：“缓存？上redis”，但我想说，redis不是万金油，某些程度上讲，用它还是噩梦（当然我说的是缓存一致性问题...😄）\n- 因为内存操作非常快，相对于redis/db你基本可以忽略不计，比如现在有一个QPS是1000查询API，我们把结果缓存1秒，也就是1秒内不会请求redis/db，那回源次数降低到了1/1000（理想情况），意味着访问redis/db部分的性能提升了1000倍，听上去是不是很棒？\n- 继续看，你会爱上她的！（当然也可能是他，亦或者是牠，ahaha）\n\n### 使用场景，解决什么问题\n\n- 高并发大流量场景\n  - 缓存热点数据（比如人气比较高的直播间）\n  - 突发QPS削峰（比如信息流中突发新闻）\n  - 降低延迟和拥堵（比如短时间内频繁访问的页面）\n- 节省成本\n  - 单机场景（不部署redis、memcache也能快速提升QPS上限）\n  - redis和db实例降配（能拦截大部分请求）\n- 不怎么会变化的数据（写少读多）\n  - 比如配置等（这类数据使用地方多，会有放大效应，很多时候可能会因为这些配置热key对redis/db实例的规格误判，需要单独为它们升配）\n- 可以容忍短暂不一致的数据\n  - 用户头像、昵称、商品库存(实际下单会在db再次检查)等\n  - 修改的配置（过期时间10秒，那最多延迟10秒生效）\n- 缓冲队列：合并更新以减少刷盘次数\n  - 可以通过给查询打补丁来实现强一致（分布式情况下，需要在负载均衡层保证同用户/设备调度到同一节点）\n  - 可以重建或容忍断电丢失的情况下\n\n## 设计思路\n\n\u003e `ecache`是[`lrucache`](http://github.com/orca-zhang/lrucache)库的升级版本\n\n- 最下层是用原生map和双链表实现的最基础`LRU`（最久未访问）\n  - PS：我实现的其他版本（[go](https://github.com/orca-zhang/lrucache) / [c++](https://github.com/ez8-co/linked_hash) / [js](https://github.com/orca-zhang/ecache.js)）在leetcode都是超越100%的解法\n- 第2层包了分桶策略、并发控制、过期控制（会自动选择2的幂次个桶，便于掩码计算）\n- 第2.5层用很简单的方式实现了`LRU-2`能力，代码不超过20行，直接看源码（搜关键词`LRU-2`）\n\n### 什么是LRU\n- 最久未访问的优先驱逐\n- 每次被访问，item会被刷新到队列的最前面\n- 队列满后再次写入新item，优先驱逐队列最后面、也就是最久未访问的item\n\n### 什么是LRU-2\n- `LRU-K`是少于K次访问的用单独的`LRU`队列存放，超过K次的另外存放\n- 主要优化的场景是比如一些遍历类型的查询，批量刷缓存以后，很容易把一些本来较热的item给驱逐掉\n- 为了实现简单，我们这里实现的是`LRU-2`，也就是第2次访问就放到热队列里，并不记录访问次数\n- 主要优化的是热key的缓存命中率\n- 和mysql的[缓冲池lru算法](https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html)非常类似\n\n### 分布式一致性组件原理\n\n- 其实简单的利用了redis的pubsub功能\n- 主动告知被缓存的信息有更新，广播到所有节点\n- 某种意义上说，它只是缩小不一致时间窗口的一个方式（有网络延迟且不保证一定完成）\n- 需要注意⚠️：\n  - 尽量减少使用，适合用在写少读多`WORM(Write-Once-Read-Many)`的场景\n    - redis性能毕竟不如内存，而且有广播类通信（写放大）\n  - 以下场景会降级（时间窗口变大），但至少会保证当前节点的强一致性\n    - redis不可用、网络错误\n    - 消费goroutine panic\n    - 存在未生效节点（灰度`canary`发布，或者发布过程中）的情况下，比如\n      - 已使用`ecache`但首次添加此插件\n      - 新加入缓存的数据或者新加的删除操作\n\n### 关于性能\n\n- 释放锁不用defer\n- 不用异步清理（没意义，分散到写时驱逐更合理，不易抖动）\n- 没有用内存容量来控制（单个item的大小一般都有预估大小，简单控制个数即可）\n- 分桶策略，自动选择2的幂次个桶（分散锁竞争，2的幂次掩码操作更快）\n- key用`string`类型（可扩展性强；语言内建支持引用，更省内存）\n- 不用虚表头（虽然绕脑一些，但是有20%左右提升）\n- 选择`LRU-2`实现`LRU-K`（实现简单，近乎没有额外损耗）\n- 可以直接存指针（不用序列化，有些场景如果使用`[]byte`那优势大大降低）\n- 使用内部计时器计时（默认100ms精度，每秒校准，剖析发现time.Now()产生临时对象导致GC耗时增加）\n- 双链表用固定分配内存存储，用时间戳置0来标记删除，减少GC（并且同规格比`bigcache`节省内存50%以上）\n\n#### 失败的优化尝试\n\n- key由`string`改为`reflect.StringHeader`，结果：负优化\n- 互斥锁改为读写锁，Get请求也会修改数据，访问违例，即使不改数据，结果：读写混合场景负优化\n- 用`time.Timer`实现内部计时器，结果：触发不稳定，后直接用`time.Sleep`实现计时器\n- 分布式一致性组件挂inspector自动同步更新和删除，结果：性能影响较大且需要特殊处理循环调用问题\n\n### 关于GC优化\n\n- 就像我在C++版性能剖析器里提到的[性能优化的几个层次](https://github.com/ez8-co/ezpp#性能优化的几个层次)，单从一个层次考虑性能并不高明\n- 《第三层次》里有一句“没有比不存在的东西性能更快的了”（类似奥卡姆剃刀），能砍掉一定不要想着优化\n- 比如为了减少GC大块分配内存，却提供`[]byte`的值存储，意味着可能需要序列化、拷贝（虽不在库的性能指标里，人家用还是要算，包括：GC、内存、CPU）\n- 如果序列化的部分可以复用用在协议层拼接，能做到`ZeroCopy`，那也无可厚非，但实际分层以后，无法在协议层直接实现拼接，而`ecache`存储指针直接省了额外的部分\n- 我想表达的并不是GC优化不重要，而更多应该结合场景，使用者额外损耗也需要考虑，而非宣称gc-free，结果用起来并非那样\n- 我所崇尚的“暴力美学”是极简，缺陷率和代码量成正比，复杂的东西早晚会被淘汰，`KISS`才是王道\n- `ecache`一共只有不到300行，千行bug率一定的情况下，它的bug不会多\n\n## 常见问题\n\u003e 问：一个实例可以存储多种对象吗？\n- 答：可以呀，比如加前缀格式化key就可以了（像用redis那样冒号分割），注意⚠️别搞错类型。\n\n\u003e 问：如何给不同item设置不同过期时间？\n- 答：用多个缓存实例。（😄没想到吧）\n\n\u003e 问：如果有热热热热key问题怎么解决？\n- 答：本身【本地内存缓存】就是用来扛住热key的，这里可以理解成是非常非常热的key（单节点几十万QPS），它们最大的问题是对单一bucket锁定次数过多，影响在同一个bucket的其他数据。那么可以这样：一是改用`LRU-2`不让类似遍历的请求把热数据刷掉，二是除了增加bucket，可以用多实例（同时写入相同的item）+读访问某一个（比如按访问用户uid hash）的方式，让热key有多个副本，不过删除（反写）的时候要注意多实例全部删除，适用于“写少读多`WORM(Write-Once-Read-Many)`”的场景，或者“写多读多”的场景可以把有变化的diff部分单独摘出来转化为“写少读多`WORM(Write-Once-Read-Many)`”的场景。\n\n\u003e 问：如果同一时间并发回源到DB查询同一个资源怎么优化？\n- 答：可以使用[sync/singleflight](https://pkg.go.dev/golang.org/x/sync/singleflight)包，同时访问同一个资源时，只回源一次，防止热点数据把DB打爆的问题。\n\n\u003e 问：为什么不用虚表头方式处理双链表？太弱了吧！\n- 答：2019-04-22泄漏的【[lrucache](http://github.com/orca-zhang/lrucache)】被人在V站上扒出来喷过，还真不是不会，现在的写法，虽然比pointer-to-pointer方式读起来绕脑，但是有20%左右的提升哈！（😄没想到吧）\n\n## 相关文献\n\n- [如何一步步提升Go内存缓存性能](https://my.oschina.net/u/5577511/blog/5438484)\n\n## 致谢\n\n感谢在开发过程中进行code review、勘误 \u0026 提出宝贵建议的各位！（排名不分先后）\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"https://github.com/askuy\"\u003e\n        \u003cimg src=\"https://avatars.githubusercontent.com/u/14119383?v=4\" width=\"64px;\" alt=\"\"/\u003e\n        \u003cbr /\u003e\n        \u003cb\u003easkuy\u003c/b\u003e\n        \u003cbr /\u003e\n        \u003csub\u003e\u003ca href=\"https://github.com/gotomicro/ego\"\u003e[ego]\u003c/a\u003e\u003c/sub\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"https://github.com/auula\"\u003e\n        \u003cimg src=\"https://avatars.githubusercontent.com/u/38412458?v=4\" width=\"64px;\" alt=\"\"/\u003e\n        \u003cbr /\u003e\n        \u003cb\u003eLeon Ding\u003c/b\u003e\n        \u003cbr /\u003e\n        \u003csub\u003e\u003ca href=\"https://mp.weixin.qq.com/mp/profile_ext?action=home\u0026__biz=MzI3MzQwNjcyNg==\u0026scene=124#wechat_redirect\"\u003e[打码匠]\u003c/a\u003e\u003c/sub\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"https://github.com/Danceiny\"\u003e\n        \u003cimg src=\"https://avatars.githubusercontent.com/u/9427454?v=4\" width=\"64px;\" alt=\"\"/\u003e\n        \u003cbr /\u003e\n        \u003cb\u003e黄振\u003c/b\u003e\n        \u003cbr /\u003e\n        \u003csub\u003e\u0026nbsp;\u003c/sub\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"https://github.com/IceCream01\"\u003e\n        \u003cimg src=\"https://avatars.githubusercontent.com/u/19547638?v=4\" width=\"64px;\" alt=\"\"/\u003e\n        \u003cbr /\u003e\n        \u003cb\u003eIce\u003c/b\u003e\n        \u003cbr /\u003e\n        \u003csub\u003e\u0026nbsp;\u003c/sub\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"https://github.com/FishGoddess\"\u003e\n        \u003cimg src=\"https://avatars.githubusercontent.com/u/36259784?v=4\" width=\"64px;\" alt=\"\"/\u003e\n        \u003cbr /\u003e\n        \u003cb\u003e水不要鱼\u003c/b\u003e\n        \u003cbr /\u003e\n        \u003csub\u003e\u003ca href=\"https://github.com/FishGoddess/cachego\"\u003e[cachego]\u003c/a\u003e\u003c/sub\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## 赞助\n\n通过成为赞助商来支持这个项目。 您的logo将显示在此处，并带有指向您网站的链接。 [[成为赞助商](https://opencollective.com/ecache#sponsor)]\n\n\u003ca href=\"https://opencollective.com/ecache/sponsor/0/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/sponsor/0/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/ecache/sponsor/1/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/sponsor/1/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/ecache/sponsor/2/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/sponsor/2/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/ecache/sponsor/3/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/sponsor/3/avatar.svg\"\u003e\u003c/a\u003e\n\n## 贡献者\n\n这个项目的存在要感谢所有做出贡献的人。\n\n请给我们一个💖star💖来支持我们，谢谢。\n\n并感谢我们所有的支持者！ 🙏\n\n\u003ca href=\"https://opencollective.com/ecache/backer/0/website?requireActive=false\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/backer/0/avatar.svg?requireActive=false\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/ecache/backer/1/website?requireActive=false\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/backer/1/avatar.svg?requireActive=false\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/ecache/backer/2/website?requireActive=false\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/backer/2/avatar.svg?requireActive=false\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/ecache/backer/3/website?requireActive=false\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/backer/3/avatar.svg?requireActive=false\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/ecache#backers\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/ecache/contributors.svg?width=890\" /\u003e\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forca-zhang%2Fecache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Forca-zhang%2Fecache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forca-zhang%2Fecache/lists"}