时间飞逝 如一名携带信息的邮差 但那只不过是咱们的比喻 人物是杜撰的 匆忙是伪装的 携带的也不是人的讯息
grpc
主要包括如下两点缘由:html
protocl buffer
一种高效的序列化结构。http 2.0
标准化协议。很对人常常拿thrift
跟grpc
比较,如今先不发表任何见解,后续会深刻thrift
进行介绍。node
http/2
HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection… Specifically, it allows interleaving of request and response messages on the same connection and uses an efficient coding for HTTP header fields. It also allows prioritization of requests, letting more important requests complete more quickly, further improving performance.The resulting protocol is more friendly to the network, because fewer TCP connections can be used in comparison to HTTP/1.x. This means less competition with other flows, and longer-lived connections, which in turn leads to better utilization of available network capacity. Finally, HTTP/2 also enables more efficient processing of messages through use of binary message framing.git
http/2
带来了网络性能的巨大提高,下面列举一些我的以为比较重要的细节:github
http/2
对每一个源只需建立一个持久链接,在这一个链接内,能够并行的处理多个请求和响应,并且作到不相互影响。更多细节,请参考文章末尾的连接,固然,后续也会专门介绍。golang
准备工做
你们能够参考protobuf
的介绍,具体包括:web
Go
的开发环境,由于后续是基于Go
语言的开发项目protocol-buffers
protoc-gen-go
,用于自动生成源码生成源码的命令以下,其中,--go_out
用于指定生成源码的保存路径;而-I
是-IPATH
的简写,用于指定查找import
文件的路径,能够指定多个;最后的order
是编译的grpc
文件的存储路径。apache
protoc -I proto/ proto/order.proto --go_out=plugins=grpc:order
protocol buffer
google
开发的高效、跨平台的数据传输格式。固然,本质仍是数据传输结构。但google
赋予了它丰富的功能,好比import
、package
、消息嵌套等等。import
用于引入别的.proto
文件;package
用于定义命名空间,转换到go
源码中就是包名;repeated
用于定义重复的数据;enum
用于定义枚举类型等。bash
.proto
内字段的基本定义:服务器
type name = tag;
Protocol buffer
自己不包含类型的描述信息,所以获取了没有.proto
描述文件的二进制信息是毫无用处的,咱们很难提取出很是有用的信息。Go
语言complier
生成的文件后缀是.pb.go
,它自动生成了set
、get
以及read
、write
方法,咱们能够很方便的序列化数据。restful
下面咱们定义一个建立订单的.proto
文件,归纳的描述:buyerID
在device
上支付amount
买sku
商品。
proto3
,package
是order
。ANDROID
和IOS
两种,并且类型被嵌套声明在OrderParams
内。sku
声明为repeated
,由于用户可能购买多个商品。OrderResult
为响应的消息体结构,包括生成的订单号和处理的响应码。service
声明了order
要提供的服务。当前仅仅实现一个simple RPC
:客户端使用OrderParams
参数请求RPC
服务器,收到OrderResult
做为响应。syntax = "proto3"; package order; service Order { //a simple RPC //create new order rpc Add (OrderParams) returns (OrderResult) { } } message OrderParams { string amount = 1; //订单金额 int64 buyerID = 2; //购买用户ID enum Device { IOS = 0; ANDROID = 1; } Device device = 3; repeated Sku sku = 4; } message Sku { int32 num = 1; string skuId = 2; int32 unitPrice = 3; } message OrderResult { int32 statusCode = 1; string orderID = 2; }
grpc
接口经过定义的.proto
文件生成grpc client
和server
端实现的接口类型。生成的内容主要包括:
protocol buffer
各类消息类型的序列化操做grpc client
实现的接口类型,以及client
实现的grpc
方法grpc server
待实现的接口类型service
处理流程第一步. 服务端为每一个接收的链接建立单独的goroutine
进行处理。
第二步. 自动生成的代码中,声明了服务的具体描述,也是该服务的“路由”。包括服务名称ServiceName
以Methods
、Streams
。当rpc
接收到新的数据时,会根据路由执行对应的方法。由于咱们的设定没有处理流的场景,因此Streams
为空的结构体。
代码中的服务名称被指定为:order.Order
,对应建立订单的方法是:Add
。
var _Order_serviceDesc = grpc.ServiceDesc{ ServiceName: "order.Order", HandlerType: (*OrderServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Add", Handler: _Order_Add_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "order.proto", }
第三步. 将路由注册到rpc
服务中。以下所示,就是将上述的路由转换为map
对应关系的过程。类比restful
风格的接口定义,等价于/order/
这种请求都由这个service
来进行处理。
最终将service
注册到gRPC server
上。同时,咱们能够逆向猜出服务的处理过程:经过请求的路径获取service
,而后经过MethodName
调用相应的处理方法。
srv := &service{ server: ss, md: make(map[string]*MethodDesc), sd: make(map[string]*StreamDesc), mdata: sd.Metadata, } for i := range sd.Methods { d := &sd.Methods[i] srv.md[d.MethodName] = d } for i := range sd.Streams { d := &sd.Streams[i] srv.sd[d.StreamName] = d } s.m[sd.ServiceName] = srv
第四步. gRPC
服务处理请求。经过请求的:path
,获取对应的service
和MethodName
进行处理。
service := sm[:pos] method := sm[pos+1:] if srv, ok := s.m[service]; ok { if md, ok := srv.md[method]; ok { s.processUnaryRPC(t, stream, srv, md, trInfo) return } if sd, ok := srv.sd[method]; ok { s.processStreamingRPC(t, stream, srv, sd, trInfo) return } }
经过结合protoc
自动生成的client
端代码,无需抓包,咱们就能够推断出path
的格式,以及系统是如何处理路由的。代码中定义的:/order.Order/Add
就是依据。
func (c *orderClient) Add(ctx context.Context, in *OrderParams, opts ...grpc.CallOption) (*OrderResult, error) { out := new(OrderResult) err := c.cc.Invoke(ctx, "/order.Order/Add", in, out, opts...) if err != nil { return nil, err } return out, nil }
建立订单
为了简单起见,咱们只保证订单的惟一性。这里咱们实现一个简易版本,并且也不作过多介绍。感兴趣的同窗能够移步到另外一篇文章:探讨分布式ID生成系统去了解,毕竟不该该是本节的重心。
//上次建立订单使用的毫秒时间 var lastTimestamp = time.Now().UnixNano() / 1000000 var sequence int64 const MaxSequence = 4096 // 42bit分配给毫秒时间戳 // 12bit分配给序列号,每4096就从新开始循环 // 10bit分配给机器ID func CreateOrder(nodeId int64) string { currentTimestamp := getCurrentTimestamp() if currentTimestamp == lastTimestamp { sequence = (sequence + 1) % MaxSequence if sequence == 0 { currentTimestamp = waitNextMillis(currentTimestamp) } } else { sequence = 0 } orderId := currentTimestamp << 22 orderId |= nodeId << 10 orderId |= sequence return strings.ToUpper(fmt.Sprintf("%x", orderId)) } func getCurrentTimestamp() int64 { return time.Now().UnixNano() / 1000000 } func waitNextMillis(currentTimestamp int64) int64 { for currentTimestamp == lastTimestamp { currentTimestamp = getCurrentTimestamp() } return currentTimestamp }
运行系统
建立服务端代码。注意:使用grpc
提供的默认选项,实际上是很危险的行为。在生产开发中,被不熟悉的默认选项坑到的状况比比皆是。这里的代码不要做为后续生产环境开发的参考。服务端的代码相比客户端要复杂一点,须要咱们去实现处理请求的接口。
type Order struct { } func (o *Order) Add(ctx context.Context, in *order.OrderParams) (*order.OrderResult, error) { return &order.OrderResult{ OrderID: util.CreateOrder(1), }, nil } func main() { lis, err := net.Listen("tcp", "127.0.0.1:10000") if err != nil { log.Fatalf("Failed to listen: %v", err) } grpcServer := grpc.NewServer() order.RegisterOrderServer(grpcServer, &Order{}) grpcServer.Serve(lis) }
客户端的代码很是简单,构造参数,处理返回就Ok
了。
func createOrder(client order.OrderClient, params *order.OrderParams) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() orderResult, err := client.Add(ctx, params) if err != nil { log.Fatalf("%v.GetFeatures(_) = _, %v: ", client, err) } log.Println(orderResult) } func main() { conn, err := grpc.Dial("127.0.0.1:10000") if err != nil { log.Fatalf("fail to dial: %v", err) } defer conn.Close() client := order.NewOrderClient(conn) orderParams := &order.OrderParams{ BuyerID: 10318003, } createOrder(client, orderParams) }
文章介绍了gRPC
的入门知识,包括protocol buffer
以及http/2
,gRPC
封装了不少东西,对于通常场合,咱们只须要指定配置,实现接口就能够了,很是简单。
在入门的介绍里,你们会以为gRPC
不就跟RESTFUL
请求同样吗?确实是,我也这样以为。但存在一个最直观的优势:经过使用gRPC
,能够将复杂的接口调用关系封装在SDK
中,直接提供给第三方使用,并且还能有效避免错误调用接口的状况。
若是gRPC
只能这样的话,它就太失败了,他用HTTP/2
简直就是用来打蚊子的,让咱们后续继续深刻了解吧。
参考文章: