introduce
I guess most developers who have used Java
for a long time should have less contact with gRPC
. After all, most of the Java
circles Dubbo/SpringClound
these two service frameworks.
I also had the opportunity to rebuild the business from scratch recently before I came into contact with gRPC
. There were several reasons for choosing gRPC
at that time:
- Develop and deploy projects based on cloud-native ideas, and
gRPC
is almost a standard communication protocol in cloud-native. - The development language chose Go, and
gRPC
is obviously a better choice in Go circles. - Some businesses in the company use
Python
for development, andgRPC
supports multi-language compatibility very well.
After more than a year of stable online operation, it can be seen that gRPC
is still very stable and efficient; the core points of the rpc framework:
- Serialization
- letter of agreement
- IDL (Interface Description Language)
These correspond in gRPC
to:
- Based on
Protocol Buffer
serialization protocol, the performance is efficient. - Based on
HTTP/2
standard protocol development, it comesstream
, multiplexing and other features; at the same time, because it is a standard protocol, the compatibility of third-party tools will be better (such as load balancing, monitoring, etc.) - Write a
.proto
interface file to generate common language codes.
HTTP/2
Before learning gRPC
, you must first know what protocol it communicates through. The HTTP/1.1
protocol is basically the one we are most exposed to in daily development or application.
Since HTTP/1.1
is a text protocol, it is very friendly to humans, but on the contrary, its performance is relatively low for machines.
It is necessary to parse the text repeatedly, and the efficiency is naturally low; to be more friendly to the machine, it is necessary to use binary, which is naturally done by HTTP/2
.
Apart from that, there are other advantages:
- Multiplexing: messages can be sent and received in parallel without affecting each other
HPACK
savesheader
space and avoidsHTTP1.1
to the sameheader
repeatedly.
Protocol
gRPC
uses Protocol
serialization, which was released earlier than gRPC
, so it is not only used for gRPC
, but can be used in any scenario that requires serialized IO operations.
It will be more space-saving and high-performance; it was used for data interaction when developing https://github.com/crossoverJie/cim .
package order.v1;
service OrderService{
rpc Create(OrderApiCreate) returns (Order) {}
rpc Close(CloseApiCreate) returns (Order) {}
// 服务端推送
rpc ServerStream(OrderApiCreate) returns (stream Order) {}
// 客户端推送
rpc ClientStream(stream OrderApiCreate) returns (Order) {}
// 双向推送
rpc BdStream(stream OrderApiCreate) returns (stream Order) {}
}
message OrderApiCreate{
int64 order_id = 1;
repeated int64 user_id = 2;
string remark = 3;
repeated int32 reason_id = 4;
}
It is also very simple to use. You only need to define your own .proto
file, and you can use the command line tool to generate the SDK of the corresponding language.
For details, please refer to the official documentation:
https://grpc.io/docs/languages/go/generated-code/
transfer
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
test.proto
After generating the code, writing the server is very simple, you only need to implement the generated interface.
func (o *Order) Create(ctx context.Context, in *v1.OrderApiCreate) (*v1.Order, error) {
// 获取 metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
}
fmt.Println(md)
fmt.Println(in.OrderId)
return &v1.Order{
OrderId: in.OrderId,
Reason: nil,
}, nil
}
The client is also very simple. It only needs to rely on the server code to create a connection
and then it is the same as calling a local method.
This is a classic unary
(unary) call, similar to the request-response mode of http, one request corresponds to one response.
Server stream
In addition to the regular gRPC
calls, unary
also supports server push, which is useful in some specific scenarios.
func (o *Order) ServerStream(in *v1.OrderApiCreate, rs v1.OrderService_ServerStreamServer) error {
for i := 0; i < 5; i++ {
rs.Send(&v1.Order{
OrderId: in.OrderId,
Reason: nil,
})
}
return nil
}
The push of the server is shown above, and the Send
function can be called to push to the client.
for {
msg, err := rpc.RecvMsg()
if err == io.EOF {
marshalIndent, _ := json.MarshalIndent(msgs, "", "\t")
fmt.Println(msg)
return
}
}
The client obtains the server message by judging whether the currently received data packet has expired through a loop.
In order to show this process more intuitively, a previously developed gRPC
client is optimized, which can debug stream
calls intuitively.
The figure above is an example of server push.
Client Stream
In addition to supporting server push, the client also supports it.
The client keeps sending data to the server in the same connection, and the server can process the messages in parallel.
// 服务端代码
func (o *Order) ClientStream(rs v1.OrderService_ClientStreamServer) error {
var value []int64
for {
recv, err := rs.Recv()
if err == io.EOF {
rs.SendAndClose(&v1.Order{
OrderId: 100,
Reason: nil,
})
log.Println(value)
return nil
}
value = append(value, recv.OrderId)
log.Printf("ClientStream receiv msg %v", recv.OrderId)
}
log.Println("ClientStream finish")
return nil
}
// 客户端代码
for i := 0; i < 5; i++ {
messages, _ := GetMsg(data)
rpc.SendMsg(messages[0])
}
receive, err := rpc.CloseAndReceive()
The code is similar to server push, but the roles are reversed.
Bidirectional Stream
Similarly, it is also supported when both the client and the server are sending messages at the same time.
// 服务端
func (o *Order) BdStream(rs v1.OrderService_BdStreamServer) error {
var value []int64
for {
recv, err := rs.Recv()
if err == io.EOF {
log.Println(value)
return nil
}
if err != nil {
panic(err)
}
value = append(value, recv.OrderId)
log.Printf("BdStream receiv msg %v", recv.OrderId)
rs.SendMsg(&v1.Order{
OrderId: recv.OrderId,
Reason: nil,
})
}
return nil
}
// 客户端
for i := 0; i < 5; i++ {
messages, _ := GetMsg(data)
// 发送消息
rpc.SendMsg(messages[0])
// 接收消息
receive, _ := rpc.RecvMsg()
marshalIndent, _ := json.MarshalIndent(receive, "", "\t")
fmt.Println(string(marshalIndent))
}
rpc.CloseSend()
In fact, the two appeals are combined into one.
It is easy to understand by calling an example.
metadata
gRPC
also supports metadata transfer, similar to HTTP
in header
.
// 客户端写入
metaStr := `{"lang":"zh"}`
var m map[string]string
err := json.Unmarshal([]byte(metaStr), &m)
md := metadata.New(m)
// 调用时将 ctx 传入即可
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 服务端接收
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
}
fmt.Println(md)
gRPC gateway
Although gRPC
is powerful and easy to use, its support for browsers and APPs is not as extensive as REST applications (the browsers also support it, but there are very few applications).
To this end, the community created the https://github.com/grpc-ecosystem/grpc-gateway project, which can expose gRPC services as RESTFUL APIs.
In order to allow the testers to be accustomed to using postman for interface testing, we also proxy the gRPC service for easier testing.
reflection call
As an RPC framework, generalization calls must also be supported, which can facilitate the development of supporting tools; gRPC is supported through reflection, and reflection calls are made by obtaining the service name and pb file.
https://github.com/jhump/protoreflect This library encapsulates common reflection operations.
The visualization stream
call seen in the image above is also implemented through this library.
load balancing
Since gRPC
is based on HTTP/2
, the client and server will maintain a long connection; at this time, load balancing is not as simple as HTTP
.
And we use gRPC
to achieve the same effect as HTTP, and we need to load balance requests instead of connections.
There are usually two approaches:
- Client Load Balancing
- Server Load Balancing
Client load balancing is widely used in rpc
calls. For example, Dubbo
is the client load balancing used.
gRPC
also provides related interfaces, please refer to the official demo for details.
https://github.com/grpc/grpc-go/blob/87eb5b7502/examples/features/load_balancing/README.md
Client-side load balancing is relatively more flexible for developers (you can customize your own strategies), but you also need to maintain this logic yourself. If there are multiple languages, you have to maintain multiple copies.
Therefore, in the context of cloud native, it is more recommended to use server-side load balancing.
Options are:
- istio
- envoy
- apix
We are also working on this, and we will probably use envoy/istio
.
Summarize
There is still a lot of content in gRPC
. This article is only an introductory material. I hope that those who do not know gRPC
can have a basic understanding; this is indeed a necessary skill in the cloud native era.
Friends who are interested in the gRPC client in this article can refer to the source code here:
https://github.com/crossoverJie/ptg
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。