golang源码分析:groupcache(1)

2023-09-06 19:28:24 浏览数 (1)

github.com/golang/groupcache 存储的是kv结构,同是memcache作者出品.在放弃update/delete 的特性后,换来的是:Cluster 的能力,处理热点的能力。

因为groupcache只能get,不能update和delete,也不能设置过期时间,只能通过lru淘汰最近最少访问的数据;有些数据如果长时间不更改,那么可以用groupcache作为缓存;groupcache已经在dl.Google.com、Blogger、Google Code、Google Fiber、Google生产监视系统等项目中投入使用。groupcache既是服务器,也是客户端,当在本地groupcache缓存中没有查找的数据时,通过一致性哈希,查找到该key所对应的peer服务器,在通过http协议,从该peer服务器上获取所需要的数据;还有一点就是当多个客户端同时访问memcache中不存在的键时,会导致多个客户端从mysql获取数据并同时插入memcache中,而在相同情况下,groupcache只会有一个客户端从mysql获取数据,其他客户端阻塞,直到第一个客户端获取到数据之后,再返回给多个客户端。

下面我们用一个例子看下,如何使用它。

代码语言:javascript复制
package main

import (
  "context"
  "errors"
  "log"
  "net/http"
  "strconv"

  "github.com/golang/groupcache"
)

const (
  // 启动的http端口
  ServicePort = 9000
  // groupcache内部通信端口 同时修改CachePort 和ServicePort 端口,模拟启动运行多个节点
  CachePort = 8000
  // 组名称
  GroupName = "user"
)

// 模拟数据库
var UserDb = map[string]string{
  "1001": "张三",
  "1002": "李四",
  "1003": "王五",
}

// 数据不在缓存中时,加载数据
// 使用单飞,防止缓存惊群效应
func getterFunc(ctx context.Context, key string, dest groupcache.Sink) (err error) {
  log.Println("从数据库获取用户姓名,uid="   key)

  name, ok := UserDb[key]
  if !ok {
    return errors.New("uid miss")
  }

  dest.SetString(name)
  return nil
}

func startHTTP() {
  group := groupcache.NewGroup(GroupName, 1<<20, groupcache.GetterFunc(getterFunc))
  http.HandleFunc("/getName", func(writer http.ResponseWriter, request *http.Request) {
    // 数据查询
    uid := request.URL.Query().Get("uid")
    log.Println("http uid="   uid)
    var name []byte
    err := group.Get(context.Background(), uid, groupcache.AllocatingByteSliceSink(&name))
    if err != nil {
      writer.Write([]byte("404"))
      return
    }

    writer.Write([]byte("name="   string(name)))
  })

  go http.ListenAndServe(":" strconv.Itoa(ServicePort), nil)
}

func main() {
  startHTTP()

  // 启动groupcache
  localUrl := "http://127.0.0.1:"   strconv.Itoa(CachePort)
  peers := groupcache.NewHTTPPool(localUrl)
  peers.Set("http://127.0.0.1:8000", "http://127.0.0.1:8001")

  http.ListenAndServe(":" strconv.Itoa(CachePort), peers)
}

测试下

代码语言:javascript复制
% curl 'http://127.0.0.1:9000/getName?uid=1002'
name=李四%                                                                     
% curl 'http://127.0.0.1:9000/getName?uid=1001'
name=张三%                                                                   
% curl 'http://127.0.0.1:9000/getName?uid=1003'
name=王五%
代码语言:javascript复制
2023/05/14 16:58:17 http uid=1002
2023/05/14 16:58:17 从数据库获取用户姓名,uid=1002
2023/05/14 16:58:20 http uid=1001
2023/05/14 16:58:23 http uid=1003
2023/05/14 16:58:23 从数据库获取用户姓名,uid=1003

整个流程如下:

1,初始化groupCache,并指定回源函数。

2,注册peer节点

3,启动cache服务供peer查询

查询过程如下:

1,先从本地的lru cache里查询

2,如果本地查不到,到peer的本地缓存查询,peer的选取采用一致性hash算法

3,如果peer中也没有取到结果则回源查询

4,将回源查询的结果存入本地缓存

5,上述查询过程为了防止击穿,采用了singleflight组件。

总的来说,它是为了保证可用性,放弃了一致性,所以不支持主动设置缓存、缓存过期、和缓存内容的修改。

适合不修改的内容的缓存。

0 人点赞