头图

一个优雅的 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-...
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!


12 声望
5 粉丝
0 条评论
推荐阅读
如何快速定位现网 BUG
“幸福的家庭都是相似的,不幸的家庭却各有各的不幸”,托尔斯泰的名言。在 BUG 定位这件事情上,其实也有类似的现象:”菜鸟们的紧张无措都是相似的,老鸟们的方法却各有各的不同”。

有疑说阅读 964

封面图
一个HTTP请求的曲折经历
作为程序员的我们每天都在和网络请求打交道,而前端程序员接触的最多的就是HTTP请求。平时工作中,处理网络请求之类的操作是最多的了。但是一个请求从客户端发出到被服务端处理、再回送响应,再被客户端接收这一...

nero24阅读 5.1k评论 1

Nginx 一网打尽:动静分离、压缩、缓存、黑白名单、跨域、高可用、性能优化...
早期的业务都是基于单体节点部署,由于前期访问流量不大,因此单体结构也可满足需求,但随着业务增长,流量也越来越大,那么最终单台服务器受到的访问压力也会逐步增高。时间一长,单台服务器性能无法跟上业务增...

民工哥23阅读 1.1k

封面图
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀24阅读 58.4k评论 2

浏览器之HTTP缓存的那些事
简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html,图片,js)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制...

Samon16阅读 10k评论 7

最好用的 python 库合集
🎈 分词 - jieba优秀的中文分词库,依靠中文词库,利用词库确定汉子之间关联的概率,形成分词结果 {代码...} 🎈 词云库 - wordcloud对数据中出现频率较高的 关键词 生成的一幅图像,予以视觉上的突出 {代码...} 🎈 ...

tiny极客11阅读 2.9k评论 2

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬14阅读 1.8k

12 声望
5 粉丝
宣传栏