源码架构分析图
建立 gRPC 客户端连接
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "HelloWorld"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
}
client 端连接的建立主要包括以下三步
- 创建一个客户端连接 conn
- 通过一个 conn 创建一个客户端
- 发起 rpc 调用
创建一个客户端连接 conn
import "google.golang.org/grpc"
conn, err := grpc.Dial("host:port", grpc.WithInsecure())
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
return DialContext(context.Background(), target, opts...)
}
实例化一个 ClientConn 的结构体
//ClientConn表示到概念端点的虚拟连接,以执行RPC。
//ClientConn可以根据配置、负载等自由地拥有零个或多个到端点的实际连接。
//它还可以自由地确定要使用的实际端点,并可以在每个RPC中更改它,从而允许客户端负载平衡。
//ClientConn封装了一系列功能,包括名称解析、TCP连接建立(带重试和退避)和TLS握手。
//它还通过重新解析名称并重新连接来处理已建立连接上的错误。
type ClientConn struct {
ctx context.Context
cancel context.CancelFunc
target string
parsedTarget resolver.Target
authority string
dopts dialOptions
csMgr *connectivityStateManager
balancerBuildOpts balancer.BuildOptions
blockingpicker *pickerWrapper
safeConfigSelector iresolver.SafeConfigSelector
mu sync.RWMutex
resolverWrapper *ccResolverWrapper
sc *ServiceConfig
conns map[*addrConn]struct{}
mkp keepalive.ClientParameters
curBalancerName string
balancerWrapper *ccBalancerWrapper
//...
}
type connectivityStateManager struct {
mu sync.Mutex
state connectivity.State
notifyChan chan struct{}
channelzID int64
}
连接的状态管理器,每个连接具有 IDLE
、CONNECTING
、READY
、TRANSIENT_FAILURE
、SHUTDOW N
、Invalid-State”
这几种状态。
type pickerWrapper struct {
mu sync.Mutex
done bool
blockingCh chan struct{}
picker balancer.Picker
}
pickerWrapper
是对 balancer.Picker
的一层封装,balancer.Picker
其实是一个负载均衡器,它里面只有一个 Pick
方法,它返回一个 PickResult
结构,包含 SubConn
连接。
type Picker interface {
Pick(info PickInfo) (PickResult, error)
}
// PickResult包含与为RPC选择的连接相关的信息。
type PickResult struct {
SubConn SubConn
Done func(DoneInfo)
}
client
发起一个rpc
调用之前,需要通过 balancer
去找到一个 server
的 address
,balancer
的 Picker
类返回一个 SubConn
,SubConn
里面包含了多个 server
的 address
,假如返回的 SubConn
是 READY
状态,grpc
会发送 RPC
请求,否则则会阻塞,等待 UpdateState
这个方法更新连接的状态并且通过 picker
获取一个新的 SubConn
连接。
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
cc := &ClientConn{
target: target,
csMgr: &connectivityStateManager{},
conns: make(map[*addrConn]struct{}),
dopts: defaultDialOptions(),
blockingpicker: newPickerWrapper(),
czData: new(channelzData),
firstResolveEvent: grpcsync.NewEvent(),
}
//...
cc.balancerBuildOpts = balancer.BuildOptions{
DialCreds: credsClone,
CredsBundle: cc.dopts.copts.CredsBundle,
Dialer: cc.dopts.copts.Dialer,
Authority: cc.authority,
CustomUserAgent: cc.dopts.copts.UserAgent,
ChannelzParentID: cc.channelzID,
Target: cc.parsedTarget,
}
// Build the resolver.
rWrapper, err := newCCResolverWrapper(cc, resolverBuilder)
if err != nil {
return nil, fmt.Errorf("failed to build resolver: %v", err)
}
//...
return cc, nil
}
可以看到通过 dialer
的 resolver
来进行服务发现,这里后续再单独详细讲解。
通过一个 conn 创建一个客户端
c := pb.NewGreeterClient(conn)
这一步非常简单,其实是 pb 文件中生成的代码,就是创建一个 greeterClient 的客户端。
type greeterClient struct {
cc *grpc.ClientConn
}
func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
return &greeterClient{cc}
}
发起 rpc 调用
前面在创建 Dialer
的时候,我们已经将请求的 target
解析成了 address
。之后向指定 address
发起 rpc
请求。
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
SayHello
方法是通过调用 Invoke
的方法去发起 rpc
调用, Invoke
方法如下:
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error {
opts = combine(cc.dopts.callOptions, opts)
if cc.dopts.unaryInt != nil {
return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...)
}
return invoke(ctx, method, args, reply, cc, opts...)
}
func invoke(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error {
cs, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...)
if err != nil {
return err
}
if err := cs.SendMsg(req); err != nil {
return err
}
return cs.RecvMsg(reply)
}
SendMsg
func (cs *clientStream) SendMsg(m interface{}) (err error) {
// ...
op := func(a *csAttempt) error {
err := a.sendMsg(m, hdr, payload, data)
m, data = nil, nil
return err
}
// ...
}
func (a *csAttempt) sendMsg(m interface{}, hdr, payld, data []byte) error {
// ...
if err := a.t.Write(a.s, hdr, payld, &transport.Options{Last: !cs.desc.ClientStreams}); err != nil {
if !cs.desc.ClientStreams {
// For non-client-streaming RPCs, we return nil instead of EOF on error
// because the generated code requires it. finish is not called; RecvMsg()
// will call it with the stream's status independently.
return nil
}
return io.EOF
}
// ...
}
最终是通过 a.t.Write
发出的数据写操作,a.t
是一个 ClientTransport
类型,所以最终是通过 ClientTransport
这个结构体的 Write
方法发送数据
RecvMsg
func (cs *clientStream) RecvMsg(m interface{}) error {
// ...
err := cs.withRetry(func(a *csAttempt) error {
return a.recvMsg(m, recvInfo)
}, cs.commitAttemptLocked)
// ...
}
func (a *csAttempt) recvMsg(m interface{}, payInfo *payloadInfo) (err error) {
// ...
err = recv(a.p, cs.codec, a.s, a.dc, m, *cs.callInfo.maxReceiveMessageSize, payInfo, a.decomp)
// ...
if a.statsHandler != nil {
a.statsHandler.HandleRPC(cs.ctx, &stats.InPayload{
Client: true,
RecvTime: time.Now(),
Payload: m,
Data: payInfo.uncompressedBytes,
WireLength: payInfo.wireLength,
Length: len(payInfo.uncompressedBytes),
})
}
// ...
}
func recv(p *parser, c baseCodec, s *transport.Stream, dc Decompressor, m interface{}, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor) error {
d, err := recvAndDecompress(p, s, dc, maxReceiveMessageSize, payInfo, compressor)
// ...
}
func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor) ([]byte, error) {
pf, d, err := p.recvMsg(maxReceiveMessageSize)
// ...
}
func (p *parser) recvMsg(maxReceiveMessageSize int) (pf payloadFormat, msg []byte, err error) {
if _, err := p.r.Read(p.header[:]); err != nil {
return 0, nil, err
}
// ...
}
最终还是调用了 p.r.Read
方法,p.r
是一个 io.Reader
接口, s *transport.Stream
类型
附带 HTTP2 Client 写逻辑:
欢迎指教,有疑问或者有其它感兴趣的学习方向可以在下方评论,我们将一一为您解答