Open sangheee opened 2 years ago
advantages of gRPC
disadvantages of gRPC
client–server communication patterns for each method invocation
syntax = "proto3";
import "google/protobuf/wrappers.proto";
package somepackage;
generate the stubs for the service definition
go mod init github.com/sangheee/grpc-test/service
go.mod
file with the dependencies with specific version
module github.com/sangheee/grpc-test/service
require ( github.com/gofrs/uuid v3.2.0 github.com/golang/protobuf v1.3.2 github.com/google/uuid v1.1.1 google.golang.org/grpc v1.24.0 )
- generate client/server stubs
- download and install latest protocol buffer version 3 compiler
- install the gRPC library
```bash
go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go
protoc --go_out=plugins=grpc:<module_dir_path>/ecommerce ecommerce/product_info.proto
implement the business logic using the generated code
go module 내에 test_service.go
같은 이름의 새로운 go file 생성하고 remote method 구현
create server that hosts the service and accepts requests from client
main_client.go
와 같은 이름의 새로운 go file을 생성하고 remote method를 호출
unary RPC (simple RPC), server-side streaming, client-side streaming, and bidirectional streaming.
how a remote procedure call works over the network when the client calls remote function in the generated stub
application/grpc
prefixed/ProductInfo/getProduct
)는 다른 HTTP header로 보내진다.gRPC uses HTTP/2 as its transport protocol to send messages over the network
HTTP/2의 모든 client-server communication은 양방향의 원하는 만큼 데이터를 전달할 수 있는 단일 TCP 연결을 통해 수행된다.
HTTP/2 terminology:
gRPC channel은 enp-point로의 HTTP/2 connection을 가리킨다.
request message는 항상 client application을 통해 trigger된다.
three main components:
END_SREAM
flag 추가
DATA (flags = END_STREAM)
<Length-Prefixed Message>
request header example
HEADERS (flags = END_HEADERS)
:method = POST
:scheme = http
:path = /ProductInfo/getProduct
:authority = abc.com
te = trailers
grpc-timeout = 1s
content-type = application/grpc
grpc-encodeing = gzip
authorization = Bearer xxxxxx
/{servicr name}/{method name}
과 같이 정의한다.If the length-prefixed message doesn’t fit one data frame, it can span to multiple data frames.
client는 EOF를 보내면 connection을 half-close한다.
“half-close the connection” means the client closes the connection on its side so the client is no longer able to send messages to the server but still can listen to the incoming messages from the server.
HEADERS(flags = END_STREAM, END_HEADERS)
grpc-status = 0
grpc-message = xxxxx
END_STREAM
flag 추가한 형태 ㅇㅇ)remote function 실행 전/후로 실행시킬 common logic(such as logging, authentication, authorization, metrics, tracing, and so on)을 수행하기 위해 intercept incoming and outgoing RPC
unary interceptor example
implement a function of type UnaryServerInterceptor
and register that function when you create a gRPC server
func(ctx context.Context, req interface{}, info *UnaryServerInfo,
handler UnaryHandler) (resp interface{}, err error)
func orderUnaryServerInterceptor(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler)
(interface{}, error) {
// Preprocessing logic
// Gets info about the current RPC call by examining the args passed in
log.Println("======== [Server Interceptor]", info.FullMethod)
// Invoking the RPC method via UnaryHandler
m, err := handler(ctx, req)
// Post processing logic
log.Printf("Post Proc Message: %s", m)
return m, err
}
...
func main() {
...
// Registering the interceptor at the server-side
s := grpc.NewServer(grpc.UnaryInterceptor(orderUnaryServerInterceptor))
...
}
- preprocessor 단계에서는 RPC call을 수정할 수도 있다.
#### Client-Side Interceptors
UnaryClientInterceptor
, server connection dial option에 설정
func(ctx context.Context, method string, req, reply interface{},
cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
func main(){
// Setting up a connection to the server
conn, err := grpc.Dial(address, grpc.WithInsecure(),
grpc.WithUnaryInterceptor(yourInterceptorFunctionName))
...
}
error creation and propagation on the server side
if orderReq.Id == "-1" {
log.Printf("Order ID is invalid! -> Received Order ID %s", orderReq.Id)
errorStatus := status.New(codes.InvalidArgument,
"Invalid information received")
ds, err := errorStatus.WithDetails(
// error details with an error type BadRequest_FieldViolation from google.golang.org/genproto/googleapis/rpc/errdetails.
&epb.BadRequest_FieldViolation{
Field:"ID",
Description: fmt.Sprintf(
"Order ID received is not valid %s : %s",
orderReq.Id, orderReq.Description),
},
)
if err!=nil {
return nil, errorStatus.Err()
}
return nil, ds.Err()
}
...
error handling on the client side
order1 := pb.Order{Id: "-1",
Items:[]string{"iPhone XS", "Mac Book Pro"},
Destination:"San Jose, CA", Price:2300.00}
res, addOrderError := client.AddOrder(ctx, &order1)
if addOrderError != nil {
errorCode := status.Code(addOrderError)
if errorCode == codes.InvalidArgument {
log.Printf("Invalid Argument Error : %s", errorCode)
errorStatus := status.Convert(addOrderError)
for _, d := range errorStatus.Details() {
switch info := d.(type) {
case *epb.BadRequest_FieldViolation:
log.Printf("Request Field Invalid: %s", info)
default:
log.Printf("Unexpected error type: %s", info)
}
}
} else {
log.Printf("Unhandled error : %s ", errorCode)
}
} else {
log.Print("AddOrder Response -> ", res.Value)
}
you can use load-balancing solutions such as Nginx, Envoy proxy, etc., as the LB proxy for your gRPC applications.
There are two load-balancing policies supported in gRPC by default: _pickfirst and _roundrobin.
pickfirstConn, err := grpc.Dial(
fmt.Sprintf("%s:///%s, exampleSheme, exampleServiceName).
grpc.WithBalancerName("pick_first"), // pick_first is the default option
grpc.WithInsecure(),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
makeRPCs(pickfirstConn)
roundrobinConn, err := grpc.Dial(
fmt.Sprintf("%s:///%s, exampleSheme, exampleServiceName).
grpc.WithBalancerName("round_robin"),
grpc.WithInsecure(),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
makeRPCs(roundrobinConn)
use mocking framework Gomock
mockgen github.com/sangheee/grpc-test/proto \
ProductInfoClient > mock_prodinfo/prodinfo_mock.go
func TestAddProduct(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockProdInfoClient := NewMockProductInfoClient(ctrl)
...
req := &pb.Product{Name: name, Description: desc, Price: price}
mockProdInfoClient.EXPECT().
AddProduct(gomock.Any(), &rpcMsg{msg:req},).
Return(&wrapper.StringValue{Value: "ABC123"+name}, nil)
testAddProduct(t, mockProdClient)
}
func testAddProduct(t *testing.T, client pb.ProductInfoClient) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
...
r, err := client.AddProduct(ctx, &pb.Product{Name: name, Description: desc, Price: price})
// test and verify response.
}
ghz is a load-testing tool
ghz --insecure \
--proto ./greeter.proto \
--call helloworld.Greeter.SayHello \
-d '{"name":"Joe"}' \
-n 2000 \ // total number of requests
-c 20 \ // 20 threads
0.0.0.0:50051
If you are running your gRPC applications on Kubernetes, then you can run the grpc_health_probe to check to define Kubernetes’s liveness and readiness checks for your gRPC server pods.
protoc --go_out=. --go-grpc_out=. ${SOME_PROTOBUF_FILE}
--go_out
과 --go-grpc_out
왜 둘 다 줘야 하는가? 에 관해서
https://stackoverflow.com/a/60580149/9624199 에 설명이 나와있다.
요약하자면 --go_out
은 protoc-gen-go
plugin, --go-grpc_out
은 protoc-gen-go-grpc
plugin과 매칭된다.
https://go.dev/blog/protobuf-apiv2 이런 이유로 protoc-gen-go는 no longer supports generating gRPC service definitions
For gRPC code, a new plugin called protoc-gen-go-grpc was developed by Go gRPC project. The plugins flag, which provided a way to invoke the gRPC code generator in the old-way, is deprecated.
protoc-gen-go-grpc
This tool generates Go language bindings of services in protobuf definition files for gRPC.
https://developers.google.com/protocol-buffers/docs/proto#packages
In Go, the package directive is ignored, and the generated
.pb.go
file is in the package named after the correspondinggo_proto_library
rule.
gRPC connections are sticky. gRPC uses HTTP/2, which multiplexes multiple calls on a single TCP connection. All gRPC calls over that connection go to one endpoint.
client로부터 server로의 connection이 맺어지면, 해당 connection은 여러 request에 재사용된다.(multiplexed)
클라이언트가 서버로 large volumes을 보내려 하면, there will be no chance of distributing that load to other instances.
A Network LB operates at the L4.
사실 이런 형태의 LB 구성은 위에서 설명한 이유(multiplex)로 부하를 제대로 분산하지 못한다.
Recommended by the official gRPC load balancing: Look-Aside
L7 proxies understand HTTP/2, and are able to distribute gRPC calls,
There are many L7 proxies available. Some options are:
你好,你的邮件我已经收到 Alexwangyu
gRPC: Up and Running (Building Cloud Native Applications with Go and Java for Docker and Kubernetes) cloud native와 MSA의 등장으로 application간의 inter-process communication technologies를 사용한 network 연결이 필요
읽을 거리: