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