groupcache 是一個分布式緩存 go 語言庫,支持多節(jié)點互備熱數(shù)據(jù)滞造,有良好的穩(wěn)定性和較高的并發(fā)性。
這里有個簡單的應(yīng)用場景:
當(dāng) GET foo 打到 groupcache-1 后:
- groupcache-1 先看看自己的 cache 里有沒有 foo,有的話直接返回
- 要是沒有露筒,看看這個請求歸不歸自己管,若是敌卓,去 DataSever 獲取慎式,否則問 group-2(假設(shè) foo 歸 -2管) 要數(shù)據(jù),成功返回后 groupcache-1 本地也緩存一份
- 在 2 過程中,所有后來打到 groupcache-1 的 GET foo 都會阻塞瘪吏,直到第一個請求返回
問題來了癣防,如何判斷 foo 由誰來處理?
如上圖掌眠,利用hash將所有節(jié)點平均打散到全集蕾盯,然后當(dāng) foo 進來后用相同hash算法就會得到一個唯值,落在那個區(qū)間就屬于那個節(jié)點蓝丙,要保證一致性级遭。
因為 foo 和某資源一一對應(yīng),這就要求 groupcache 只有 get 沒有 update渺尘。
一個簡單的HTTP groupcache Server:
package main
import "github.com/golang/groupcache"
import "github.com/gin-gonic/gin"
import "net/http"
import "time"
import "bytes"
// 虛擬文件生成方法
func generateThumbnail(fileName string) []byte {
return []byte("fake file")
}
func main() {
// 本機 ip
me := "http://10.0.0.1"
peers := groupcache.NewHTTPPool(me)
// 設(shè)置互備的 node
peers.Set("http://10.0.0.1", "http://10.0.0.2", "http://10.0.0.3")
// 創(chuàng)建一個 cache group挫鸽,最大緩存為64M
var thumbNails = groupcache.NewGroup("thumbnail", 64<<20, groupcache.GetterFunc(
func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
fileName := key
dest.SetBytes(generateThumbnail(fileName))
return nil
}))
// 設(shè)置 thumbnail 的 peers
groupcache.RegisterPeerPicker(func() groupcache.PeerPicker {
return peers
})
// 起一個 HTTP server
server := gin.Default()
server.GET("/files/:name", gin.HandlerFunc(
func(ctx *gin.Context) {
var data []byte
name := ctx.Param("name")
// 獲取緩存
err := thumbNails.Get(ctx, name, groupcache.AllocatingByteSliceSink(&data))
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"mesage": "file not found"})
return
}
// 返回給客戶端
http.ServeContent(ctx.Writer, ctx.Request, name, time.Now(), bytes.NewReader(data))
}))
server.Run("10.0.0.1:80")
}
Group
groupcache.NewGroup(addr string)
Group 代表一個 cache資源庫
type Group struct {
name string
getter Getter // cache 沒有命中,從數(shù)據(jù)庫獲取
peersOnce sync.Once
peers PeerPicker // peer 節(jié)點調(diào)度器
cacheBytes int64 // 最大cache字節(jié)數(shù)
mainCache cache // 此節(jié)點緩存
hotCache cache // 其他節(jié)點緩存
loadGroup flightGroup // 請求并發(fā)控制器
Stats Stats // 統(tǒng)計數(shù)據(jù)
}
對于一個 Group 來說鸥跟,會緩存自己節(jié)點的數(shù)據(jù)和訪問比較頻繁的 peer節(jié)點 的數(shù)據(jù)丢郊,用LRU算法控制緩存。
當(dāng) cache 沒有命中的時候医咨,首先看看這個請求歸不歸該節(jié)點管枫匾,若是就是調(diào)用 getter:
Getter
type Getter interface {
Get(ctx Context, key string, dest Sink) error
}
對于一個 cache 來說,他不知道如何拉取需要緩存的數(shù)據(jù)拟淮,所以他說啊干茉,你要是想緩存新的東西,就得有個 type 實現(xiàn) Getter 接口很泊,然后給我一個 Getter 對象角虫,這樣cache沒有命中的時候我能靠這個對象拉取數(shù)據(jù)。
這個 Getter 類似于 http.Handler撑蚌,抽象拉取要緩存的數(shù)據(jù)這個行為上遥,Context(interface{}) 是操作的附帶信息,key 請求的 id争涌,Sink 類似于 http.ResponseWriter粉楚,抽象了數(shù)據(jù)載體的行為:
Sink
type Sink interface {
// SetString 寫入 string
SetString(s string) error
// SetBytes 寫入字節(jié)數(shù)組,調(diào)用者會保留 v 引用
SetBytes(v []byte) error
// SetProto 寫入proto.Message亮垫,調(diào)用者會保留 m 應(yīng)用
SetProto(m proto.Message) error
// ...
}
groupcache 提供了一些常用的 Sink 如 StringSink模软,BytesSliceSink 和 ProtoSink,這個 proto 是github.com/golang/protobuf/proto
饮潦,groupcache 規(guī)定內(nèi)部 peer 節(jié)點之間數(shù)據(jù)通信格式使用 google/protobuf燃异,為了抽象 peer 節(jié)點,定義了 ProtoGetter:
ProtoGetter
type ProtoGetter interface {
Get(context Context, in *pb.GetRequest, out *pb.GetResponse) error
}
pb.GetRequest
和 pb.GetResponse
定義了請求和響應(yīng) struct继蜡,這個抽象可以分離底層傳輸方式回俐。
當(dāng)然還需要對節(jié)點調(diào)度器抽象逛腿,PeerPicker:
PeerPicker
type PeerPicker interface {
// PickPeer 根據(jù) key 返回應(yīng)該處理這個 key 的節(jié)點
// ok 為 true 代表找到了節(jié)點
// nil, false 代表當(dāng)前節(jié)點就是 key 的處理器
PickPeer(key string) (peer ProtoGetter, ok bool)
}
調(diào)度器主要負(fù)責(zé)根據(jù)管理 key 和節(jié)點的一致性映射。
groupcache 實現(xiàn)了一個 HTTP 的 PeerPicker仅颇,HTTPPool单默。
至此,groupcache 通過 Getter忘瓦,PeerPicker搁廓,ProtoGetter 三個 interface 定義了cache,節(jié)點和調(diào)度器之間的連接方式耕皮,可以有效地控制耦合度境蜕,也提供了比較大的靈活性。