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的地方
回顾下多节点访问的流程
?需要访问远程节点的话,会走到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
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!