前言:最近做了小组内的分享,是关于浅谈RPC与gRpc源码分析。所以将分享的记录发到个人博客上来。接下来的几篇文章都是gRPC源码分析的个人理解,如有错误,还烦请指出。
源码分析所用案例github仓库地址:https://github.com/BansheeLW/gRPCTest
客户端建立链接发送一次请求获取响应代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16func main() {
conn, err := grpc.Dial("127.0.0.1:1235", grpc.WithInsecure())
defer conn.Close()
if err != nil{
log.Fatal(err)
}
client := server_hello_proto.NewTigerServiceClient(conn)
response, err := client.HelloTiger(context.Background(), &server_hello_proto.HelloRequest{
Name: "ban",
Age: 11,
})
if err != nil{
log.Fatal(err)
}
log.Println(response)
首先来看 Dial函数
1 | // Dial creates a client connection to the given target. |
从Dial函数声明可以看出,Dial函数实际上是对DialContext的一层封装,Dial函数接收两个参数,target是连接地址,opts是可变长参数,接受一个元素类型为DialOptionl的切片。
DialContext入参多了一个context
1 | // DialOption configures how we set up the connection. |
可以看出,DialOption是一个接口类型,有一个apply方法(接收一个dialOption的指针类型)。
接下来看一下使用的地方1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 cc := &ClientConn{
target: target,
csMgr: &connectivityStateManager{},
conns: make(map[*addrConn]struct{}),
dopts: defaultDialOptions(),
blockingpicker: newPickerWrapper(),
czData: new(channelzData),
firstResolveEvent: grpcsync.NewEvent(),
}
...
for _, opt := range opts {
opt.apply(&cc.dopts)
}
....
}
在看一下grpc中如何生成实现DialOption接口的参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// EmptyDialOption does not alter the dial configuration. It can be embedded in
// another structure to build custom dial options.
//
// This API is EXPERIMENTAL.
type EmptyDialOption struct{}
func (EmptyDialOption) apply(*dialOptions) {}
// funcDialOption wraps a function that modifies dialOptions into an
// implementation of the DialOption interface.
type funcDialOption struct {
f func(*dialOptions)
}
func (fdo *funcDialOption) apply(do *dialOptions) {
fdo.f(do)
}
func newFuncDialOption(f func(*dialOptions)) *funcDialOption {
return &funcDialOption{
f: f,
}
}
grpc包中提供了两个实现了DialOption的类型方便使用者去生成参数,特别是这个 funcDialOption 类型 ,提供了new方法得以让使用者去注入逻辑处理函数。
我们在Dial中传入的参数,grpc.WithInsecure(),其函数体的逻辑就是调用newFuncDialOption函数传入一个对clientCoon的secure属性进行设置(关闭客户端连接校验)的函数。
1 | // WithInsecure returns a DialOption which disables transport security for this |
我们再来看一下dialOptions的定义1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// dialOptions configure a Dial call. dialOptions are set by the DialOption
// values passed to Dial.
type dialOptions struct {
unaryInt UnaryClientInterceptor //一元拦截器
streamInt StreamClientInterceptor //流式拦截器
chainUnaryInts []UnaryClientInterceptor
chainStreamInts []StreamClientInterceptor
cp Compressor //压缩
dc Decompressor //解压缩
bs internalbackoff.Strategy //重试策略
block bool //拨号是否阻塞
insecure bool //安全校验
timeout time.Duration
scChan <-chan ServiceConfig
authority string
copts transport.ConnectOptions
callOptions []CallOption
// This is used by v1 balancer dial option WithBalancer to support v1
// balancer, and also by WithBalancerName dial option.
balancerBuilder balancer.Builder
channelzParentID int64
disableServiceConfig bool
disableRetry bool
disableHealthCheck bool
healthCheckFunc internal.HealthChecker
minConnectTimeout func() time.Duration
defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON.
defaultServiceConfigRawJSON *string
// This is used by ccResolverWrapper to backoff between successive calls to
// resolver.ResolveNow(). The user will have no need to configure this, but
// we need to be able to configure this in tests.
resolveNowBackoff func(int) time.Duration
resolvers []resolver.Builder
}
以上说了一堆,其实就是客户端拨号连接时,有一些默认配置,如果需要修改这些配置,就需要传递实现的了DialOption接口的类型值切片进来覆盖默认配置。gRPC包通过暴露接口的形式来实现对包内不可导出类型属性的修改。
ClientConn
整个Dial拨号调用过程中,ClientConn是最重要的类型,它是一个结构体类型。
1 | // ClientConn represents a virtual connection to a conceptual endpoint, to |
也就是说,ClientCoon是一个终端(客户端)逻辑TCP链接,用来执行RPC相关业务。它封装了一些功能:命名(目标地址)解析、建立TCP连接(带重试和回退),及建立连接过程中的一些错误处理。
ClientCoon结构体包含dialOptions类型的字段,这个dialOptions结构体类型还有一个属性ConnectOptions需要我们关注下,这个结构体类型包含了所有与服务端交流是相关的参数。
1 | // ConnectOptions covers all relevant options for communicating with the server. |
整体调用流程
1 | func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { |
所以,从以上代码可以看出,调用Dial函数实际上是调用了DialContext函数,而DialContext主要是对 ConnectCoon结构体、 dialOptions结构体(cc.dopts)及ConnectOptions结构体(cc.dopts.copts)做初始化操作。