前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >grpc-go之异常处理(四)

grpc-go之异常处理(四)

原创
作者头像
Johns
发布2022-10-11 10:37:13
1.2K0
发布2022-10-11 10:37:13
举报
文章被收录于专栏:代码工具代码工具

介绍

我在之前的文章《go里面的异常处理》简单地说了下go的异常处理机制, 在web中, 一般可以通过框架层面提供的过滤器/拦截器统一地处理这种异常, 避免main函数被带崩.

grpc-go的异常处理

grpc-go的异常处理比较简单, 需要注意的点其实就是需要针对Unary和Stream两种模式都添加拦截器

下面实现一个简单的异常处理拦截器并将其应用到Server中

grpc_recovery目录

interceptors.go

代码语言:txt
复制
package grpc_recovery

import (
	"context"
	"fmt"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// RecoveryHandlerFunc is a function that recovers from the panic `p` by returning an `error`.
type RecoveryHandlerFunc func(p interface{}) (err error)

// RecoveryHandlerFuncContext is a function that recovers from the panic `p` by returning an `error`.
// The context can be used to extract request scoped metadata and context values.
type RecoveryHandlerFuncContext func(ctx context.Context, p interface{}) (err error)

// UnaryServerInterceptor returns a new unary server interceptor for panic recovery.
func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
	o := evaluateOptions(opts)
	fmt.Println("拦截器")
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (_ interface{}, err error) {
		panicked := true

		defer func() {
			if r := recover(); r != nil || panicked {
				err = recoverFrom(ctx, r, o.recoveryHandlerFunc)
			}
		}()

		resp, err := handler(ctx, req)
		panicked = false
		return resp, err
	}
}

// StreamServerInterceptor returns a new streaming server interceptor for panic recovery.
func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
	o := evaluateOptions(opts)
	fmt.Println("拦截器")
	return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
		panicked := true

		defer func() {
			if r := recover(); r != nil || panicked {
				err = recoverFrom(stream.Context(), r, o.recoveryHandlerFunc)
			}
		}()

		err = handler(srv, stream)
		panicked = false
		return err
	}
}

func recoverFrom(ctx context.Context, p interface{}, r RecoveryHandlerFuncContext) error {
	if r == nil {
		return status.Errorf(codes.Internal, "%v", p)
	}
	return r(ctx, p)
}

options.go

代码语言:go
复制
package grpc_recovery

import "context"

var (
	defaultOptions = &options{
		recoveryHandlerFunc: nil,
	}
)

type options struct {
	recoveryHandlerFunc RecoveryHandlerFuncContext
}

func evaluateOptions(opts []Option) *options {
	optCopy := &options{}
	*optCopy = *defaultOptions
	for _, o := range opts {
		o(optCopy)
	}
	return optCopy
}

type Option func(*options)

// WithRecoveryHandler customizes the function for recovering from a panic.
func WithRecoveryHandler(f RecoveryHandlerFunc) Option {
	return func(o *options) {
		o.recoveryHandlerFunc = RecoveryHandlerFuncContext(func(ctx context.Context, p interface{}) error {
			return f(p)
		})
	}
}

// WithRecoveryHandlerContext customizes the function for recovering from a panic.
func WithRecoveryHandlerContext(f RecoveryHandlerFuncContext) Option {
	return func(o *options) {
		o.recoveryHandlerFunc = f
	}
}

server目录

main.go

代码语言:go
复制
package main

import (
	"crypto/tls"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/examples/data"
	"google.golang.org/grpc/status"
	"grpc-demo/helloworld/grpc_recovery"
	"grpc-demo/helloworld/pb"
	"log"
	"net"
)

const (
	port = ":50051"
)

var (
	customFunc grpc_recovery.RecoveryHandlerFunc
)

func main() {
	cert, err := tls.LoadX509KeyPair(
		data.Path("/Users/guirong/go/src/grpc-demo/helloworld/server/server.crt"),
		data.Path("/Users/guirong/go/src/grpc-demo/helloworld/server/server.key"))
	if err != nil {
		log.Fatalf("failed to load key pair: %s", err)
	}

	// Define customfunc to handle panic
	customFunc = func(p interface{}) (err error) {
		fmt.Printf("panic triggered: %v\n", p)
		return status.Errorf(codes.Unknown, "panic triggered: %v", p)
	}
	// Shared options for the logger, with a custom gRPC code to log level function.
	opts := []grpc_recovery.Option{
		grpc_recovery.WithRecoveryHandler(customFunc),
	}

	s := grpc.NewServer(
		grpc.Creds(credentials.NewServerTLSFromCert(&cert)),
		grpc.ChainUnaryInterceptor(grpc_recovery.UnaryServerInterceptor(opts...), myEnsureValidToken),
		grpc.ChainStreamInterceptor(grpc_recovery.StreamServerInterceptor(opts...), streamInterceptorAuth),
	)

	pb.RegisterGreeterServer(s, &GreeterServer{})
	// 玩家连续进行了多次战斗请求,服务器将操作结果响应给玩家
	pb.RegisterBattleServiceServer(s, &BattleServer{})

	listen, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	log.Println("Serving gRPC on 0.0.0.0" + port)
	if err := s.Serve(listen); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

⚠️需要注意的是, 异常处理的拦截器应该在整个chain的顶端, 这样才能避免异常传递到主函数导致服务崩溃.

输出效果

我们需要手动在服务里添加一个painc, 然后对比有异常处理和没有的区别.

image.png
image.png

无异常处理

可以看到, 一旦触发异常, 那么服务器main函数也会被拉垮.

image.png
image.png

有异常处理

有异常处理, 只是输出了一个painc信息, main函数不受影响.

image.png
image.png

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • grpc-go的异常处理
    • grpc_recovery目录
      • server目录
      • 输出效果
        • 无异常处理
          • 有异常处理
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档