一个优雅的 LRU 缓存实现

简介: 一个优雅的 LRU 缓存实现

Golang 的各种组件很灵活也很强大,但对于初级入门的使用者来说,要用好着实不易。最近,在开发一个可以拿来即用的 golang 库。第一个组件选择了缓存,主要是因为这个组件非常的关键,但也非常不容易实现好。

第一步:定义 Cache 接口

设计一个高扩展的缓存包,就需要利用 里氏替换原则(Liskov Substitution Principle),做好抽象,将缓存定义为接口

type Cache interface {
    ...
}

第二步:组织包结构

然后,实现一个具体的 LRU 缓存,那么此时首先要组织好包结构,如下:

|-cache
| |-lru
| | |-lru.go
| | |-segment.go
| | |-options.go
| | |-expvar.go
|-cache.go

利用包划分层次,将接口放在根包下,作为所有子包的通用语言:

// cache.go
package edge
type Cache interface {
    ...
}

第三步:实现 LRU 缓存

  1. 为了防止锁竞争导致的性能低下,此处使用分段加锁的方式降低锁粒度以提高缓存性能
  2. 同时将 segmentnewSegmentcache 以小写命名,避免对外暴露实现细节
  3. 使用 Higher-order function,实现可扩展的配置参数
  4. 使用 expvar 暴露缓存的状态
// lru.go
package lru
type cache struct {
    ...
}
func New(opts ...Opt) (*cache, error) {
    ...
}
// segment.go
package lru
type segment struct {
    ...
}
func newSegment(c int) *segment {
    ...
}
// options.go
type options struct {
    ...
}
type Opt func(*options)
func WithConcurrency(c int) Opt {
  return func(o *options) {
    o.concurrency = c
  }
}
func WithCapacity(c int) Opt {
  return func(o *options) {
    o.capacity = c
  }
}
// expvar.go
var m = struct {
  Get    *expvar.Int
  Set    *expvar.Int
  Delete *expvar.Int
  Exists *expvar.Int
  Hit    *expvar.Int
  Evict  *expvar.Int
}{
  Get:    expvar.NewInt("cache.lru.get"),
  Set:    expvar.NewInt("cache.lru.set"),
  Delete: expvar.NewInt("cache.lru.delete"),
  Exists: expvar.NewInt("cache.lru.exists"),
  Hit:    expvar.NewInt("cache.lru.hit"),
  Evict:  expvar.NewInt("cache.lru.evict"),
}

第四步:结束了么?

当然没有,从以上可以看到,以下几点:

  1. options 可以做到多种实现共用,更应该放在 cache 文件夹下。
  2. 在使用时,lru.New() 赋值给 Cache 接口略微不自然
  3. segment.go 和 expvar.go 未对使用者开放但文件却对外暴露
  4. segment 可能后续会用来实现其他缓存算法,也不适合放在 lru 包下

基于以上原因,再次调整包结构如下:

|-cache
| |-options.go
| |-lru.go
|-cache.go
|-internal
| |-cache
| | |-lru
| | | |-expvar.go
| | | |-segment.go

同时,调整 LRU 缓存的接口为:

// cache.go
package cache
type cache struct {
    ...
}
func NewLRU(opts ...Opt) (*cache, error) {
    ...
}

是不是自然了很多,使用示例:

var c edge.Cache = cache.NewLRU()

总结

学以致用,此 LRU 的实现应用了很多之前的知识。追求优秀代码的路是没有尽头的,下课。

源代码:github.com/cyningsun/edge

本文作者 : cyningsun

本文地址https://www.cyningsun.com/07-26-2021/go-a-graceful-lru-implement.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Golang

  1. 译|There Are No Reference Types in Go
  2. Go 语言没有引用类型,指针也与众不同
  3. 译|What “accept interfaces, return structs” means in Go
  4. 如何用好 Go interface
  5. Go 函数式编程:Higher-order function
目录
相关文章
|
2月前
|
缓存 算法 数据挖掘
深入理解缓存更新策略:从LRU到LFU
【10月更文挑战第7天】 在本文中,我们将探讨计算机系统中缓存机制的核心——缓存更新策略。缓存是提高数据检索速度的关键技术之一,无论是在硬件还是软件层面都扮演着重要角色。我们会详细介绍最常用的两种缓存算法:最近最少使用(LRU)和最少使用频率(LFU),并讨论它们的优缺点及适用场景。通过对比分析,旨在帮助读者更好地理解如何选择和实现适合自己需求的缓存策略,从而优化系统性能。
53 3
|
2月前
|
缓存 分布式计算 NoSQL
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
73 2
|
4月前
|
缓存 算法 前端开发
深入理解缓存淘汰策略:LRU和LFU算法的解析与应用
【8月更文挑战第25天】在计算机科学领域,高效管理资源对于提升系统性能至关重要。内存缓存作为一种加速数据读取的有效方法,其管理策略直接影响整体性能。本文重点介绍两种常用的缓存淘汰算法:LRU(最近最少使用)和LFU(最不经常使用)。LRU算法依据数据最近是否被访问来进行淘汰决策;而LFU算法则根据数据的访问频率做出判断。这两种算法各有特点,适用于不同的应用场景。通过深入分析这两种算法的原理、实现方式及适用场景,本文旨在帮助开发者更好地理解缓存管理机制,从而在实际应用中作出更合理的选择,有效提升系统性能和用户体验。
201 1
|
5月前
|
缓存 Python
在Python中,`functools`模块提供了一个非常有用的装饰器`lru_cache()`,它实现了最近最少使用(Least Recently Used, LRU)缓存策略。
在Python中,`functools`模块提供了一个非常有用的装饰器`lru_cache()`,它实现了最近最少使用(Least Recently Used, LRU)缓存策略。
|
4月前
|
存储 缓存 Java
|
4月前
|
存储 缓存 算法
Python 从零开始实现一个简单的LRU缓存
Python 从零开始实现一个简单的LRU缓存
42 0
|
6月前
|
缓存 算法 索引
LeetCode146:LRU缓存
LeetCode146:LRU缓存
45 1
|
5月前
|
缓存 算法 前端开发
前端 JS 经典:LRU 缓存算法
前端 JS 经典:LRU 缓存算法
99 0
|
7月前
|
缓存 算法 Java
数据结构~缓存淘汰算法--LRU算法(Java的俩种实现方式,万字解析
数据结构~缓存淘汰算法--LRU算法(Java的俩种实现方式,万字解析
|
7月前
|
缓存 算法 前端开发
前端开发者必知的缓存淘汰策略:LRU算法解析与实践
前端开发者必知的缓存淘汰策略:LRU算法解析与实践
206 0