在构建分布式系统时,缓存是提高性能和减少负载的关键组件之一。Golang 作为一种快速、高效的编程语言,有着丰富的生态系统,其中一个备受推崇的缓存库就是 GroupCache。
什么是 GroupCache?
GroupCache 是由 Google 设计并开源的一个分布式缓存库,旨在解决大规模数据的缓存需求。它不仅适用于单个节点上的缓存,还支持分布式环境下的缓存共享与协同。GroupCache 是由 Google 设计并开源的一个分布式缓存库,旨在解决大规模数据的缓存需求。它不仅适用于单个节点上的缓存,还支持分布式环境下的缓存共享与协同。
GroupCache 是 Brad Fitzpatrick (memcached 的作者)开发的另一个键值缓存项目,并且在很多情况下被设计成 memcached 的替代品。与其他常用的缓存实现(比如 Redis)不同,GroupCache 不在单独的服务器上运行,而是作为一个库(library)和应用程序(app)运行在同一个进程中。因此,GroupCache 既是server又是client。
作为server,GroupCache 与其他 GroupCache 组件一起形成一个哈希环(Hash Ring)。当某个键的缓存不在本地时,GroupCache 会根据键的哈希值(默认为 CRC32)自动将缓存请求转发给其他成员,并将结果返回给调用方。对于调用方来说,这一切都是透明的,GroupCache 的客户端用户可以像使用本地缓存库一样使用 GroupCache 客户端。
特性
GroupCache 具有以下几个主要特性:
- 数据分片与分布式存储:GroupCache 可以将数据分片存储在多个节点上,以实现高性能和可扩展性。
- 懒加载与缓存逻辑:它支持懒加载机制,只有在需要时才会从数据源加载数据,并且可以根据具体应用的需求进行自定义的缓存逻辑。
- 一致性哈希算法:GroupCache 使用一致性哈希算法来确定数据在集群中的分布,从而保证了负载均衡和高效的缓存访问。
- 支持多种后端存储:GroupCache 可以与各种后端存储系统集成,包括内存、磁盘、数据库等,从而灵活应对不同的场景和需求。
GroupCache 最大的特点是它专注于提供高性能的读取缓存服务,而不支持常见的更新(update)、删除(delete)和 Time To Live(TTL)等操作。唯一更改cache的方式是refill。 GroupCache 之所以选择不支持这些操作,是因为它的设计目标是提供高性能的读取缓存服务,特别是在大规模的分布式系统中。
使用示例
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/golang/groupcache"
)
func main() {
// 创建一个 Group 实例
pool := groupcache.NewGroup("exampleGroup", 64<<20, groupcache.GetterFunc(
func(ctx context.Context, key string, dest groupcache.Sink) error {
// 模拟从数据源加载数据的过程
data := fmt.Sprintf("value for key: %s", key)
dest.SetBytes([]byte(data))
return nil
}))
// 从 Group 中获取数据
var data groupcache.ByteView
key := "exampleKey"
err := pool.Get(context.Background(), key, groupcache.AllocatingByteSliceSink(&data))
if err != nil {
log.Fatal(err)
}
fmt.Println("Data:", string(data))
}
这段代码创建了一个名为 exampleGroup
的 GroupCache 组,并指定了如何从后端数据源中读取数据的逻辑。
在这个示例中,使用了 groupcache.GetterFunc
函数,它接受一个函数作为参数,该函数定义了在缓存未命中时如何从后端数据源加载数据。在这个函数中,模拟了从数据源加载数据的过程,并将数据写入了 groupcache.Sink
。然后,可以通过 GroupCache 组的 API 来使用该组,例如通过调用 pool.Get
方法来获取数据。
GroupCache 中通过一个结构体来完成缓存的写入和读取操作,这种设计使得 GroupCache 能够更有效地控制缓存的重新填充(refill)逻辑。
举个例子,假设有 N 个请求需要读取同一个 Group 中的同一个键。这些 N 个请求都会被转发到同一台机器上,由一个 groupcache.Group
实例来处理。这样,只有第一个请求需要从数据库中读取数据来重新填充缓存,而其他的 N-1 个请求只需等待第一个请求完成数据库读取即可。
由于同一个键的请求都由同一个 groupcache.Group
处理,因此实现起来非常简单,只需要一个映射(map)来记录所有正在处理的请求和一个 WaitGroup
来控制线程的等待即可。这种实现可以在 GroupCache 的代码中找到,具体的代码实现可以参考 groupcache/singleflight
包中的内容。
GroupCache 架构图示
+--------------+
| |
| GroupCache | <--- 该组件负责整个缓存系统的核心功能
| |
+------+-------+
|
+-----------------+-----------------+
| |
+------v------+ +-------v------+
| | | |
| Local Cache | | Peer Cache | <--- 存储本地和远程节点的缓存数据
| | | |
+-------------+ +--------------+
- Hot Cache 的概念图示
+------------------------------------+
| GroupCache |
| |
+----------------------+-------------+
|
+------------+------------+
| |
+-------v-------+ +--------v--------+
| | | |
| Local Hot Cache| | Peer Hot Cache | <--- 存储频繁访问的热门数据,优化访问性能
| | | |
+---------------+ +-----------------+
- HTTPPool 的作用
+-------------------+
| |
| HTTP Pool | <--- 负责处理与远程节点之间的通信
| |
+--------+----------+
|
+---------------+---------------+
| |
v v
+-----------------+ +-----------------+
| | | |
| HTTP Client | | HTTP Client | <--- 与远程节点之间进行通信的客户端
| | | |
+-----------------+ +-----------------+
- HTTPPool 与 GroupCache 交互的流程图示
sql
Copy code
+----------------+ +----------------+
| | | |
| HTTP Pool | | GroupCache | <--- HTTPPool 通过调用 GroupCache 的 API 来获取缓存数据
| | | |
+-------+--------+ +-------+--------+
| |
| |
| HTTP Request |
+----------------------------------->|
| |
| |
| Get Cache Value |
|<-----------------------------------+
| |
| Cache Value |
+----------------------------------->|
| |
GroupCache系统的核心过程是这样的:
- 用户发起请求时,GroupCache 首先检查本地缓存中是否有所需数据,如果有,则直接返回给用户;如果没有,则进入下一步。
- 如果本地缓存中没有所需数据,GroupCache 会根据一致性哈希算法将请求路由到相应的远程节点(Peer),尝试从远程节点获取数据。
- 如果远程节点(Peer)上有所需数据,则将数据返回给本地节点,本地节点再将数据返回给用户,并且在本地缓存中保存一份数据副本。
- 如果远程节点(Peer)上也没有所需数据,则 GroupCache 将调用用户提供的回调函数(通常是实现了
groupcache.Getter
接口的对象)来从后端数据源加载数据,并将数据写入缓存,然后返回数据给用户。
GroupCache跟redis的区别:
特性 Redis GroupCache 数据分片与分布式存储 支持分布式存储,具有灵活的分片机制 可以将数据分片存储在多个节点上,实现可扩展性 懒加载与缓存逻辑 支持懒加载,但缓存逻辑相对简单 支持懒加载机制,允许自定义缓存逻辑 一致性哈希算法 支持一致性哈希算法进行数据分片和负载均衡 使用一致性哈希算法实现数据在集群中的分布 支持多种后端存储 作为一种存储系统,可以与其他存储系统集成 可以与各种后端存储系统集成,包括内存、磁盘等Redis 作为一个独立的缓存和存储系统,它的核心过程是:
- 用户发起请求时,Redis 首先检查内存中是否有所需数据,如果有,则直接返回给用户;如果没有,则进入下一步。
- 如果内存中没有所需数据,Redis 会根据数据的持久化配置(如 RDB 快照或 AOF 日志)来查找磁盘中是否有数据的备份,如果有,则加载数据到内存,并返回给用户。
- 如果磁盘中也没有数据的备份,则 Redis 将调用用户提供的回调函数(如果配置了),或者直接返回缓存未命中的错误给用户。
主要区别在于 GroupCache 是一个分布式缓存系统,可以将数据分片存储在多个节点上,并使用一致性哈希算法进行负载均衡和数据分布,而 Redis 是一个单机或者分布式的缓存系统,数据存储在内存中,可以选择将数据持久化到磁盘中。 GroupCache 的设计更注重于分布式缓存的高性能和可扩展性,而 Redis 则更加灵活,支持丰富的数据类型和功能,并且有着更强大的持久化和事务支持。
使用注意
-
Membership 管理:GroupCache 不负责成员管理,需要通过
HTTPPool.Set
方法更新成员列表,会触发重新分片。调用频率需要谨慎考虑,特别是在滚动更新时,可能受到调度速度的影响。 - Stateful 特性:使用 GroupCache 可能使应用变得有状态,增加了运维的难度,可能需要引入 Operator 来解决。在主机维护时需要小心,以防止重新分片对集群造成影响。
- 仅支持 Refill:GroupCache 只支持对不可变值进行缓存,即只能通过回调函数重新填充缓存。这限制了其适用场景,需确保数据的不可变性。
- Stateful 的优势:GroupCache 主要优势在于减少对数据库的读取,特别是在重新填充缓存时。使用时需考虑到这些限制,并确保系统的稳定运行