golang 源碼分析:groupcache(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 獲取數據,其他客戶端阻塞,直到第一個客戶端獲取到數據之後,再返回給多個客戶端。

        下面我們用一個例子看下,如何使用它。

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)
}

測試下

% 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=王五%
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 組件。

    總的來說,它是爲了保證可用性,放棄了一致性,所以不支持主動設置緩存、緩存過期、和緩存內容的修改。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4ISO1LMRl8BCi5cGnI_Jhw