golang:缓存库cache2go介绍

╰+哭是因爲堅強的太久メ 2022-04-15 00:53 389阅读 0赞

文章目录

    • 是什么
    • 项目结构
    • 设计原理
    • 关键数据结构
      • CacheItem
      • CacheTable
    • cache.go
    • 例子

是什么

在这里插入图片描述

  • 带有时效性的单机缓存

项目结构

项目地址:https://github.com/muesli/cache2go
在这里插入图片描述

设计原理

在这里插入图片描述

关键数据结构

  • CacheItem:缓存表中的条目
  • CacheTable :缓存表

CacheItem

没什么好看的,除了需要注意一下cacheItem的结构之外

  1. import (
  2. "sync"
  3. "time"
  4. )
  5. type CacheItem struct {
  6. sync.RWMutex //读写锁:为了保证其并发安全性,都带有sync.RWMutex,维持操作的原子性。并带有时间戳来实现过期控制。
  7. key interface{
  8. } //缓存项的key
  9. data interface{
  10. } //缓存项的value
  11. lifeSpan time.Duration//缓存的生命周期:当不再被访问时,该项目在缓存中生存多久
  12. createdOn time.Time //缓存项目的创建时间戳。
  13. accessedOn time.Time //缓存项目上一次被访问的时间戳
  14. accessCount int64 //缓存项目被访问的次数
  15. aboutToExpire func(key interface{
  16. }) // // 在删除缓存项之前调用的回调函数
  17. }
  18. // NewCacheItem返回一个新创建的CacheItem的指针
  19. // 参数key时缓存项目的缓存密码key
  20. // 参数lifeSpan决定经过多久之后将该缓存项删除【存活时间】
  21. // 参数data是缓存项的value
  22. func NewCacheItem(key interface{
  23. }, lifeSpan time.Duration, data interface{
  24. }) *CacheItem {
  25. t := time.Now()
  26. return &CacheItem{
  27. key: key,
  28. lifeSpan: lifeSpan,
  29. createdOn: t,
  30. accessedOn: t, //createdOn和accessedOn设置成了当前时间
  31. accessCount: 0,
  32. aboutToExpire: nil, //被删除时触发的回调方法初始化为nil【推测还应当调用其他方法来设置这个属性】
  33. data: data,
  34. }
  35. }
  36. // KeepAlive标记一个项目保持另一个expireDuration(持续时间)周期
  37. func (item *CacheItem) KeepAlive() {
  38. item.Lock()
  39. defer item.Unlock()
  40. item.accessedOn = time.Now() //记录此时访问的时间
  41. item.accessCount++ //增加被访问的次数
  42. }
  43. // LifeSpan返回CacheItem的生命期限。
  44. func (item *CacheItem) LifeSpan() time.Duration {
  45. // immutable
  46. return item.lifeSpan
  47. }
  48. // AccessedOn返回CacheItem最后被访问的时间
  49. func (item *CacheItem) AccessedOn() time.Time {
  50. item.RLock()
  51. defer item.RUnlock()
  52. return item.accessedOn
  53. }
  54. // CreatedOn返回当前CacheItem的最开始创建的时间
  55. func (item *CacheItem) CreatedOn() time.Time {
  56. // immutable
  57. return item.createdOn
  58. }
  59. // AccessCount返回当前CacheItem被访问的次数
  60. func (item *CacheItem) AccessCount() int64 {
  61. item.RLock()
  62. defer item.RUnlock()
  63. return item.accessCount
  64. }
  65. // Key返回当前CacheItem中的key
  66. func (item *CacheItem) Key() interface{
  67. } {
  68. // immutable
  69. return item.key
  70. }
  71. // Data返回当前CacheItem的value
  72. func (item *CacheItem) Data() interface{
  73. } {
  74. // immutable
  75. return item.data
  76. }
  77. // SetAboutToExpireCallback配置被删除之前要调用的回调函数
  78. func (item *CacheItem) SetAboutToExpireCallback(f func(interface{
  79. })) {
  80. item.Lock()
  81. defer item.Unlock()
  82. item.aboutToExpire = f
  83. }

