前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang源码分析:grpc 链接池(1)

golang源码分析:grpc 链接池(1)

作者头像
golangLeetcode
发布2023-03-01 16:18:01
9640
发布2023-03-01 16:18:01
举报
文章被收录于专栏:golang算法架构leetcode技术php

开始源码分享之前,我们先问自己几个问题:

1,grpc client和server之间是长链接还是短链接?

2,我们通过grpc.Dial拿到的*ClientConn对应的是一个连接么?

3,grpc.Dial 拿到的连接应该什么时候释放?

4,同一个*ClientConn,多次rpc请求,中间连接会断开么?

5,如果一个连接不断开,心跳机制是在哪里实现的?需要我们自己实现吗?

上面几个问题,看着都觉得这么简单还用问?但是仔细一想,内心还是有些不太确定。因为没有分析过源码!下面我们带着问题来进行研究。我们生成一段代码,启动一个server

代码语言:javascript
复制
syntax = "proto3";

option go_package = "grpc/conn/hello";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayHello1 (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
代码语言:javascript
复制
protoc --go_out=./ --go_opt=paths=import --go-grpc_out=./  --go-grpc_opt=paths=import grpc/conn/helloworld.proto

注意下,这里的最终生成路径是指定的out路径和package里的路径拼接而成的。然后我们启动我们的server

代码语言:javascript
复制
package main

import (
  helloworld "learn/grpc/conn/hello"
  "log"
  "net"

  "google.golang.org/grpc"
)

func main() {
  srv := grpc.NewServer()
  helloworld.RegisterGreeterServer(srv, &helloworld.UnimplementedGreeterServer{})
  listener, err := net.Listen("tcp", ":12345")
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }

  err = srv.Serve(listener)
  if err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

为了验证我们的疑问,我们分别启动三个客户端,第一个客户端每次发完一个请求,断开连接重连;第二个客户端在一个连接上发多个请求;第三个客户端发完一个请求后sleep 20s再发下一个请求,看看连接是否会断开。三个客户端的代码分别如下:

代码语言:javascript
复制
package main

import (
  "context"
  "fmt"
  "log"

  helloworld "learn/grpc/conn/hello"

  "google.golang.org/grpc"
)

func main() {
  for i := 0; i < 2; i++ {
    conn, err := grpc.Dial("127.0.0.1:12345", grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
      log.Fatalf("did not connect: %v", err)
    }
    client := helloworld.NewGreeterClient(conn)

    resp, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "hello world!"})
    fmt.Println(i+1, "个", resp, err)
    resp, err = client.SayHello1(context.Background(), &helloworld.HelloRequest{Name: "hello world!"})
    fmt.Println(i+1, "个", resp, err)
    if err != nil {
      log.Println("could not greet", err)
      //  log.Fatal("hhhhhh") //exit status 1
    }
    conn.Close()
  }
}
代码语言:javascript
复制
package main

import (
  "context"
  "fmt"
  "log"

  helloworld "learn/grpc/conn/hello"

  "google.golang.org/grpc"
)

func main() {
  conn, err := grpc.Dial("127.0.0.1:12345", grpc.WithInsecure(), grpc.WithBlock())
  defer conn.Close()
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  client := helloworld.NewGreeterClient(conn)
  for i := 0; i < 2; i++ {
    resp, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "hello world!"})
    fmt.Println(i+1, "个", resp, err)
    resp, err = client.SayHello1(context.Background(), &helloworld.HelloRequest{Name: "hello world!"})
    fmt.Println(i+1, "个", resp, err)
    if err != nil {
      log.Println("could not greet", err)
      //  log.Fatal("hhhhhh") //exit status 1
    }
  }
}
代码语言:javascript
复制
package main

import (
  "context"
  "fmt"
  "log"
  "time"

  helloworld "learn/grpc/conn/hello"

  "google.golang.org/grpc"
  "google.golang.org/grpc/keepalive"
)

func main() {
  conn, err := grpc.Dial("127.0.0.1:12345", grpc.WithInsecure(), grpc.WithBlock(), grpc.WithKeepaliveParams(keepalive.ClientParameters{
    Time:                time.Duration(time.Second), //最小10s
    Timeout:             time.Duration(time.Second),
    PermitWithoutStream: false,
  }))
  defer conn.Close()
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  client := helloworld.NewGreeterClient(conn)
  for i := 0; i < 2; i++ {
    resp, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "hello world!"})
    fmt.Println(i+1, "个", resp, err)
    resp, err = client.SayHello1(context.Background(), &helloworld.HelloRequest{Name: "hello world!"})
    fmt.Println(i+1, "个", resp, err)
    if err != nil {
      log.Println("could not greet", err)
      //  log.Fatal("hhhhhh") //exit status 1
    }
    time.Sleep(20 * time.Second)
  }
}

然后我们用wireshark抓包测试下,看下网络情况,首先到https://www.wireshark.org/#download 下载,并安装,默认情况下,没法抓127.0.0.1的包的,启动的时候需要选择Loopback lo0;然后在抓包的时候过滤我们的服务的端口:

代码语言:javascript
复制
tcp and ip.addr==127.0.0.1 and tcp.port==12345

对应的我们可以得到3个tcp连接发包记录:

对于每次发完请求都断开连接的情形,我们可以看到两次三次握手和四次挥手的记录。

对于发请求过程中不主动close ClientConn的场景,对应的只有一次三次握手和四次挥手的记录,说明grpc在发多个请求的时候并不是发完一个请求就断开连接了,而是保持了底层的http2长链接,因此我们在使用grpc的时候需要注意两个问题:A,如果链接能复用,尽量不要一个请求处理完就断开重连,这样每次都要连接的代价比较大。B,打开的连接记得要关闭,不要不断建立新连接不断开,否则有泄漏风险。

如果链接不释放会怎么样呢?从第三个场景的抓包我们可以看到,如果一个连接长时间没有请求发送,连接并不会断开,而是发送keep-alive请求,图中我们可以看到,超过时间阈值没有请求的时候,客户端和服务端分别发送了一个keep-alive的包,对端也发送了一个响应的心跳,说明连接并不会断开,tcp层会有保活机制。

那么我们对于dial,我们拿到的是一个连接么,答案是否定的,对应的应该是一个连接池,grpc的SubConn对应的才是连接池中的一个连接。http层有心跳保活机制吗?答案是有的,具体是在哪里实现的,我们在下一篇中结合源码详细介绍。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档