介绍一下groupcache实现和使用

一.groupcache介绍
memcached作者Brad Fitzpatrick用Go开发了前者的替代版,现已在Google多个生产环境(如dl.google.com)中投入使用。本文粗略介绍一下groupcache的实现方式。
memcached的业务架构如下图,memcache的分布式不是服务器端实现,而是通过客户端实现;是客户端根据key自己计算决定memcached实例 。



groupcahe的业务结构如下图,key的存储采用分布式方式,key通过一致性哈希分散在各个实例中,通过任意一个实例皆可得到数据。



二.groupcahe数据结构

1.byteview
byteview对字符串数组和string进行封装,存储何种形式的字符串对用户透明,定义如下

type struct {
     b []byte
     s string
}
 


具体的操作函数的实现方式如下
func at(i){
     if b!=nil 
         return b[i]
     return s[i]
}
 


2.singleflight

singleflight保证“同时”多个同参数的get请求只执行一次操作功能,整个流程如下




当Thread3在时间00:01执行Get("太阳")时候,在系统中以key为“太阳”标识正在翻译“太阳”,整个翻译过程需要8秒钟。
当Thread1和Thread2分别与00:02,00:03提交执行Get("太阳")时候,发现系统中存在以key为“太阳”标识的翻译执行,进程阻塞等待Thread3的翻译结果。
在时间00:09,Thread3得到“太阳”的翻译结果,返回给客户,此时Thread1和Thread2执行读取翻译结果返回给客户。
整个过程中,不仅减少Thread1和Thread2的得到结果时间,也减少读取数据库的次数,节约系统资源。

3. consistenthash
consistenthash提供一致性hash功能,将key按照一致性hash分布在各个实例中,主要作用是实例加入离开时不会导致映射关系的重大变化。
consistenthash 由数组和一个存储实例标识和标识key的hashmap实现,结构如下,



consistenthash会将实例标识复制replicas(可以设置)份放到hashmap结构中,选择数据key的时候,选择大于实例key的hash值最近的一个。
eg:如果 hash("太阳")的值为24,选择整数数组中的30(大于24最近的key)作为存储实例的key,从hashmap找到30的实例为192.168.0.1,判断192.168.0.1作为“太阳”的存储实例。

4.lru

lru提供缓存的清除算法,groupcache实现方式无特别之处,再次略过不说。

5.group
group是key-value的容器,每个group都有一个名字,相当于一批key-value的家族标识。
当用户访问某个key时,group现在本地内存中维持的hashmap中查找,如果没有则从远端的peer或者本地的文件系统等地方加载。



Getter定了如何从本地读取数据,这部分需要使用使用groupcache的开发者自己提供实现,group 函数getLocally将会调用。
PickPeer会按照一致性hash选择peer,发起http请求拉取数据,这便是group函数getFromPeer实现。
在getLocally和getFromPeer调用的过程中,用singleflight提供多次同参get请求只执行一次操作。
maincahe 是本地读取的数据的存储容器;hotcahe是从远端peer读取的热数据的存储,现在的热数据完全凭运气,从远端读取的数据有10%的概率会称为热数据。
maincahe和hotcahe的内存用量总和高于此group的cacheBytes限制时,便启用lru进行数据清理。
stats是指标统计,如Gets便是用户请求本实例的计次,cacheHits是命中本地内存maincahe和hotcahe的计次。
group通过key对应的value的伪代码如下:
func get(key){
        /*从本地内存中读取*/
        if  maincache.has(key)
               return   maincache[key]
        if  hotcahe.has(key)
              return   hotcahe [key]
        /*从远端peer读取*/
        value :=  PeerPicker.PickPeer(key).get(key)
         if value !=nil
               if(rand()%10 == 1)
                    hotcahe[key] = value
               return value
         /*从本地文件等系统读取*/
         value:= getLocally(key)
          if value !=nil
               maincache[key] = value
        return value
}
 



6.httppool
httppool便是各个peer通讯的封装,开启通讯http,group的getFromPeer便是调用相对应peer的httpool提供的服务。
httppool同时保存了所有的远端peer实例的请求地址,实现pickPeer安装一致性hash取得某key对应的远端peer实例的地址。
三. groupcahe使用
groupcahe是个库,不是一个程序,如果需要使用,需要自己写部分逻辑,在此提供一个简单的例子
package main
import (
    "fmt"
    "github.com/groupcache"
    "io/ioutil"
    "log"
    "net/http"
    "os"
)
var (
    peers_addrs = []string{"http://127.0.0.1:8001", "http://127.0.0.1:8002", "http://127.0.0.1:8003"}
)
func main() {
    if len(os.Args) != 3 {
        fmt.Println("\r\n Usage local_addr \t\n local_addr must in(127.0.0.1:8001,localhost:8002,127.0.0.1:8003)\r\n")
        os.Exit(1)
    }
    local_addr := os.Args[1]
    peers := groupcache.NewHTTPPool("http://" + local_addr)
    peers.Set(peers_addrs...)
    var image_cache = groupcache.NewGroup("image", 8<<30, groupcache.GetterFunc(
        func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
            result, err := ioutil.ReadFile(key)
            if err != nil {
                fmt.Printf("read file error %s.\n", err.Error())
                return nil
            }
            fmt.Printf("asking for %s from local file system\n", key)
            dest.SetBytes([]byte(result))
            return nil
        }))
    http.HandleFunc("/image", func(rw http.ResponseWriter, r *http.Request) {
        var data []byte
        k := r.URL.Query().Get("id")
        fmt.Printf("user get %s from groupcache\n", k)
        image_cache.Get(nil, k, groupcache.AllocatingByteSliceSink(&data))
        rw.Write([]byte(data))
    })
    log.Fatal(http.ListenAndServe(local_addr, nil))
}
 


使用的时候
src.exe 127.0.0.1:8001   src.exe 127.0.0.1:8002   src.exe 127.0.0.1:8003创建3个peer实例即可
客户端访问http://127.0.0.1:8001/image?id=configure.ini即可得到数据



对源码进行修改打印出来的日志,各位看到的并不是这样,并没有这些日志。
四.最后说几点
groupcahe不提供过期、删除等操作,所有groupcahe只适合与静态不变的数据,不能用于取代memcahe。
一些地方实现比较粗略,如定义热点数据等,现在采用10%随机并不合理;peer节点的删除增加也没有处理。

猜你喜欢

转载自orangeholic.iteye.com/blog/2257245