Golang中的GRPC连接管理

时间:2019-05-09 20:27:45

标签: go grpc grpc-go

我对GRPC相对较新,并且想要确保我正确地使用golang进行了连接管理。我不想为每个呼叫都创建一个新的连接,但是我也不想在扩展时创建瓶颈。

我所做的是在init函数中创建一个连接:

var userConn *grpc.ClientConn
var userServiceName string

func init() {
    userServiceName := os.Getenv("USER_SERVICE_URL")
    if userServiceName == "" {
        userServiceName = "localhost"
    }
    logging.LogDebug("userClient:  Connecting to: "+userServiceName, "")
    tempConn, err := grpc.Dial(userServiceName, grpc.WithInsecure())
    if err != nil {
        logging.LogEmergency("account_user_client.Init()  Could not get the connection.  "+err.Error(), "")
        return
    }
    userConn = tempConn
}

然后针对每个功能,我将使用该连接来创建客户端:

c := user.NewUserClient(userConn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.GetUserFromTokenID(ctx, &user.GetUserFromTokenRequest{TransactionID: transactionID, OathToken: *oathToken})
//Handle Error and Response

这是处理grpc连接的可接受方法吗?有更好的建议吗?

非常感谢您。

2 个答案:

答案 0 :(得分:3)

是的,每个服务只有一个GRPC客户端连接是可以的。而且,我在这里看不到其他选择。 GRPC在后台进行了所有繁重的工作:例如,您不需要编写自己的客户端连接池(就像对典型RDBMS所做的那样),因为它不会提供比单个GRPC连接更好的结果。

但是我建议您避免使用全局变量和init函数,尤其是对于网络设置。另外,您不需要在每次向GRPC服务发布请求时都创建GRPC客户端(c := user.NewUserClient(userConn)):这只是垃圾收集器的一项额外工作,您可以在创建垃圾客户端时创建唯一的客户端实例。应用程序启动。

更新

假设您正在编写服务器应用程序(因为可以从您在远程GRPC服务上调用的方法中看到),您可以简单地定义一个类型,该类型将包含与整个应用程序具有相同生存期的所有对象本身。按照传统,这些类型通常被称为“服务器上下文”,尽管由于Go在其标准库中具有context的非常重要的概念而使它有点混乱。

   // this type contains state of the server
   type serverContext struct {
       // client to GRPC service
       userClient user.UserClient

       // default timeout
       timeout time.Duration

       // some other useful objects, like config 
       // or logger (to replace global logging)
       // (...)       
   }

   // constructor for server context
   func newServerContext(endpoint string) (*serverContext, error) {
       userConn, err := grpc.Dial(endpoint, grpc.WithInsecure())
       if err != nil {
           return nil, err
       }
       ctx := &serverContext{
          userClient: user.NewUserClient(userConn),
          timeout: time.Second,
       }
       return ctx, nil
   }

   type server struct {
       context *serverContext
   }

   func (s *server) Handler(ctx context.Context, request *Request) (*Response, error) {
       clientCtx, cancel := context.WithTimeout(ctx, time.Second)
       defer cancel()
       response, err := c.GetUserFromTokenID(
          clientCtx, 
          &user.GetUserFromTokenRequest{
              TransactionID: transactionID,
              OathToken: *oathToken,
          },
       )
       if err != nil {
            return nil, err
       }
       // ...
   }

   func main() {
       serverCtx, err := newServerContext(os.Getenv("USER_SERVICE_URL"))
       if err != nil {
          log.Fatal(err)
       }
       s := &server{serverCtx}

       // listen and serve etc...
   }

细节可能会根据您的实际工作而变化,但是我只是想表明,将应用程序的状态封装在不同类型的实例中而不是感染全局名称空间要好得多。

答案 1 :(得分:1)

有几件事使这个实现有效。

  1. gRPC 通道(即 c 中的 c := user.NewUserClient(userConn))由 http/2 连接支持。当连接关闭或死机时,它会自动重新连接或重试连接。

  2. http/2 支持在单个连接中同时发送消息。考虑到这种情况,服务订单每次获取一个产品库存、产品库存更新和产品优惠券更改为服务产品。三个grpc请求可以复用单个http/2连接,grpc会并发处理数据交换。所以可以只使用一个连接,而不是创建三个连接(如 http/1)来实现这一点。

  3. 为了避免过早优化,一个连接应该可以启动一个服务。以防将来出现池性能问题,请考虑为热点 grpc 请求创建一个单独的 tcp 连接(然后单独的 http/2 连接)。

保持连接处于活动状态可能会很好,以防某些代理可能会终止空闲连接。在 https://github.com/grpc/grpc-go/blob/master/Documentation/keepalive.md 查看更多解释,在 https://github.com/grpc/grpc-go/tree/master/examples/features/keepalive 查看示例代码。

对于 gRPC 与 http/2 连接,检查 https://grpc.io/blog/grpc-on-http2/#a-robust-high-performance-protocolhttps://www.cncf.io/blog/2018/07/03/http-2-smarter-at-scale/