grpc服务发现分析
naming
naming/naming.go
grpc自带只支持dns域名解析名字服务注册发布, 但是通过开放接口的形式, 满足用户的自定义名字发现组件
创建一个监视器,返回是Watcher, 用于监视目标域名的名称解析, 从而得到ip地址
```go
type Resolver interface {
// 返回一个watcher监听器, 可以自行通过for Next()来等待获取可用目标ip地址
Resolve(target string) (Watcher, error)
}
```
```go
type Watcher interface {
// Next blocks until an update or error happens. It may return one or more
// updates. The first call should get the full set of the results. It should
// return an error if and only if Watcher cannot recover.
Next() ([]*Update, error)
// Close closes the Watcher.
Close()
}
```
只要外部实现了此两个接口, 就能通过插件的形式使用自定义名字发现服务.
比如框架自带的dns域名解析名字服务naming/dns_resolver.go:58
通过dnsResolver和dnsWatcher两个结构,实现上面的接口, 从而实现域名解析服务
但是看源码, 不建议使用这个包了, 而是使用resolver/resolver.go
resolver
```go
//ClientConn 客户端连接包装, 维护连接的状态,可用连接列表
type ClientConn interface {
UpdateState(State)
ReportError(error)
NewAddress(addresses []Address)
NewServiceConfig(serviceConfig string)
ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult
}
//Builder 创建命名服务,返回Resolver, 可用于获取一个可用地址
type Builder interface {
Build(target Target, cc ClientConn, opts BuildOptions) (Resolver, error)
Scheme() string
}
//Resolver 监听目标服务的可用地址, 更新本地可用地址列表
type Resolver interface {
ResolveNow(ResolveNowOptions)
Close()
}
```
关键的是这三个接口, 其实实质是Builder接口, 这里使用了建造者设计模式, 通过把命名服务的创建和表示分离, 不像上面实现接口的方式, 有点粗糙. 直接把命名服务的创建放到外部创建, 使内部框架保持统一.
看例子, dns命名服务的实现, resolver/dns/dns_resolver.go:34
–>internal/resolver/dns/dns_resolver.go:100
的实现
```go
type dnsBuilder struct{}
//创建并启动一个DNS解析器,该解析器监视目标的名称解析
func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
//省略了ip解析等代码
...
// DNS address (non-IP).
ctx, cancel := context.WithCancel(context.Background())
d := &dnsResolver{
host: host,
port: port,
ctx: ctx,
cancel: cancel,
cc: cc,
rn: make(chan struct{}, 1),
disableServiceConfig: opts.DisableServiceConfig,
}
...
d.wg.Add(1)
//创建新的goroutine实时监听域名解析, 监听变更
go d.watcher()
//实现ResolveNow, 并且返回ResolveNow接口, 用于通知域名解析有变更
d.ResolveNow(resolver.ResolveNowOptions{})
return d, nil
}
// ResolveNow 更新一波监听域名的可用列表
func (d *dnsResolver) ResolveNow(resolver.ResolveNowOptions) {
select {
//通知有变更
case d.rn <- struct{}{}:
default:
}
}
//watcher 后台实时监听更新通知goroutine
func (d *dnsResolver) watcher() {
defer d.wg.Done()
for {
//收到通知,来一波域名解析变更更新
select {
case <-d.ctx.Done():
return
case <-d.rn:
}
//域名解析
state, err := d.lookup()
if err != nil {
d.cc.ReportError(err)
} else {
//更新客户端连接状态
d.cc.UpdateState(*state)
}
// Sleep to prevent excessive re-resolutions. Incoming resolution requests
// will be queued in d.rn.
t := time.NewTimer(minDNSResRate)
select {
case <-t.C:
case <-d.ctx.Done():
t.Stop()
return
}
}
}
```
通过看dns resolver的实现, 可用得出在实现Build接口时, 需要有实时的watch服务可用地址这个过程, 看dns是使用创建一个goroutine来等待ResolveNow的通知, 再发起一次此连接的命名服务可用地址列表的更新.
因此, 得出Build是用于创建一个命名服务, ResolveNow用于发起一次连接可用服务地址列表的通知, Build内部的watch goroutine通知会拉取新的可用地址列表, 刷新本地服务可用地址列表缓存.
ClientConn接口用于维护客户端连接conn的状态, 服务可用地址列表等. ClientConn被ccResolverWrapper包装了, ccResolverWrapper是ClientConn的包装器
ClientConn结构, google.golang.org/grpc@v1.27.0/clientconn.go:473
是grpc客户端的连接对象,
```go
//google.golang.org/grpc@v1.27.0/clientconn.go:473
type ClientConn struct {
ctx context.Context
cancel context.CancelFunc
...
csMgr *connectivityStateManager
blockingpicker *pickerWrapper
resolverWrapper *ccResolverWrapper
balancerWrapper *ccBalancerWrapper
...
}
```
包含了ccResolverWrapper包装器, 因此ClientConn结构具有命名服务的能力.
```go
//ClientConn grpc客户端抽象结构
func (cc *ClientConn) resolveNow(o resolver.ResolveNowOptions) {
cc.mu.RLock()
r := cc.resolverWrapper
cc.mu.RUnlock()
if r == nil {
return
}
go r.resolveNow(o)
}
```
===>
```go
//ccBalancerWrapper 负载均衡器
func (ccb *ccBalancerWrapper) ResolveNow(o resolver.ResolveNowOptions) {
ccb.cc.resolveNow(o)
}
```
以上就是grpc的命名服务, 从第一版到最新版, 和被谁使用的分析…