grpc 最佳实践

grpc 最常见的使用场景是:微服务框架下。多种语言只见的交互,将手机服务、浏览器连接至后台。产生高校的客户端库。(维基百科)

低延迟,高可用,分布式系统;移动客户端和云端通讯;跨语言协议;独立组件方便扩展,例如认证,负载均衡,和监控(来自grpc官方文档,最后一项翻译可能不准确)。

grpc的创建是非常简单的:

1. proto文件

math.proto

Service Math {
 rpc Div (Request) returns (Response)()
}

message Request {
 int64 divedend =1 ;
 int64 divisor =2;
}

message Response {
 int64 quotient =1 ;
 int64 remainder =2;
}

2.生成服务端代码

math.go

type server struct{}

func (s *server) Div (ctx context.Context, in *pb.Request)
(*pb.Response,error) {

  n,d := in.Dividened, in.Divisor
  if d == 0 {
    return nil , status. Errof(codes.InvalidArgumnet, "division by 0")
  }
  return &pb.Response{
    Quotient: int64(n / d)
    Reainder: int64(n % d)
  },nil
}

func main() {
  lis, _ := net.Listen("tcp",port)
  s:= grpc.NewServer()
  pb.RegisterMathServer(s,&server())
  s.Serve(lis) 
}

  服务的创建是非常容易的但是,真正需要在生产环境中用好还是非常有挑战的:

  1. 可靠性
  2. 安全性
  3. 性能
  4. 错误处理
  5. 监控
  6. 负载均衡
  7. 日志
  8. QOS
  9. ...

API 设计之幂等

  需要实现接口的可重入。

例如转钱:

  不好的设计:

message Request {
 string from =1;
 string to= 2;
 float amount = 3;
}

message Response {
 int64 confirmations= 1;
}

好的设计:

message Request {
 string from =1;
 string to= 2;
 float amount = 3;
 int64 timestamp =4 ;
}

message Response {
 //每次相同的请求的到相同的结果
 int64 confirmations= 1;
}

API设计之性能

重复的地方:

  Requests: 有可能有无限多的请求, 需要设置限制;

  Response: 需要支持分页;

避免耗时较的操作:

  时间越长,重试概率越大;

  在后台处理,异步发送执行结果。(callback , email , pubsub, etc), 或者tracking token。

API设计之默认值

  定义更加敏感的默认信息。

    尽量将未知的,未定义的 作为默认值

  向后兼容

API设计之错误处理

  错误:

    是grpc独立的一个类别,不要把错误信息放在响应内容中。否则判断是否成功的逻辑会非常复杂。因为需要读出响应内容进行判断。不如一开始就将错误统一定义好。

  避免批量运行独立的操作:

    例如: 一次性更新多个表。

    错误处理非常复杂。

    如果确实需要,使用stream 或者multiple call。

错误处理之 - DON'T PANIC

尽最大努力优雅的处理错误

 panic只适合机器内部故障:内存泄露,内存用完,imminetn data corruption

 除了上述error, 剩下都返回i给调用者。

 当心空指针。使用proto的getter方式是nil安全的。

错误处理之-合理转换

别直接把其他服务的错误返回。这样不利于调试

res , err := client.Call(...)
if err!= nil {
  s,ok := status.FromError(err)
  if !ok {
    return status.Errorf(codes.Internal, "client.Call:unkown error:%v",err)
  }
  switch s.Code() {
    case code.InvalidArgument:
      return ....
  }
}

DeadLines

  客户端:

  一般需要设置,这样客户端才能知道什么时候放弃操作。一定要使用DEADLINE

  使用带有deadline的ctx

  res , err := client.Call( ctx, req)

 服务端:

 也比较关注DEADLINE

   超市时间太短:不够执行对应操作,过早失败;

   超时时间太长:消耗用户的其他资源。

func (s *Server) MyRequestHandler(ctx contes.Contex, ...) (*Res,error) {
  d,ok := ctx.Deadline()
  if !ok {
    return staus.Error {...}
  }
  timeout := d.Sub(time.Now())
  if timeout < 5*time.Second || timeout > 30*time.Secone {
    return status.Error (...)
  }
 
}

如果可以的话,尽量为不同请求创建各自的ctx 和 ctx的超时时间。

限流

  服务端“

import "golang.org/x/time/rate"

...
s := grpc.NewServer (grpc.InTapHadle(rateLimiter))
...

func rateLimiter (ctx context.Context, info *tap.Info) (contex.Context, error) {
  if m[user] == nil { // 
    m[user] = rate.newLimiter(5,1)
  }
  if !m[user].Allow() {
    return nil, status.Eoorof(codes....)
  }
  return ctx, nil
}

 好的客户端也需要限流

import "golang.org/x/time/rate"

...
s := grpc.NewServer (grpc.InTapHadle(rateLimiter))
...

func Myhandler (ctx contex.Context , req Request) (Response,err) {
  if err := limiter.Wait(ctx); err != nil {
    return nil , err
  }
  return c.Call(ctx, req)
}

重试

 官方特性说明: gRRFC A6

 . 可以通过服务端的配置

  支持: 按失败顺序重试 或者 并发重试。 

  同样需要考虑ctx中过期时间的问题。

通用的框架

func (c *client) ChildRpc (ctx contex.Context , name string , f func(contes.Contex) error){
  for attempts := 1; attempts <= c.maxAttempts; attempts++ {
    if err := c.limiter.Wait(ctx); err != nil {
      return c.limiterErr(...)
   }
    if err := f(ctx); err == nil {
     return nil
   } else if !c.retry (err) {
     return c.convertErr (name , err, attensm)
   }
  }
  return c.TomanyRetry
})


var res Response

err := c.ChildRpc (ctx , "SendMony", func(ctx context.Contex)(err error){
  res , err := sendMondClient.SendMoney(ctx, req)
  return
})

内存管理

方法一:

import "golang.org/x/net/netutil"

listener := netutil.LimitListener(listener, connectionLimit)

grpc.NewServer(grpc.MaxConcurrentStream(streamsLimit))

 

方法二:

  user Tap Handler, 当过多的rpc连接或者内存比较低。

方法三:

  health 报告机制

限制服务的请求数据大小:

 grpc.NewServer(grpc.MaxRecvMsgSize(4096 /* bytes*/))

小的请求可能有大的数据响应:

  eg : database query

  api design issue:

     使用 streaming response 

     按照数据最大限制进行分页。

日志

  多打印吧,方便调试,以及监控发出警告

监控

  。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值