cache教程 7.使用 protobuf 通信

0.对原教程的一些见解

原教程的作者的写法是抽象接口出来,而一般人或小白比较难能想到。原教程是修改了Get方法,而我的做法是修改getFromPeer,在其进行proto解码。

原教程的protbuf文件中写上service,而这是用在RPC中的,也可能会容易误导读者认为该章节使用了RPC的。而这一章节没有使用RPC的,只是使用protobuf来进行编解码的

有不同的见解,欢迎在评论区交流讨论。

1 为什么要使用 protobuf

protobuf 即 Protocol Buffers,Google 开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。protobuf 以二进制方式存储,占用空间小。

protobuf 广泛地应用于远程过程调用(RPC) 的二进制传输,使用 protobuf 的目的非常简单,为了获得更高的性能。传输前使用 protobuf 编码(序列化),接收方再进行解码(反序列化),可以显著地降低二进制传输的大小。另外一方面,protobuf 可非常适合传输结构化数据,便于通信字段的扩展。

这一节使用 protobuf 进行节点间通信,编码报文,提高效率和性能。

protobuf 的安装可参考https://subingwen.cn/cpp/protobuf/。

2 使用 protobuf 通信

编写protpbuf文件

syntax="proto3";

package geecachepb;

// ./是go文件生成的路径,geecachepb是该go文件的包名
option go_package = "./;geecachepb";

message Request{
    string group=1;
    string key=2;
}

message Response{
    bytes value=1;
}

//service是RPC的,这里没有使用,注释掉没有影响
//定义服务(Services),如果消息类型是用来远程通信的(RPC),在 .proto文件中定义RPC服务接口
// service GroupCache{
//     rpc Get(Request) returns(Response);
// }
  • Request 包含 2 个字段, group 和 key, 和方法func (h *httpGetter) Get(group string, key string)吻合。
  • Response 包含 1 个字段,bytes,类型为 byte 数组,与之前要返回的数据吻合。

 生成对应的go文件:到该protobuf的文件目录执行命令。geecachepb.pb.go就是其生成的go文件。

protoc --go_out=. *.proto
ls
geecachepb.pb.go  geecachepb.proto

需要使用到protobuf的地方

回顾下多节点访问的流程

cache教程 7.使用 protobuf 通信_第1张图片

 需要访问远程节点的话,会走到ServHTTP方法中,该方法中,会获取到value,之后把value返回给客户端。那在返回之前,我们就使用protobuf进行编码,简单理解成编码成二进制数据,再发给给客户端。那来修改下这里的代码。

func (pool *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    //...........
	view, err := group.Get(parts[1])

    //上面的是没有修改的,下面的是添加的,使用proto.Marshal进行编码,把编码后的数据放到body中
	body, err := proto.Marshal(&geecachepb.Response{Value: view.ByteSlice()})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(body)
}

之后回到节点作为客户端进行访问的Get()方法。

func (h *httpGetter) Get(group string, key string) ([]byte, error) {
	//QueryEscape 对字符串进行转义,以便可以将其安全地放置在 URL 查询中。
	u := fmt.Sprintf("%v%v/%v", h.baseURL,url.QueryEscape(group),url.QueryEscape(key))

	res, err := http.Get(u)

    //省略了一些在这章节不重要的内容......
	bytes, err := io.ReadAll(res.Body)
	return bytes, nil
}

func (g *Group) getFromPeer(peer *httpGetter, key string) (ByteView, error) {
	bytes, err := peer.Get(g.name, key)    //该函数就是前面的httpGetter.Get方法
	if err != nil {
		return ByteView{}, err
	}
	return ByteView{b: bytes}, nil
}

在io.ReadAll(res.Body)中,我们就得到缓存服务器返回的数据了嘛,而这时数据是经过编码的,需要进行解码。那在Get方法中,可以不用进行解码,回到getFromPeer再解码就行,那修改getFromPeer方法即可。

func (g *Group) getFromPeer(peer *httpGetter, key string) (ByteView, error) {
	bytes, err := peer.Get(g.name, key)
	if err == nil {
		//没有错误,那使用protobuf解码
		res := &geecachepb.Response{}
		if err := proto.Unmarshal(bytes, res); err == nil {
			return ByteView{b: res.Value}, nil
		}
	}

	return ByteView{}, err
}

这里的修改和原教程的有所差别,原教程是修改了Get()方法的,其修改如下。

//原教程的做法
func (h *httpGetter) Get(in *pb.Request, out *pb.Response) error {
	u := fmt.Sprintf(
		"%v%v/%v",
		h.baseURL,
		url.QueryEscape(in.GetGroup()),
		url.QueryEscape(in.GetKey()),
	)
    res, err := http.Get(u)
	// ...
	if err = proto.Unmarshal(bytes, out); err != nil {
		return fmt.Errorf("decoding response body: %v", err)
	}

	return nil
}

3.总结

这章节我的做法是:

  • ServeHTTP() 中使用 proto.Marshal() 编码 HTTP 响应。
  • getFromPeer() 中使用 proto.Unmarshal() 解码 HTTP 响应。

至此,我们已经将 HTTP 通信的中间载体替换成了 protobuf。运行 run.sh 即可以测试 GeeCache 能否正常工作。

完整代码:https://github.com/liwook/Go-projects/tree/main/go-cache/7-proto-buf

你可能感兴趣的:(#,Go实现分布式缓存,网络,go,protobuf)