在这里插入图片描述

CacheTable

  1. package cache2go
  2. import (
  3. "log"
  4. "sort"
  5. "sync"
  6. "time"
  7. )
  8. // CacheTable is a table within the cache
  9. type CacheTable struct {
  10. sync.RWMutex
  11. // 缓存表名
  12. name string
  13. //所有的缓存项
  14. items map[interface{
  15. }]*CacheItem
  16. //负责触发缓存清理的定时器。
  17. cleanupTimer *time.Timer
  18. // 缓存清理周期
  19. cleanupInterval time.Duration
  20. // 该缓存表的日志
  21. logger *log.Logger
  22. //当试图获取一个不存在的缓存项时的回调函数
  23. loadData func(key interface{
  24. }, args ...interface{
  25. }) *CacheItem
  26. //当向缓存表中增加一个缓存项时的回调函数
  27. addedItem func(item *CacheItem)
  28. //当从缓存表中删除一个缓存项时被调用的函数
  29. aboutToDeleteItem func(item *CacheItem)
  30. }
  31. // Count returns返回当前缓存中存储有多少个缓存项:求map的长度
  32. func (table *CacheTable) Count() int {
  33. table.RLock()
  34. defer table.RUnlock()
  35. return len(table.items)
  36. }
  37. //遍历所有的缓存项:遍历map
  38. func (table *CacheTable) Foreach(trans func(key interface{
  39. }, item *CacheItem)) {
  40. table.RLock()
  41. defer table.RUnlock()
  42. for k, v := range table.items {
  43. trans(k, v)
  44. }
  45. }
  46. // SetDataLoader配置【当试图获取一个不存在的缓存项时】的回调函数
  47. func (table *CacheTable) SetDataLoader(f func(interface{
  48. }, ...interface{
  49. }) *CacheItem) {
  50. table.Lock()
  51. defer table.Unlock()
  52. table.loadData = f
  53. }
  54. // SetAddedItemCallback配置【当向缓存表中增加一个缓存项时的回调函数
  55. func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {
  56. table.Lock()
  57. defer table.Unlock()
  58. table.addedItem = f
  59. }
  60. // SetAboutToDeleteItemCallback配置【当从缓存表中删除一个缓存项时】被调用的函数
  61. func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) {
  62. table.Lock()
  63. defer table.Unlock()
  64. table.aboutToDeleteItem = f
  65. }
  66. //SetLogger配置当前缓存表所使用的日志
  67. func (table *CacheTable) SetLogger(logger *log.Logger) {
  68. table.Lock()
  69. defer table.Unlock()
  70. table.logger = logger
  71. }
  72. //由计时器触发的到期检查.
  73. func (table *CacheTable) expirationCheck() {
  74. table.Lock()
  75. if table.cleanupTimer != nil {
  76. //不设置为nil
  77. table.cleanupTimer.Stop()
  78. }
  79. if table.cleanupInterval > 0 {
  80. //计时器的时间间隔
  81. table.log("Expiration check triggered after", table.cleanupInterval, "for table", table.name)
  82. } else {
  83. table.log("Expiration check installed for table", table.name) //不设置为0
  84. }
  85. // 为了更准确的使用定时器,我们应该再每个记时时间周期跟新now()。不确定是否真的有效
  86. now := time.Now()
  87. smallestDuration := 0 * time.Second
  88. for key, item := range table.items {
  89. // Cache values so we don't keep blocking the mutex.
  90. item.RLock()
  91. lifeSpan := item.lifeSpan
  92. accessedOn := item.accessedOn
  93. item.RUnlock()
  94. if lifeSpan == 0 {
  95. //0永久有效
  96. continue
  97. }
  98. if now.Sub(accessedOn) >= lifeSpan {
  99. // 项目超过了项目周期则删除项目
  100. table.deleteInternal(key)
  101. } else {
  102. // 查询最靠近死亡周期的项目
  103. if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration {
  104. smallestDuration = lifeSpan - now.Sub(accessedOn)
  105. }
  106. }
  107. }
  108. //为下次清理设置间隔
  109. table.cleanupInterval = smallestDuration
  110. if smallestDuration > 0 {
  111. table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
  112. go table.expirationCheck()
  113. })
  114. }
  115. table.Unlock()
  116. }
  117. //
  118. func (table *CacheTable) addInternal(item *CacheItem) {
  119. // Careful: 除非表互斥锁被锁定,不要调用此方法
  120. // It will unlock it for the caller before running the callbacks and checks
  121. table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name)
  122. table.items[item.key] = item //将item加入缓存表中
  123. // Cache values so we don't keep blocking the mutex.
  124. expDur := table.cleanupInterval //默认为0
  125. addedItem := table.addedItem //设置item加入缓存表中的回调函数,[如果没有使用SetAddedItemCallback设置的化就是nil]
  126. table.Unlock()
  127. // 将item加入cache表中是触发
  128. if addedItem != nil {
  129. //如果设置了回调函数
  130. addedItem(item) //调用回调函数
  131. }
  132. // If we haven't set up any expiration check timer or found a more imminent item.
  133. if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {
  134. table.expirationCheck()
  135. }
  136. }
  137. // Add创建一个CacheItem,加锁,调用addInternal:传参:key,生命周期,和key-value中的value
  138. func (table *CacheTable) Add(key interface{
  139. }, lifeSpan time.Duration, data interface{
  140. }) *CacheItem {
  141. item := NewCacheItem(key, lifeSpan, data)
  142. //增加一个item到cache
  143. table.Lock()
  144. table.addInternal(item) //传参就是新创建的CacheItem
  145. return item
  146. }
  147. func (table *CacheTable) deleteInternal(key interface{
  148. }) (*CacheItem, error) {
  149. r, ok := table.items[key]
  150. if !ok {
  151. return nil, ErrKeyNotFound
  152. }
  153. // Cache value so we don't keep blocking the mutex.
  154. aboutToDeleteItem := table.aboutToDeleteItem
  155. table.Unlock()
  156. //从缓存中删除item之前触发回调函数
  157. if aboutToDeleteItem != nil {
  158. aboutToDeleteItem(r)
  159. }
  160. r.RLock()
  161. defer r.RUnlock()
  162. if r.aboutToExpire != nil {
  163. r.aboutToExpire(key)
  164. }
  165. table.Lock()
  166. table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount, "times from table", table.name)
  167. delete(table.items, key)
  168. return r, nil
  169. }
  170. // 从缓存中删除缓存
  171. func (table *CacheTable) Delete(key interface{
  172. }) (*CacheItem, error) {
  173. table.Lock()
  174. defer table.Unlock()
  175. return table.deleteInternal(key)
  176. }
  177. // Exists判断item是否存在cache中.
  178. func (table *CacheTable) Exists(key interface{
  179. }) bool {
  180. table.RLock()
  181. defer table.RUnlock()
  182. _, ok := table.items[key]
  183. return ok
  184. }
  185. // NotFoundAdd测试item是否在缓存表中. 如果没有找到就新创建一个item并加入缓存表中
  186. func (table *CacheTable) NotFoundAdd(key interface{
  187. }, lifeSpan time.Duration, data interface{
  188. }) bool {
  189. table.Lock()
  190. if _, ok := table.items[key]; ok {
  191. //遍历map表,如果找到就返回false
  192. table.Unlock()
  193. return false
  194. }
  195. item := NewCacheItem(key, lifeSpan, data) //如果当前没有对应key-value,就新创建一个item并加入缓存表,然后返回true
  196. table.addInternal(item)
  197. return true
  198. }
  199. // Value returns an item from the cache and marks it to be kept alive. You can
  200. // pass additional arguments to your DataLoader callback function.
  201. func (table *CacheTable) Value(key interface{
  202. }, args ...interface{
  203. }) (*CacheItem, error) {
  204. table.RLock()
  205. r, ok := table.items[key] //从table表也就是map中查找key
  206. loadData := table.loadData //如果没有专门就是就是nil
  207. table.RUnlock()
  208. if ok {
  209. //更新访问时间戳和访问次数加1
  210. r.KeepAlive()
  211. return r, nil
  212. }
  213. // 如果查找item不存在缓存表中. 可以设置data-loader回调然后加入到cache表中.
  214. //即如果我们去查找某个key的缓存,如果找不到且我们设置了dataloader回调,就会执行该回调函数。这个功能还是挺实用的,举个例子比如我们缓存了数据库中的一些用户信息,如果我们可以设置dataloader回调,
  215. //如果从缓存里面查找某个用户信息时没有找到,就从数据库中读取该用户信息并加到缓存里面,这个动作就可以加在dataloader回调里面
  216. if loadData != nil {
  217. item := loadData(key, args...)
  218. if item != nil {
  219. table.Add(key, item.lifeSpan, item.data)
  220. return item, nil
  221. }
  222. return nil, ErrKeyNotFoundOrLoadable
  223. }
  224. return nil, ErrKeyNotFound
  225. }
  226. // Flush deletes all items from this cache table.
  227. func (table *CacheTable) Flush() {
  228. table.Lock()
  229. defer table.Unlock()
  230. table.log("Flushing table", table.name)
  231. table.items = make(map[interface{
  232. }]*CacheItem)
  233. table.cleanupInterval = 0
  234. if table.cleanupTimer != nil {
  235. table.cleanupTimer.Stop()
  236. }
  237. }
  238. // CacheItemPair maps key to access counter
  239. type CacheItemPair struct {
  240. Key interface{
  241. }
  242. AccessCount int64
  243. }
  244. // CacheItemPairList is a slice of CacheIemPairs that implements sort.
  245. // Interface to sort by AccessCount.
  246. type CacheItemPairList []CacheItemPair
  247. func (p CacheItemPairList) Swap(i, j int) {
  248. p[i], p[j] = p[j], p[i] }
  249. func (p CacheItemPairList) Len() int {
  250. return len(p) }
  251. func (p CacheItemPairList) Less(i, j int) bool {
  252. return p[i].AccessCount > p[j].AccessCount }
  253. // MostAccessed returns返回当前缓存表中被访问最多的表
  254. func (table *CacheTable) MostAccessed(count int64) []*CacheItem {
  255. table.RLock()
  256. defer table.RUnlock()
  257. p := make(CacheItemPairList, len(table.items))
  258. i := 0
  259. for k, v := range table.items {
  260. p[i] = CacheItemPair{
  261. k, v.accessCount}
  262. i++
  263. }
  264. sort.Sort(p)
  265. var r []*CacheItem
  266. c := int64(0)
  267. for _, v := range p {
  268. if c >= count {
  269. break
  270. }
  271. item, ok := table.items[v.Key]
  272. if ok {
  273. r = append(r, item)
  274. }
  275. c++
  276. }
  277. return r
  278. }
  279. // Internal logging method for convenience.
  280. func (table *CacheTable) log(v ...interface{
  281. }) {
  282. if table.logger == nil {
  283. return
  284. }
  285. table.logger.Println(v...)
  286. }

