内容简介:—— 时间飞逝 如一名携带信息的邮差 但那只不过是我们的比喻 人物是杜撰的 匆忙是假装的 携带的也不是人的讯息主要包括以下两点原因:很对人经常拿
—— 时间飞逝 如一名携带信息的邮差 但那只不过是我们的比喻 人物是杜撰的 匆忙是假装的 携带的也不是人的讯息
为什么使用 grpc
主要包括以下两点原因:
protocl buffer http 2.0
很对人经常拿
thrift
跟
grpc
比较,现在先不发表任何看法,后续会深入 thrift
进行介绍。
http/2
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.
http/2
带来了网络性能的巨大提升,下面列举一些个人觉得比较重要的细节:
http/2
更多细节,请参考文章末尾的链接,当然,后续也会专门介绍。
准备工作
大家可以参考
protobuf
的介绍,具体包括:
-
安装
Go
的开发环境,因为后续是基于Go
语言的开发项目 -
安装
protocol-buffers
-
安装
protoc-gen-go
,用于自动生成源码
生成源码的命令如下,其中, --go_out
用于指定生成源码的保存路径;而 -I
是 -IPATH
的简写,用于指定查找 import
文件的路径,可以指定多个;最后的 order
是编译的 grpc
文件的存储路径。
protoc -I proto/ proto/order.proto --go_out=plugins=grpc:order
protocol buffer
google
开发的高效、跨平台的数据传输格式。当然,本质还是数据传输结构。但 google
赋予了它丰富的功能,比如 import
、 package
、消息嵌套等等。 import
用于引入别的 .proto
文件; package
用于定义命名空间,转换到 go
源码中就是包名; repeated
用于定义重复的数据; enum
用于定义枚举类型等。
.proto
内字段的基本定义:
type name = tag;
Protocol buffer
本身不包含类型的描述信息,因此获取了没有 .proto
描述文件的二进制信息是毫无用处的,我们很难提取出非常有用的信息。 Go
语言 complier
生成的文件后缀是 .pb.go
,它自动生成了 set
、 get
以及 read
、 write
方法,我们可以很方便的序列化数据。
下面我们定义一个创建订单的 .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
简直就是用来打蚊子的,让我们后续继续深入了解吧。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- TiDB入门(四):从入门到“跑路”
- MyBatis从入门到精通(一):MyBatis入门
- MyBatis从入门到精通(一):MyBatis入门
- Docker入门(一)用hello world入门docker
- 赵童鞋带你入门PHP(六) ThinkPHP框架入门
- 初学者入门 Golang 的学习型项目,go入门项目
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Verilog数字系统设计教程
夏宇闻 / 北京航空航天大学出版社 / 2003-7-1 / 38.0
《Verilog数字系统设计教程》可作为电子工程类、自动控制类、计算机类的大学本科高年级及研究生教学用书,亦可供其他工程人员自学与参考。一起来看看 《Verilog数字系统设计教程》 这本书的介绍吧!