前言
RPC,远程过程调用,通俗的讲就是调用远程的一个函数;RPC可能会涉及到不同语言的函数,这时候就需要Protobuf来支持多种不同的语言,而且Protobuf本身就很方便描述服务的接口,因此非常适合作为RPC世界的接口交流语言。
RPC
通过一个的例子来实现简单的Rpc功能
// 服务端
package main
import (
"log"
"net"
"net/rpc"
)
type HelloService struct {
}
//Go语言的Rpc规则:方法只能有2个可序列化的参数,第二个是指针类型,并且返回一个error类型
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello:" + request
return nil
}
func main() {
// 注册一个rpc服务
// 会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在HelloService服务的空间下
rpc.RegisterName("HelloService", new(HelloService))
listen, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("listen tcp fail", err)
}
accept, err := listen.Accept()
if err != nil {
log.Fatal("Accept fail:", err)
}
rpc.ServeConn(accept)
}
// 客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing error:",err)
}
var reply string
// 第二第三是RPC方法的参数
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal("call error:",err)
}
fmt.Println(reply)
}
- 我们在涉及RPC开发的过程中,有三种角色:服务器端实现RPC方法,客户端调用RPC方法,制定服务端和客户端RPC接口规范。上面的例子中,我们把3种角色都耦合在一起,不利于维护。
- 我们将RPC服务的接口规范分为3部分:服务的名字,服务要实现的详细方法列表,注册该类型服务的函数
跨语言的RPC
- Go语言的RPC框架有2个比较有特色的设计
- RPC数据打包时可以通过插件实现自定义编码和解码
- RPC建立在抽象的
io.ReadWriteCloser
接口上,我们可以将RPC架设在不同的通信协议上,egnet/rpc/jsonrpc
id为调用方维护的唯一的调用编号,Go语言的RPC支持异步调用,当返回结果的顺序和调用顺序不一致时,可以通过id来识别对应的调用
无论采取何种语言,只要遵循同样的JSON格式,以同样的流程就可以和Go语言编写的RPC服务进行通信,这样就实现了跨语言的RPC
http的RPC
Protobuf
- protobuf是谷歌开发的一种数据描述语言,通过附带工具生成代码并实现将结构化数据序列化功能,但是我们更关注Protobuf作为接口规范的描述下语言。
- 第三版的protobuf对所有成员均采用类似Go语言中的零值初始化,取消了
required
特性。 - Protobuf编码通过成员的唯一编码来绑定对应的数据,因此编码后数据的体积更小。
protoc
protobuf的核心工具集是c++开发的,要使用protoc工具集,可以通过
# 安装
go get github.com/golang/protobuf/protoc-gen-go
# 使用 生成go代码
protoc --go_out=. hello.proto
hello.proto
syntax= "proto3";
package main;
message String{
string value = 1;
}
生成一个hello.pb.go
文件
/ Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.23.0
// protoc v3.12.3
// source: hello.proto
package main
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
....
// s生成一个新的String类型,可以和rpc结合
type String struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *String) Reset() {
*x = String{}
...
}
func (x *String) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*String) ProtoMessage() {}
func (x *String) ProtoReflect() protoreflect.Message {
mi := &file_hello_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use String.ProtoReflect.Descriptor instead.
func (*String) Descriptor() ([]byte, []int) {
return file_hello_proto_rawDescGZIP(), []int{0}
}
// 为每一个类型都生成一个Get方法
func (x *String) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
...
- 如果在pb文件中定义rpc服务接口,
- 重新生成go代码并没有变化,因为世界上rpc的语言有千万种,protoc编译器并不知道该如何为HelloService服务生成代码。
- proto-gen-go内部集成了一个grpc插件
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
...
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type HelloServiceClient interface {
Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error)
}
type helloServiceClient struct {
cc grpc.ClientConnInterface
}
func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
return &helloServiceClient{cc}
}
func (c *helloServiceClient) Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error) {
out := new(String)
err := c.cc.Invoke(ctx, "/main.HelloService/Hello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// HelloServiceServer is the server API for HelloService service.
type HelloServiceServer interface {
Hello(context.Context, *String) (*String, error)
}
// UnimplementedHelloServiceServer can be embedded to have forward compatible implementations.
type UnimplementedHelloServiceServer struct {
}
func (*UnimplementedHelloServiceServer) Hello(context.Context, *String) (*String, error) {
return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented")
}
func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
s.RegisterService(&_HelloService_serviceDesc, srv)
}
func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(String)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HelloServiceServer).Hello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/main.HelloService/Hello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HelloServiceServer).Hello(ctx, req.(*String))
}
return interceptor(ctx, in, info, handler)
}
var _HelloService_serviceDesc = grpc.ServiceDesc{
ServiceName: "main.HelloService",
HandlerType: (*HelloServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Hello",
Handler: _HelloService_Hello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "hello.proto",
}