gRPC进阶
服务端拦截器
一元拦截器
func orderUnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 前置处理逻辑
log.Println("======= [Server Interceptor] ", info.FullMethod)
log.Printf(" Pre Proc Message : %s", req)
// 调用handle 执行一元RPC
m, err := handler(ctx, req)
// 后置处理逻辑
log.Printf(" Post Proc Message : %s", m)
return m, err
}
func main(){
lis, err := net.Listen("tcp", port)
// 服务端注册拦截器
s := grpc.NewServer(grpc.UnaryInterceptor(orderUnaryServerInterceptor))
pb.RegisterOrderManagementServer(s, &server{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
流拦截器
type wrappedStream struct {
// 包装器流
grpc.ServerStream
}
func (w *wrappedStream) RecvMsg(m interface{}) error {
log.Printf("====== [Server Stream Interceptor Wrapper] Receive a message (Type: %T) at %s", m, time.Now().Format(time.RFC3339))
return w.ServerStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m interface{}) error {
log.Printf("====== [Server Stream Interceptor Wrapper] Send a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ServerStream.SendMsg(m)
}
func newWrappedStream(s grpc.ServerStream) grpc.ServerStream {
return &wrappedStream{s}
}
func orderServerStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
// 前置处理
log.Println("====== [Server Stream Interceptor] ", info.FullMethod)
// 包装器流调用 流RPC
err := handler(srv, newWrappedStream(ss))
if err != nil {
log.Printf("RPC failed with error %v", err)
}
return err
}
func main(){
lis, err := net.Listen("tcp", port)
// 服务端注册拦截器
s := grpc.NewServer(grpc.StreamInterceptor(orderServerStreamInterceptor))
pb.RegisterOrderManagementServer(s, &server{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端拦截器
一元拦截器
func orderUnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 前置处理逻辑
log.Println("Method : " + method)
// 调用invoker 执行远程方法
err := invoker(ctx, method, req, reply, cc, opts...)
// 后置处理逻辑
log.Println(reply)
return err
}
func main(){
// 注册拦截器到客户端流
conn,err:=grpc.Dial(address,grpc.WithInsecure(),grpc.WithUnaryInterceptor(orderUnaryClientInterceptor)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewOrderManagementClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
// 调用一元RPC方法
order1 := pb.Order{Id: "101", Items: []string{"iPhone XS", "Mac Book Pro"}, Destination: "San Jose, CA", Price: 2300.00}
res, _ := c.AddOrder(ctx, &order1)
log.Print("AddOrder Response -> ", res.Value)
}
流拦截器
type wrappedStream struct {
grpc.ClientStream
}
func (w *wrappedStream) RecvMsg(m interface{}) error {
log.Printf("====== [Client Stream Interceptor] Receive a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ClientStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m interface{}) error {
log.Printf("====== [Client Stream Interceptor] Send a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ClientStream.SendMsg(m)
}
func newWrappedStream(s grpc.ClientStream) grpc.ClientStream {
return &wrappedStream{s}
}
func clientStreamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
// 前置处理逻辑
log.Println("======= [Client Interceptor] ", method)
// 调用streamer 来获取客户端流
s, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
return nil, err
}
return newWrappedStream(s), nil
}
func main(){
// 注册拦截器到客户端流
conn,err:=grpc.Dial(address,grpc.WithInsecure(),grpc.WithStreamInterceptor(clientStreamInterceptor))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewOrderManagementClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
// 调用客户端流RPC方法
searchStream, _ := c.SearchOrders(ctx, &wrapper.StringValue{Value: "Google"})
for {
searchOrder, err := searchStream.Recv()
if err == io.EOF {
log.Print("EOF")
break
}
if err == nil {
log.Print("Search Result : ", searchOrder)
}
}
}
截止时间、超时时间
截止时间:从请求开始时间+持续时间的偏移,多个服务调用,整个请求链需要在截止时间前响应,避免持续的等待RPC响应,造成资源消耗服务延迟
超时时间:指定等待RPC完成的时间,超时以错误结束返回
截止时间
func main(){
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewOrderManagementClient(conn)
// 设置截止时间+持续时间的偏移
clientDeadline := time.Now().Add(time.Duration(2 * time.Second))
ctx, cancel := context.WithDeadline(context.Background(), clientDeadline)
defer cancel()
order1 := pb.Order{Id: "101", Items: []string{"iPhone XS", "Mac Book Pro"}, Destination: "San Jose, CA", Price: 2300.00}
res, addErr := client.AddOrder(ctx, &order1)
}
// 服务端判断客户端是否满足截止时间的状态,然后丢弃这个RPC返回一个错误,可以是select来实现
if ctx.Err() == context.DeadlineExceeded {
log.Printf("RPC has reached deadline exceeded state : %s", ctx.Err())
return nil, ctx.Err()
}
超时时间
func main(){
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewOrderManagementClient(conn)
// 设置超时时间
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
order1 := pb.Order{Id: "101", Items: []string{"iPhone XS", "Mac Book Pro"}, Destination: "San Jose, CA", Price: 2300.00}
res, addErr := client.AddOrder(ctx, &order1)
}
错误处理
// 服务端
// 通过 status包创建所需的错误码和错误状态
errorStatus := status.New(codes.InvalidArgument, "Invalid information received")
// 错误详情
ds, err := errorStatus.WithDetails(
&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()
// 客户端
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)
}
多路复用
同一台服务器上的多个RPC服务的多路复用,比如同时保存一个订单的存根、一个欢迎的存根
因为多个RPC服务运行在一个服务端上,所以客户端的多个存根之间是可以共享gRPC连接的
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
// 注册进订单服务
ordermgt_pb.RegisterOrderManagementServer(grpcServer, &orderMgtServer{})
// 注册进欢迎服务
hello_pb.RegisterGreeterServer(grpcServer, &helloServer{})
}
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 订单服务建立实例连接
orderManagementClient := pb.NewOrderManagementClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
order1 := pb.Order{Id: "101", Items:[]string{"iPhone XS", "Mac Book Pro"}, Destination:"San Jose, CA", Price:2300.00}
res, addErr := orderManagementClient.AddOrder(ctx, &order1)
// 欢迎服务建立实例连接
helloClient := hwpb.NewGreeterClient(conn)
hwcCtx, hwcCancel := context.WithTimeout(context.Background(), time.Second)
defer hwcCancel()
helloResponse, err := helloClient.SayHello(hwcCtx, &hwpb.HelloRequest{Name: "gRPC Up and Running!"})
fmt.Println("Greeting: ", helloResponse.Message)
}
元数据
元数据创建
// 方法1
md := metadata.Pairs(
"1", "v1",
"1", "v2", // 方法1会把相同的键的字段合并,[ ]string{"v1","v2"}
"2", "v3",
)
// 方法2
md := metadata.New(map[string]string{"1":"v1","2":"v2"})
客户端收发
在context中设置的元数据会转换成线路层的gRPC头信息和 trailer
客户端发送这些头信息,收件方会以头信息的形式接收他们
// 创建元数据
md := metadata.Pairs(
"timestamp", time.Now().Format(time.StampNano),
"kn", "vn",
)
// 创建新元数据的上下文,这种方法会替换掉已有的上下文
mdCtx := metadata.NewOutgoingContext(context.Background(), md)
// 这种方法是将元数据附加到已有的上下文
ctxA := metadata.AppendToOutgoingContext(mdCtx, "k1", "v1", "k1", "v2", "k2", "v3")
// 定义头信息和 trailer,可以用来接收元数据
var header, trailer metadata.MD
order1 := pb.Order{Id: "101", Items: []string{"iPhone XS", "Mac Book Pro"}, Destination: "San Jose, CA", Price: 2300.00}
res, _ := client.AddOrder(ctxA, &order1, grpc.Header(&header), grpc.Trailer(&trailer))
log.Print("AddOrder Response -> ", res.Value)
// 获取头信息
head, err := res.Header()
// 获取trailer
trail, err := res.Trailer()
服务端收发
// 从上下文中获取元数据列表
md, metadataAvailable := metadata.FromIncomingContext(ctx)
if !metadataAvailable {
return nil, status.Errorf(codes.DataLoss, "UnaryEcho: failed to get metadata")
}
// 操作元数据逻辑
if t, ok := md["timestamp"]; ok {
fmt.Printf("timestamp from metadata:\n")
for i, e := range t {
fmt.Printf("====> Metadata %d. %s\n", i, e)
}
}
// 创建元数据
header := metadata.New(map[string]string{"location": "San Jose", "timestamp": time.Now().Format(time.StampNano)})
// 发送头信息
grpc.SendHeader(ctx, header)
trailer := metadata.Pairs("status","ok")
// 设置trailer
grpc.SetTrailer(ctx,trailer)
负载均衡
负载均衡器代理
也就是说后端的结构对gRPC客户端是不透明的,客户端只需要知道均衡器的断点就可以了
比如NGINX代理、Envoy代理
客户端负载均衡
func main(){
roundrobinConn, err := grpc.Dial(
address,
grpc.WithBalancerName("round_robin"), // 指定负载均衡的算法
// 默认是"pick_first",也就是从服务器列表中第一个服务端开始尝试发送请求,成功则后续所有RPC都发往这个服务器
// "round_robin"轮询调度算法,连接所有地址,每次向后端发送一个RPC
grpc.WithInsecure(),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer roundrobinConn.Close()
// 起10个RPC调度任务
makeRPCs(roundrobinConn, 10)
}
func makeRPCs(cc *grpc.ClientConn, n int) {
hwc := ecpb.NewEchoClient(cc)
for i := 0; i < n; i++ {
callUnary(hwc)
}
}
func callUnary(c ecpb.EchoClient) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
}
压缩数据
在服务端会对已注册的压缩器自动解码,响应时自动编码
始终从客户端获取指定的压缩方法,如果没被注册就会返回Unimplemented
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
defer conn.Close()
client := pb.NewOrderManagementClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
defer cancel()
order1 := pb.Order{Id: "101", Items:[]string{"iPhone XS", "Mac Book Pro"}, Destination:"San Jose, CA", Price:2300.00}
// 通过 grpc.UseCompressor(gzip.Name) 就可以轻松压缩数据
res, _ := client.AddOrder(ctx, &order1, grpc.UseCompressor(gzip.Name))
}