我们一起来探寻rpcx框架,本系列会详细讲解rpcx,尽量覆盖它的所有代码,看看这款优秀的rpc框架是如何实现的。
什么是序列化
编码/解码对应也就是就是序列化/反序列化的过程,我们知道,网络只能传输字节。所以在rpc中,如果客户端要传输一个Person
对象给服务端,客户端必须先将**Person
对象转换成转换成 []byte**(这就是序列化),然后发起rpc调用,服务端在接收到请求后,将接收到的[]byte 还原成成Person
对象(这就是反序列化)。
type Person struct{
Age uint8
Name string
}
常用的序列化协议
json
简洁,易读,层次清晰。格式与编程语言无关,是一种轻量级的数据交换格式。不足就是空间开销比较大,且性能开销比较大
Thrift
Thrift并不仅仅是序列化协议,而是一个RPC框架。具有体积小, 速度快,支持多语言,数据类型丰富的优点,但作为RPC框架,性能不太理想
Avro
属于Hadoop
的子项目,是Hive、Pig和MapReduce的持久化的存储格式。提供json格式或者Binary两种个格式。其中Binary的空间开销和解析性能方面非常不错
Hessian
一款支持多种语言进行序列化操作的框架技术,同时在进行序列化之后产生的码流也较小,处理数据的性能方面远超于java内置的jdk序列化方式。具备非常好的兼容性。
Protobuf
谷歌开源,描述文件:.proto。优点:空间小,性能高,跨语言,且按照特定的字段顺序可以实现协议的前向兼容。
Message pack
内置的LZ4压缩功能,可以实现超快速序列化,占用空间小,支持多语言,按照特定顺序也可以实现前向兼容。这个是rpcx默认的序列化选择
关于序列化的协议很多,这里就不一一列举了,我们甚至可以定义自己的协议,前提是序列化和反序列化能匹配上。当然,我们不会这么做,已有的协议已经够好了,那选择协议的时候有几个非常重要的维度:是否支持多语言,性能开销如何,体积大小,是否向前兼容?
进入正题,我们来学习rpcx的序列化
源码
先看rpcx支持哪些序列化方式:
var (
// Codecs are codecs supported by rpcx. You can add customized codecs in Codecs.
Codecs = map[protocol.SerializeType]codec.Codec{
//用户自己实现与[]byte直接的转换
protocol.SerializeNone: &codec.ByteCodec{},
//json
protocol.JSON: &codec.JSONCodec{},
//protobuf:github.com/gogo/protobuf 使用的并非官方库
protocol.ProtoBuffer: &codec.PBCodec{},
//msgpack
protocol.MsgPack: &codec.MsgpackCodec{},
//thrift
protocol.Thrift: &codec.ThriftCodec{},
}
)
默认使用MsgPack
,这个从默认的配置可以看到
var DefaultOption = Option{
Retries: 3,
RPCPath: share.DefaultRPCPath,
ConnectTimeout: 10 * time.Second,
Breaker: CircuitBreaker,
SerializeType: protocol.MsgPack,//默认MsgPack
CompressType: protocol.None,
}
修改默认配置,可以通过传入Option参数,每种序列化方式,我都会举一个例子
func NewXClient(servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option)
我们先来看编码器的接口定义,我们也可以通过实现Codec接口,来定义自己的编码解码器。
type Codec interface {
//序列化(编码)
Encode(i interface{}) ([]byte, error)
//反序列化(解码)
Decode(data []byte, i interface{}) error
}
接下里,我们开始介绍每种编码解码器
JSON
这个直接调用json库即可完成
// Encode encodes an object into slice of bytes.
func (c JSONCodec) Encode(i interface{}) ([]byte, error) {
return json.Marshal(i)
}
// Decode decodes an object from slice of bytes.
func (c JSONCodec) Decode(data []byte, i interface{}) error {
d := json.NewDecoder(bytes.NewBuffer(data))
d.UseNumber()
return d.Decode(i)
}
需要注意的是:解码使用了d.UseNumber()
:这个方法的作用是告诉反序列化JSON的数字类型的时候,不要直接转换成float,而是转换成json.Number
类型,用户可以根据自己的需求转换成float类型,下面是json.Number
的定义(前提字段是interface{}类型),之所以单拎出来讲,是担心你们会踩坑
type Number string
// String returns the literal text of the number.
func (n Number) String() string { return string(n) }
// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
return strconv.ParseFloat(string(n), 64)
}
// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64)
}
使用例子
option := client.DefaultOption
option.SerializeType = protocol.JSON
xclient := client.NewXClient("Arith", client.Failtry, client.ConsistentHash, d, option)
Protobuf
直接上代码,对protobuf api
不熟悉的可以参考
官方文档:https://developers.google.com/protocol-buffers/docs/proto3,
也可以参考译文:https://colobu.com/2017/03/16/Protobuf3-language-guide
// PBCodec uses protobuf marshaler and unmarshaler.
type PBCodec struct{}
// Encode encodes an object into slice of bytes.
func (c PBCodec) Encode(i interface{}) ([]byte, error) {
if m, ok := i.(proto.Marshaler); ok {
return m.Marshal()
}
if m, ok := i.(pb.Message); ok {
return pb.Marshal(m)
}
return nil, fmt.Errorf("%T is not a proto.Marshaler", i)
}
// Decode decodes an object from slice of bytes.
func (c PBCodec) Decode(data []byte, i interface{}) error {
if m, ok := i.(proto.Unmarshaler); ok {
return m.Unmarshal(data)
}
if m, ok := i.(pb.Message); ok {
return pb.Unmarshal(data, m)
}
return fmt.Errorf("%T is not a proto.Unmarshaler", i)
}
在前面讲过,作者选用的protobuf
并非官方库:https://github.com/golang/protobuf,而是:https://github.com/gogo/protobuf。从这些细节方面,就可以看出作者对性能的执着(更进一步可以看作者的博客,很多文章都是关于性能方面的,博客地址:https://colobu.com/)
如果对序列化框架的性能感兴趣的话,可以参考:https://github.com/alecthomas/go_serialization_benchmarks,也可以只看结论:
Recommendation
If performance, correctness and interoperability are the most important factors, gogoprotobuf is currently the best choice. It does require a pre-processing step (eg. via Go 1.4's "go generate" command).
But as always, make your own choice based on your requirements.
使用例子
1、先新建一个arith_service.proto文件
// protoc --gogofaster_out=. arith_service.proto
syntax = "proto3";
package pb;
message ProtoArgs {
int32 A = 1;
int32 B = 2;
}
message ProtoReply {
int32 C = 1;
}
2、编译arith_service.proto文件,在编译之前需要先安装protoc
库
安装
protoc
在 https://github.com/protocolbuffers/protobuf/releases 下载对应自己系统的文件,如果是window,则可以直接下载可以执行包,其他则需要先编译。
下载完成后,将protoc.exe
添加到环境变量
中,然后下载gogofaster
go get github.com/gogo/protobuf/protoc-gen-gogofaster
编译:在
arith_service.proto
所在的文件目录
protoc --gogofaster_out=. arith_service.proto
3、编写例子
这里给出一个完整的例子,文件的目录结构(包含了上面的proto文件和编译生成的go文件)
客户端 client.go
package main
import (
"context"
"flag"
"github.com/rpcxio/rpcx-examples/codec/protobuf/pb"
"github.com/smallnest/rpcx/client"
"github.com/smallnest/rpcx/protocol"
"log"
)
var (
addr = flag.String("addr", "localhost:8972", "server address")
)
func main() {
flag.Parse()
// register customized codec
option := client.DefaultOption
option.SerializeType = protocol.ProtoBuffer
d := client.NewPeer2PeerDiscovery("tcp@"+*addr, "")
xclient := client.NewXClient("Arith", client.Failtry, client.RandomSelect, d, option)
defer xclient.Close()
args := &pb.ProtoArgs{
A: 10,
B: 20,
}
reply := &pb.ProtoReply{}
err := xclient.Call(context.Background(), "Mul", args, reply)
if err != nil {
log.Fatalf("failed to call: %v", err)
}
log.Printf("%d * %d = %d", args.A, args.B, reply.C)
}
服务端 server.go
package main
import (
"context"
"flag"
"fmt"
"github.com/rpcxio/rpcx-examples/codec/protobuf/pb"
"github.com/smallnest/rpcx/server"
)
var (
addr = flag.String("addr", "localhost:8972", "server address")
)
type Arith int
func (t *Arith) Mul(ctx context.Context, args *pb.ProtoArgs, reply *pb.ProtoReply) error {
reply.C = args.A * args.B
fmt.Printf("call: %d * %d = %d\n", args.A, args.B, reply.C)
return nil
}
func main() {
flag.Parse()
s := server.NewServer()
//s.RegisterName("Arith", new(example.Arith), "")
s.Register(new(Arith), "")
s.Serve("tcp", *addr)
}
这里用了较多的篇幅讲解protobuf,主要基于它应用很广,几乎是rpc框架必备的协议,所以希望读者能掌握它
MsgPack
rpcx默认的协议,是一款高性能的序列化共工具,支持多语言:https://msgpack.org/index.html
// MsgpackCodec uses messagepack marshaler and unmarshaler.
type MsgpackCodec struct{}
// Encode encodes an object into slice of bytes.
func (c MsgpackCodec) Encode(i interface{}) ([]byte, error) {
var buf bytes.Buffer
enc := msgpack.NewEncoder(&buf)
// enc.UseJSONTag(true)
err := enc.Encode(i)
return buf.Bytes(), err
}
// Decode decodes an object from slice of bytes.
func (c MsgpackCodec) Decode(data []byte, i interface{}) error {
dec := msgpack.NewDecoder(bytes.NewReader(data))
// dec.UseJSONTag(true)
err := dec.Decode(i)
return err
}
例子就省略了,他是默认的协议,不需要配置
Thrift
这里就只贴一下源码了,如果想看Thrift的使用例子,欢迎留言,我会择时补充(请允许我偷个懒,在rpc中使用Thrift的场景并不多见)。
type ThriftCodec struct{}
func (c ThriftCodec) Encode(i interface{}) ([]byte, error) {
b := thrift.NewTMemoryBufferLen(1024)
p := thrift.NewTBinaryProtocolFactoryDefault().GetProtocol(b)
t := &thrift.TSerializer{
Transport: b,
Protocol: p,
}
t.Transport.Close()
return t.Write(context.Background(), i.(thrift.TStruct))
}
func (c ThriftCodec) Decode(data []byte, i interface{}) error {
t := thrift.NewTMemoryBufferLen(1024)
p := thrift.NewTBinaryProtocolFactoryDefault().GetProtocol(t)
d := &thrift.TDeserializer{
Transport: t,
Protocol: p,
}
d.Transport.Close()
return d.Read(i.(thrift.TStruct), data)
}
自定义序列化
自定义非常简单,只要实现codec
接口就行了,定义如下:
type Codec interface {
Encode(i interface{}) ([]byte, error)
Decode(data []byte, i interface{}) error
}
我们自定义如下(使用了官方的例子,内嵌God编码解码器)
import (
"bytes"
"encoding/gob"
)
type GobCodec struct {
}
func (c *GobCodec) Decode(data []byte, i interface{}) error {
enc := gob.NewDecoder(bytes.NewBuffer(data))
err := enc.Decode(i)
return err
}
func (c *GobCodec) Encode(i interface{}) ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(i)
return buf.Bytes(), err
}
使用例子
// 注入GodCodec到rpcx中
share.Codecs[protocol.SerializeType(4)] = &GobCodec{}
option := client.DefaultOption
option.SerializeType = protocol.SerializeType(4)
xclient := client.NewXClient("Arith", client.Failtry, client.RandomSelect, d, option)
至此,已经对序列化做了全部的讲解
结语
我打算花足够多的时间来和大家读一读rpcx的源码,来一层层的剖解rpcx,有兴趣的朋友,可以关注我。
下一篇我们分析通信协议