Golang业务开发:本地缓存选择对比及原理总结
本地缓存大家都知道。任何具有后端开发经验的人都知道缓存的来龙去脉。本文简单讨论了golang业务开发过程中一些可选的本地缓存开源解决方案,分析了它们的特点和内部实现原理。
1。分析本地缓存需求
首先我们看一下业务开发中经常遇到的一些本地缓存需求。我们通常会进行缓存来提高系统读写性能。缓存命中率越高,缓存效果越好。其次,本地缓存通常受到本地内存大小的限制,通常无法存储全部数据量。基于这种场景,一方面,缓存中的数据越多,理论上命中率会随着缓存数据的增加而增加;另一方面,由于并不是所有的数据都能被存储,我们需要找到一种方法来利用有限的内存。这些有限的数据必须经常被访问并具有一定的时效性(不经常改变)。基于这两点,我们一般需要一个本地缓存来支持过期时间和清除策略。最后,在使用像golang这样自动管理内存的语言时,还需要考虑添加本地缓存的GC问题。
分析了我们日常的本地缓存需求,结合日常开发所使用的golang语言,我们可以确定golang本地缓存组件应该具备以下能力:
分析让我们的需求明确,能力也明确。我们需要。那么自然优先考虑的是golang内置标准库中是否有可以直接使用的组件?不幸的是,这儿没有。 golang中内置的唯一可以直接用于本地缓存的东西是map和sync.Map。两者中,map是非并发安全的数据结构,使用时需要加锁;而 synchronous.Map 是线程安全的。但同时读写时必须关闭。而且,它们都不支持数据过期和删除。然而,当存储大量数据时,GC问题更容易出现。更严重的情况下,在线服务无法稳定运行。
由于标准库中没有本地缓存组件可以满足我们上述需求,所以我们认为解决方案只有两种:
- 能否选择业界开源成熟的解决方案?
- 如果业界没有可用的部分,请自行编写一个。
您面临的第一个问题是研究和选择解决方案。如果没有合适的解决方案,您可以自己构建。现在介绍一下golang中一些可以直接使用的本地缓存组件。? 4 (https://github.com/golang/groupcache)
- ristretto
( ()()()(
go-cache
) ) (https://github.com/golang/groupcache)github.com/patrickmn/go-cache)
经过作者一段时间的研究和研究,总结了golang可选的开源本地缓存的组成部分,如下表所示。为了您的方便。将其用作选择计划时的参考。
上述方案中,根据实际业务场景,freecache、bigcache、fastcache、ristretto、groupcache 是首选。当有定制需求时,Offheap 就会发挥作用。
通过上表的总结,我想再谈谈关于理解本地缓存组件的几点:
(1)上述本地缓存组件中,实现零的方案主要有两种GC
无GC:堆外内存分配(Mmap)
避免GC:映射非指针优化(map[uint64]uint32)或使用切片来实现一组无指针映射。
避免GC:数据存储在[]字节切片中(可以考虑使用底部的环行嵌套回收区)
(2)高性能的关键
锁的粒度)
上述各个缓存组件的实现都是(1)和(2)的几个分支的组合。下面简要介绍每个组件的基本原理。
3。主要缓存组件实现原理分析
本节重点分析freecache、bigcache、fastcache、offheap的内部实现原理。
(一)Freecache 的实现原理
首先分析一下freecache 的内部实现原理。在空闲缓存中,您可以将数据分段。 Freecache有256个段,每个段都有一个互斥锁。接收到每个kv数据后,我们首先根据k计算哈希值,然后计算哈希值。判断当前数据属于哪个段。
对于每个段,它由两部分组成:索引和数据。
Index:维护索引最简单的方式就是map,比如map[uint64]uint32。 Freecache没有采取这种方式,而是在较低层使用切片来实现无指针映射,以避免GC扫描。
数据:使用环形缓冲区回收数据,下层以[]byte关闭。数据写入环形缓冲区后,将写入的位置索引记录为索引。读取时,我们首先读取数据头信息,然后读取kv数据。
freecache中的数据传输过程如下:freecache->segment->(slot,ringbuffer) 下图是freecache的内部实现框架图。
总结:Freecache通过数据拆分来降低锁的粒度。存储时,索引并不使用内置的map来维护,而是使用自制的map来减少指针以避免GC。同时,预分配的内存用于数据存储。然后稍后再回收。以上两种方法保证了堆上预留内存的同时减少了GC对系统性能的影响。
(2)Bigcache 的实现原理
Bigcache 与 freecache 类似。它也是一个零GC的高性能缓存组件,但是它的实现与freecache还是有区别的。有一篇英文博客(https://blog.allegro.tech/2016/03/writing-fast-cache-service-in-go.html)介绍了bigcache的设计原理。如果您对内容感兴趣,可以阅读。下面介绍bigcache的实现原理。
bigcache 也由分片组成。一个bigcache对象包含2^n个cacheShard对象,默认值为1024。每个cacheShard对象维护一个sync.RWLock锁(读写锁)。所有数据都存储在不同的cacheShards中。
每个cacheShard都由索引和数据组成。索引使用map[uint64]uint32存储,数据使用entry([]byte)环形队列存储。数据写入entryBuffer 的索引位置。所有kv数据按照TLV格式排队。
不过值得注意的是bigcache和freecache的区别在于环形队列可以自动扩展。同时,bigcache中数据的过期时间是通过全局时间窗口来维护的,每个kv不能设置其他过期时间。
下图是bigcache的内容实现原理框架图。
总结:bigcache的思想和freecache大致相同,不同的是它在索引存储方面更加娴熟,它直接利用内置的map结构和基本数据类型来实现。同时,存储数据的底层等待列表也可以根据区域大小决定是否扩展。唯一的错误是您无法为每个密钥设置不同的过期时间。此人认为,如果你想使用bigcache,想要这个功能,可以做二次开发。
从性能测试数据(https://github.com/allegro/bigcache-bench)来看,bigcache的性能略优于freecache。您能想到他们的表现可能存在的差异吗?
(3)Fastcache 的实现原理
本节介绍fastcache 的实现原理。根据fastcache官方文档,它的灵感来自于bigcache。所以总体思路与bigcache非常相似,数据分布在桶之间。 Fastcache由512个桶组成。所有容器都维护读写锁。同样,桶中的数据由索引和数据组成。使用map[uint64]uint64 存储索引。数据使用二维块(二维数组)存储。不过值得注意的是,fastcache的一大特点是它的内存分配是在优惠券之外,而不是优惠券内。从堆上分配的内存。这样就避免了golang GC的影响。下图是fastcache内部实现框架图。
总结:一方面,fastcache充分利用分片的优势,降低锁定粒度。另一方面,它在索引存储期间使用映射优化。然而,在分配内存时,它直接申请堆外内存并自行执行分配。和内存释放逻辑。有了以上工具,GC的影响就降到了最低。 fastcache唯一的缺陷是官方版本没有提供kv数据过期时间。所以如果需要这个功能,就得自己进行二次开发了。总体来说,在性能方面比bigcache和freecache要好。
(四)offheap的实现原理
本节介绍offheap的相关内容。 offheap的实际功能比较简单,是一个基于堆外内存的哈希表。它通过直接调用系统调用函数来分配内存。然后通过数组在内部实现哈希表。如果执行过程中发生哈希冲突,则使用 detector 方法解决。因为哈希表是基于在优惠券之外分配的内存的。它造成的GC开销很小。下图是offheap的内部实现框架图。
总结:由于offheap采用检测的方式解决哈希冲突,当哈希冲突严重时,删除和查询数据会导致处理过程非常复杂。性能也会下降。对于学习和研究项目来说仍然非常好。
总结
本文首先从日常需求出发,分析日常业务流程中对本地缓存的需求,然后对业界的一些可选组件进行考察和比较,希望对本地缓存的选型起到一定的作用。参考和帮助。最后简单介绍一下一些重要组件的内部实现,比如freecache、bigcache、fastcache、offheap等。以上内容仅从架构层面呈现。以后有时间的话我会在源码层面做一些分析。由于篇幅限制,本文不涉及map、sync.Map、go-cache和groupcache。有兴趣的读者可以自行搜索资料阅读。如果你对上面的原理有了大概的了解,你就可以自己练习造轮子了。
参考资料:
1.用Go编写一个具有数百万条目的非常快的缓存服务
2.深入了解Freecache
3.如何构建高性能的Goche库。 4.freecache与bigcache性能测试数据对比作者简介文晓飞腾讯后台开发工程师
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。