cache.go

  1. //总结:根据表名返回指向对应的表的地址。如果不存在该表就创建一个表
  2. package cache2go
  3. import (
  4. "sync"
  5. )
  6. var (
  7. cache = make(map[string]*CacheTable) //cacheTable实质是一个map,key是string,value是*CacheTable;
  8. mutex sync.RWMutex
  9. )
  10. // Cache[创建一个新表]返回已经存在的缓存表
  11. func Cache(table string) *CacheTable {
  12. mutex.RLock()
  13. t, ok := cache[table] //如果没有找到table名对应的cache表,
  14. mutex.RUnlock()
  15. if !ok {
  16. mutex.Lock()
  17. t, ok = cache[table]
  18. // 第二次检查表是否存在
  19. if !ok {
  20. t = &CacheTable{
  21. //如果不存在,就定义一个map表[CacheTable]并取出表的地址
  22. name: table, //表名
  23. items: make(map[interface{
  24. }]*CacheItem), //初始化一个CacheItem结构的map
  25. }
  26. cache[table] = t //将表的地址返回
  27. }
  28. mutex.Unlock()
  29. }
  30. return t
  31. }

例子

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/muesli/cache2go"
  5. "strconv"
  6. "time"
  7. )
  8. type myStruct struct {
  9. text string
  10. moreData []byte
  11. }
  12. func main() {
  13. cache := cache2go.Cache("myCache")
  14. val := myStruct{
  15. "This is a test!", []byte{
  16. }}
  17. cache.SetAddedItemCallback(func(entry *cache2go.CacheItem){
  18. fmt.Println("Added:", entry.Key(), entry.Data(), "---", entry.CreatedOn())
  19. })
  20. cache.Add("someKey", 5*time.Second, &val)
  21. res, err := cache.Value("someKey")
  22. if err == nil {
  23. fmt.Println("Found value in cache:", res.Data().(*myStruct).text)
  24. } else {
  25. fmt.Println("Error retrieving value from cache:", err)
  26. }
  27. // Wait for the item to expire in cache.
  28. //time.Sleep(6 * time.Second)
  29. //res, err = cache.Value("someKey")
  30. //if err != nil {
  31. // fmt.Println("Item is not cached (anymore).")
  32. //}
  33. cache.SetAboutToDeleteItemCallback(func(e *cache2go.CacheItem) {
  34. fmt.Println("Deleting:", e.Key(), e.Data().(*myStruct).text, e.CreatedOn())
  35. })
  36. // Remove the item from the cache.
  37. cache.Delete("someKey")
  38. cache.Flush()
  39. //--------------------------------------------------------
  40. /*之前介绍的回调函数都是在添加或删除缓存表项时候触发,而这个dataloader回调则是在调用Value时触发。即如果我们去查找某个key的缓存,如果找不到且我们设置了dataloader回调,就会执行该回调函数。这个功能还是挺实用的,举个例子比如我们缓存了数据库中的一些用户信息,如果我们可以设置dataloader回调,
  41. 如果从缓存里面查找某个用户信息时没有找到,就从数据库中读取该用户信息并加到缓存里面,这个动作就可以加在dataloader回调里面。*/
  42. // The data loader gets called automatically whenever something
  43. // tries to retrieve a non-existing key from the cache.
  44. cache.SetDataLoader(func(key interface{
  45. }, args ...interface{
  46. }) *cache2go.CacheItem {
  47. // Apply some clever loading logic here, e.g. read values for
  48. // this key from database, network or file.
  49. val := "This is a test with key " + key.(string)
  50. // This helper method creates the cached item for us. Yay!
  51. item := cache2go.NewCacheItem(key, 0, val)
  52. return item
  53. })
  54. // Let's retrieve a few auto-generated items from the cache.
  55. for i := 0; i < 1; i++ {
  56. res, err := cache.Value("someKey_" + strconv.Itoa(i))
  57. if err == nil {
  58. fmt.Println("Found value in cache:", res.Data())
  59. } else {
  60. fmt.Println("Error retrieving value from cache:", err)
  61. }
  62. }
  63. fmt.Println(cache.Count())
  64. }

参考:https://time-track.cn/cache2go-introduction.html
https://blog.csdn.net/Paddy90/article/details/72884578

发表评论

表情:
评论列表 (有 0 条评论,389人围观)

还没有评论,来说两句吧...

相关阅读