掘金 后端 ( ) • 2024-05-07 18:01

在构建分布式系统时,缓存是提高性能和减少负载的关键组件之一。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 具有以下几个主要特性:

  1. 数据分片与分布式存储:GroupCache 可以将数据分片存储在多个节点上,以实现高性能和可扩展性。
  2. 懒加载与缓存逻辑:它支持懒加载机制,只有在需要时才会从数据源加载数据,并且可以根据具体应用的需求进行自定义的缓存逻辑。
  3. 一致性哈希算法:GroupCache 使用一致性哈希算法来确定数据在集群中的分布,从而保证了负载均衡和高效的缓存访问。
  4. 支持多种后端存储: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  |  <--- 存储本地和远程节点的缓存数据
                  |             |                    |              |
                  +-------------+                    +--------------+
  1. Hot Cache 的概念图示
    +------------------------------------+
    |           GroupCache                |
    |                                    |
    +----------------------+-------------+
                           |
              +------------+------------+
              |                         |
      +-------v-------+        +--------v--------+
      |               |        |                 |
      | Local Hot Cache|        |  Peer Hot Cache |  <--- 存储频繁访问的热门数据,优化访问性能
      |               |        |                 |
      +---------------+        +-----------------+
  1. HTTPPool 的作用
                   +-------------------+
                   |                   |
                   |    HTTP Pool      |  <--- 负责处理与远程节点之间的通信
                   |                   |
                   +--------+----------+
                            |
            +---------------+---------------+
            |                               |
            v                               v
+-----------------+             +-----------------+
|                 |             |                 |
|  HTTP Client    |             |  HTTP Client    |  <--- 与远程节点之间进行通信的客户端
|                 |             |                 |
+-----------------+             +-----------------+
  1. HTTPPool 与 GroupCache 交互的流程图示
sql
Copy code
    +----------------+                   +----------------+
    |                |                   |                |
    |   HTTP Pool    |                   |   GroupCache   |  <--- HTTPPool 通过调用 GroupCache 的 API 来获取缓存数据
    |                |                   |                |
    +-------+--------+                   +-------+--------+
            |                                    |
            |                                    |
            |        HTTP Request                |
            +----------------------------------->|
            |                                    |
            |                                    |
            |       Get Cache Value              |
            |<-----------------------------------+
            |                                    |
            |       Cache Value                 |
            +----------------------------------->|
            |                                    |

GroupCache系统的核心过程是这样的:

  1. 用户发起请求时,GroupCache 首先检查本地缓存中是否有所需数据,如果有,则直接返回给用户;如果没有,则进入下一步。
  2. 如果本地缓存中没有所需数据,GroupCache 会根据一致性哈希算法将请求路由到相应的远程节点(Peer),尝试从远程节点获取数据。
  3. 如果远程节点(Peer)上有所需数据,则将数据返回给本地节点,本地节点再将数据返回给用户,并且在本地缓存中保存一份数据副本。
  4. 如果远程节点(Peer)上也没有所需数据,则 GroupCache 将调用用户提供的回调函数(通常是实现了 groupcache.Getter 接口的对象)来从后端数据源加载数据,并将数据写入缓存,然后返回数据给用户。

GroupCache跟redis的区别:

特性 Redis GroupCache 数据分片与分布式存储 支持分布式存储,具有灵活的分片机制 可以将数据分片存储在多个节点上,实现可扩展性 懒加载与缓存逻辑 支持懒加载,但缓存逻辑相对简单 支持懒加载机制,允许自定义缓存逻辑 一致性哈希算法 支持一致性哈希算法进行数据分片和负载均衡 使用一致性哈希算法实现数据在集群中的分布 支持多种后端存储 作为一种存储系统,可以与其他存储系统集成 可以与各种后端存储系统集成,包括内存、磁盘等

Redis 作为一个独立的缓存和存储系统,它的核心过程是:

  1. 用户发起请求时,Redis 首先检查内存中是否有所需数据,如果有,则直接返回给用户;如果没有,则进入下一步。
  2. 如果内存中没有所需数据,Redis 会根据数据的持久化配置(如 RDB 快照或 AOF 日志)来查找磁盘中是否有数据的备份,如果有,则加载数据到内存,并返回给用户。
  3. 如果磁盘中也没有数据的备份,则 Redis 将调用用户提供的回调函数(如果配置了),或者直接返回缓存未命中的错误给用户。

主要区别在于 GroupCache 是一个分布式缓存系统,可以将数据分片存储在多个节点上,并使用一致性哈希算法进行负载均衡和数据分布,而 Redis 是一个单机或者分布式的缓存系统,数据存储在内存中,可以选择将数据持久化到磁盘中。 GroupCache 的设计更注重于分布式缓存的高性能和可扩展性,而 Redis 则更加灵活,支持丰富的数据类型和功能,并且有着更强大的持久化和事务支持。

使用注意

  1. Membership 管理:GroupCache 不负责成员管理,需要通过 HTTPPool.Set 方法更新成员列表,会触发重新分片。调用频率需要谨慎考虑,特别是在滚动更新时,可能受到调度速度的影响。
  2. Stateful 特性:使用 GroupCache 可能使应用变得有状态,增加了运维的难度,可能需要引入 Operator 来解决。在主机维护时需要小心,以防止重新分片对集群造成影响。
  3. 仅支持 Refill:GroupCache 只支持对不可变值进行缓存,即只能通过回调函数重新填充缓存。这限制了其适用场景,需确保数据的不可变性。
  4. Stateful 的优势:GroupCache 主要优势在于减少对数据库的读取,特别是在重新填充缓存时。使用时需考虑到这些限制,并确保系统的稳定运行