请先阅读 grpc源码注解(golang)
以下基于默认配置情况下(还有其它没有提到的配置都取默认值):
- 设置了balancer(etcd等)
- 没有设置WithBlock,即dialOptions.block = false
- 没有设置FailOnNonTempDialError,即dialOptions.copts.FailOnNonTempDialError = false
grpc.Dial 正常的执行流程,第一次进入的时候的有些逻辑是走不到或者不太重要的都舍去不表
A. grpc.Dial() 返回一个*ClientConn
-
从balancer(etcd等)返回一批地址,但是这批地址暂时还是不能用的,需要等待A225
- A2,对于每一个地址依次建立连接,循环调用cc.resetAddrConn, 即
- A3,单独goroutine监控balancer(etcd等)的变化(cc.lbWatcher()),实时更新服务集群地址,即
- 单独goroutine监控监控ServiceConfig的变化(cc.scWatcher),可以在服务启动后动态更新调用服务的配置
A2,cc.resetAddrConn针对一个地址建立连接,创建一个addrConn加入到ClientConn.conns中去,主流程分为:
- 如果这个地址已经存在连接了,先关闭掉ac.teardown
- A22,ac.resetTransport,建立一个底层连接(http2),这一步默认是goroutine出去的,不会阻塞,除非调用WithBlock;
-
A23,单独goroutine监控底层连接的状态变化(ac.transportMonitor),进行重连或者放弃
A22, ac.resetTransport里面是一个大循环,重试建立连接直到成功,除非某些条件下返回(默认情况下只有被ac.teardown了,即在balance中删除),每个循环里面:
- 如果ac.state == Shutdown ,直接返回
-
将状态改为正在连接中~~ ac.state == Connecting
- 计算sleepTime,这里是根据重试的超时策略,返回两次重试的间隔时间;即如果这次重连还是失败,会等待sleepTime才会进入下一次循环
- A224,建立一个底层的http2连接(transport.NewClientTransport);如果是临时错误, 将状态改为短暂的失败,ac.state = TransientFailure,等待sleepTime;如果是非临时错误,直接返回,默认情况下可以认为都是临时错误;
- 将状态改为readyac.state = Ready ,通知balancer(etcd等)这个地址连接ok了(up);这样下次就能从balancer 中读取到这个地址了
A224, 建立一个底层的http2连接(newHTTP2Client)
- dial一个tcp连接,失败的话,默认返回一个临时错误
- 单独goroutine,循环的读取所有的帧,并且分发到相应的流中去,如果有错误了,会有通知到A233
- 初始化http2 相关的操作(发送setting帧等)
A23,transportMonitor 是一个单独的goroutine,里面是一个循环,会监控这个连接以下几种情况:
- 如果这个连接被ac.teardown了,直接退出,不需要维护了
- 如果收到http2的goaway帧,再重新cc.resetAddrConn,即A2,然后当前直接退出;相当于用一个新的连接来替换
- 如果这个连接出错了,置为临时失败ac.state = TransientFailure ,暂时不让用,然后重试连接ac.resetTransport,即A22
A3,cc.lbWatcher监控balancer(etcd等)的变化 , 实时更新集群服务地址
- balancer.Notify() 是一个channel,每当有更新的时候,从这里读取到所有的地址(全量而非增量)
-
判断有哪些地址是新增的,哪些地址是删除掉的
-
对于新增的地址执行cc.resetAddrConn,即A2
-
对于删掉的地址直接ac.tearDown,通知balancer(etcd等)这个地址down了, 这样可能会影响到A231,A221
对于ac.tearDown,里面会关闭底层的连接,修改状态为ac.state == Shutdown,然后通知balancer(etcd等)这个地址down了,在下次轮询的时候,就不会有这个地址了;
如果这个balancer(etcd等)收到这个地址的UP的通知,表示这个地址又OK了