内容简介:—— 时间飞逝 如一名携带信息的邮差 但那只不过是我们的比喻 人物是杜撰的 匆忙是假装的 携带的也不是人的讯息主要包括以下两点原因:很对人经常拿
—— 时间飞逝 如一名携带信息的邮差 但那只不过是我们的比喻 人物是杜撰的 匆忙是假装的 携带的也不是人的讯息
为什么使用 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入门项目
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。