面试题答案
一键面试设计思路
- 在单个服务实例层面:在gRPC服务方法的处理函数中使用
recover
机制来捕获panic
,避免服务进程崩溃。 - 错误传播:将捕获到的
panic
信息转换为合适的错误类型,并通过gRPC响应返回给调用方。 - 减少性能影响:尽量减少
recover
机制对正常业务流程的额外开销,只在异常情况下触发处理逻辑。
关键代码片段
- 服务端:
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"log"
)
// 假设这是gRPC服务接口定义
type MyServiceServer interface {
MyMethod(context.Context, *MyRequest) (*MyResponse, error)
}
// 实际的服务实现
type MyServiceImpl struct{}
func (s *MyServiceImpl) MyMethod(ctx context.Context, req *MyRequest) (*MyResponse, error) {
defer func() {
if r := recover(); r != nil {
// 将panic信息转换为错误
err := fmt.Errorf("panic occurred: %v", r)
// 返回合适的错误给调用方
return nil, err
}
}()
// 正常业务逻辑
// 例如:
if req.DataFormatError() {
panic("data format error")
}
return &MyResponse{Message: "Success"}, nil
}
func main() {
s := grpc.NewServer()
RegisterMyServiceServer(s, &MyServiceImpl{})
// 启动服务监听
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
- 客户端:
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := NewMyServiceClient(conn)
req := &MyRequest{Data: "some data"}
resp, err := c.MyMethod(context.Background(), req)
if err != nil {
// 处理服务端返回的错误
fmt.Printf("Error from server: %v\n", err)
return
}
fmt.Printf("Response: %v\n", resp.Message)
}
通过上述方式,单个服务实例可以在发生panic
时不崩溃,并且能将错误有效传播给调用方,同时对正常业务流程的性能影响较小。