[Golang] groupcache的簡單範例


大概介紹

其實關於groupcache的介紹網上非常的多,搜索出來清一色都是說的介紹,當然也有配圖如何部署,但是文字與配圖完全不在一個時空,圖也是copy國外的一篇博客。它不像其它的一些緩存數據庫有個服務端,需要客戶端去連接,文檔中明確說明了它就是一個程序庫,所以沒有服務端與客戶端這麼一說,換句話說,它本沒有服務端,又或者是人人都是服務端,食神的感覺油然而生。

主要代碼結構

它的代碼結構也比較清晰,代碼量也不是很大,很適合大家去閱讀學習。主要分為了consistenthash(提供一致性哈希算法的支持),lru(提供了LRU方式清楚緩存的算法),singleflight(保證了多次相同請求只去獲取值一次,減少了資源消耗),還有一些源文件:byteview.go 提供類似於一個數據的容器,http.go提供不同地址間的緩存的溝通的實現,peers.go節點的定義,sinks.go感覺就是一個開闢空間給容器,並和容器交互的一個中間人,groupcache.go整個源碼裡的大當家,其它人都是為它服務的。

一個測試例子

在使用這個緩存的時候比較重要的幾方面也是我之前犯錯的幾個地方

  • 需要監聽兩個地方,一個是監聽節點,一個是監聽請求
  • 在批量設置節點地址的時候需要在地址前面加上http://,因為一開始我沒有加上去,所以緩存信息一直不能再節點之間交互
  • 啟動的節點地址要與設置的節點地址一致:數量和地址值。因為我每次少一個就無法在節點間交互。

以上的一些信息可能也有不對的地方,只是我個人的一個測試後的結果。

代碼部分

package main

        import (
            "flag"
            "fmt"
            "github.com/golang/groupcache"
            "io/ioutil"
            "log"
            "net/http"
            "os"
            "strconv"
            "strings"
        )
        
        var (
            // peers_addrs = []string{"127.0.0.1:8001", "127.0.0.1:8002", "127.0.0.1:8003"}
            //rpc_addrs = []string{"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}
            index = flag.Int("index", 0, "peer index")
        )
        
        func main() {
            flag.Parse()
            peers_addrs := make([]string, 3)
            rpc_addrs := make([]string, 3)
            if len(os.Args) > 0 {
                for i := 1; i < 4; i++ {
                    peers_addrs[i-1] = os.Args[i]
                    rpcaddr := strings.Split(os.Args[i], ":")[1]
                    port, _ := strconv.Atoi(rpcaddr)
                    rpc_addrs[i-1] = ":" + strconv.Itoa(port+1000)
                }
            }
            if *index < 0 || *index >= len(peers_addrs) {
                fmt.Printf("peer_index %d not invalid\n", *index)
                os.Exit(1)
            }
            peers := groupcache.NewHTTPPool(addrToURL(peers_addrs[*index]))
            var stringcache = groupcache.NewGroup("SlowDBCache", 64<<20, groupcache.GetterFunc(
                func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
                    result, err := ioutil.ReadFile(key)
                    if err != nil {
                        log.Fatal(err)
                        return err
                    }
                    fmt.Printf("asking for %s from dbserver\n", key)
                    dest.SetBytes([]byte(result))
                    return nil
                }))
        
            peers.Set(addrsToURLs(peers_addrs)...)
        
            http.HandleFunc("/zk", func(rw http.ResponseWriter, r *http.Request) {
                log.Println(r.URL.Query().Get("key"))
                var data []byte
                k := r.URL.Query().Get("key")
                fmt.Printf("cli asked for %s from groupcache\n", k)
                stringcache.Get(nil, k, groupcache.AllocatingByteSliceSink(&data))
                rw.Write([]byte(data))
            })
            go http.ListenAndServe(rpc_addrs[*index], nil)
            rpcaddr := strings.Split(os.Args[1], ":")[1]
            log.Fatal(http.ListenAndServe(":"+rpcaddr, peers))
        }
        
        func addrToURL(addr string) string {
            return "http://" + addr
        }
        
        func addrsToURLs(addrs []string) []string {
            result := make([]string, 0)
            for _, addr := range addrs {
                result = append(result, addrToURL(addr))
            }
            return result
        }

執行方式:./demo 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003

在上面的命令中我們就啟動了一個節點,並且設置節點地址個數為3個,這裡由於我是默認的index為0,所以在啟動其它節點的時候變換下地址的順序,使第一個地址三次都不一樣就好了。(抱歉,這裡實現的不是很好),這樣同樣方法啟動三個節點。

打開瀏覽器訪問127.0.0.1:9001?key=1.txt,這裡1.txt是需要獲取數據的之際地方,類似於實際中的數據庫,我這裡直接用一個文件代替了。

運行結果:

  • 當我訪問:9001時結果

 

在上面圖中,我們像:8001這個節點請求數據,它沒有緩存數據,然後會去其它節點中尋找這個key的數據,當都沒有的時候就會去數據庫中獲取(這裡我們是一個文件),所以會出現在:8003這個節點中獲取數據庫數據的操作。

  • 然後我訪問:9002

根據上圖看到,第一個地址為:8002這個節點直接從緩存裡面取值,但是在請求之前這個節點並沒有緩存數據,這個也同樣是節點間的交互完成的。

我在本地開啟了兩個虛擬機,在同一個局域網中,測試也能得出相同的結果。這裡結果就不再貼上了,當然運行的時候節點地址要做相應的變動,根據每個機子的局域網中的地址。