gRPC Name Resolution

gRPC名称解析

概貌

gRPC支持DNS作为默认名称系统。在各种部署中使用了许多不同的名称系统。我们支持一个足够通用的API来支持一系列名称系统和相应的名称语法。各种语言的gRPC客户端库将提供插件机制,因此可以插入不同名称系统的解析器。

详细设计

名称语法

用于gRPC通道构建的完全限定的自包含名称使用以下语法:

scheme://authority/endpoint_name

在这里,scheme表示要使用的名称系统。目前我们支持以下schemes:

  • dns (例: dns://a.alidns.com/www.microp.com)
  • ipv4 (IPv4地址 例: ipv4:///110.12.92.1:443)
  • ipv6 (IPv6地址 例: ipv6:///2607:f8b0:400a:801::1001)
  • unix (unix域套接字的路径 - 仅限unix系统)

在将来,可以添加额外的方案,如etcd。

authority表示一些特定于方案的引导信息,例如,对于DNS,权限可以包括要使用的DNS服务器的IP[:端口]。通常,DNS名称可以用作authority,因为解析DNS名称的能力已经内置到所有gRPC客户机库中。

最后,endpoint_name指出一个具体的名字,在一个给定的由schemeauthority确定的名称系统中查找。端点名称的语法由使用的方案规定。

解析器插件

gRPC客户端库将使用指定的方案来选择正确的解析器插件并将它传递给完全限定的名称字符串。

解析器应该能够联系authority并获得解决方案,然后返回到gRPC客户端库。返回的内容包括:

  • 已解决的地址列表,每个地址都有三个属性:
    • 地址本身,包括IP地址和端口。
    • 指示地址是否是后端地址(即用于直接与服务器联系的地址)或均衡器地址(用于外部负载均衡正在使用的情况)的布尔值。
    • 均衡器的名称,如果地址是均衡器地址。这将用于执行同行授权。
  • 一个服务配置

插件API允许解析器持续观看端点并根据需要返回更新后的解决方案。


gRPC Go实现源码解读:

package resolver

var (
    // m 是scheme和resolver构建器的关系映射.
    m = make(map[string]Builder)
    // defaultScheme 是默认使用的scheme, 这里主要是为了方便测试, 因为有些测试依赖于target并未被解决并直接返回endpoint给拨号客户端。
    defaultScheme = "passthrough"
)

// 方便注册各种scheme对应的resolver构建器
func Register(b Builder) {
    m[b.Scheme()] = b
}

// 根据scheme查找对应的resolver构建器,未找到则返回默认构建器
func Get(scheme string) Builder {
    if b, ok := m[scheme]; ok {
        return b
    }
    if b, ok := m[defaultScheme]; ok {
        return b
    }
    return nil
}

// 重置默认构建器, gRPC默认的构建器是dns
func SetDefaultScheme(scheme string) {
    defaultScheme = scheme
}

// 定义解析器返回的地址类型
type AddressType uint8

const (
    Backend AddressType = iota // 后台服务器地址
    GRPCLB // 负载均衡器地址
)

// 表示客户端联系的服务器地址
type Address struct {
    Addr string // 用于构建connection的服务器地址
    Type AddressType // 地址类型
    ServerName string // 地址名。当用于authentication时,它是grpc负载均衡器的名称
    Metadata interface{} // 地址关联元数据信息,被用于做负载均衡决策
}

// 构建器创建解析器附属信息
type BuildOption struct {
}

// 解析器通知ClientConn解析结果的回调接口
type ClientConn interface {
    NewAddress(addresses []Address) // 通知一个新地址列表
    NewServiceConfig(serviceConfig string) // 通知一个新服务配置(json字符串)
}

// 这里对应的就是该详细设计的名称语法
type Target struct {
    Scheme    string
    Authority string
    Endpoint  string
}

// 构建器接口,方便实现新的构建器。被用于监控名称解析更新
type Builder interface {
    // 构建给定目标的解析器
    Build(target Target, cc ClientConn, opts BuildOption) (Resolver, error)
    // 指定构建的是那种Scheme类型的解析器
    Scheme() string
}

// 执行立即解析附属信息
type ResolveNowOption struct{}

// 解析器监视指定目标上的更新。更新内容包括已解决的地址列表和一个服务配置
type Resolver interface {
    // 强制执行立即解析
    ResolveNow(ResolveNowOption)
    // 关闭解析器
    Close()

    // 我感觉这里可以抽象出 Watch() 行为,用于常用case:监控更新并发送更新通知。实际并没有...
}

接下来我们挑选其中一个dns解析器实现分析:

const (
    defaultFreq = time.Minute * 30 // 默认每30分钟解析一次
    txtAttribute = "grpc_config=" // 使用dns TXT记录grpc_config属性发布服务配置
)

// dns解析器构建者
type dnsBuilder struct {
    freq time.Duration
}

func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) {
    host, port, err := parseTarget(target.Endpoint)
    if err != nil {
        return nil, err
    }

    // ip地址解析.
    if net.ParseIP(host) != nil {
        host, _ = formatIP(host)
        addr := []resolver.Address{{Addr: host + ":" + port}}
        i := &ipResolver{
            cc: cc,
            ip: addr,
            rn: make(chan struct{}, 1),
            q:  make(chan struct{}),
        }
        cc.NewAddress(addr)
        go i.watcher()
        return i, nil
    }

    // 非ip地址解析.
    ctx, cancel := context.WithCancel(context.Background())
    d := &dnsResolver{
        freq:   b.freq,
        host:   host,
        port:   port,
        ctx:    ctx,
        cancel: cancel,
        cc:     cc,
        t:      time.NewTimer(0),
        rn:     make(chan struct{}, 1),
    }

    d.wg.Add(1)
    go d.watcher()
    return d, nil
}

func (b *dnsBuilder) Scheme() string {
    return "dns"
}

// dnsResolver监视非IP目标的名称解析更新。
type dnsResolver struct {
    freq   time.Duration  // 解析频率
    host   string      // 目标主机名
    port   string      // 端口号
    ctx    context.Context // 上下文
    cancel context.CancelFunc // 上下文取消函数,被用于取消解析过程
    cc     resolver.ClientConn // 待通知的客户端连接
    rn chan struct{} // 该通道被用于强制触发ResolveNow()的立即执行
    t  *time.Timer  // 定时器,被用于控制解析频率
    wg sync.WaitGroup // 被用于等待watcher()协程执行完毕后再强制Close(),否则会出现watcher()协程和replaceNetFunc函数间数据竞争问题
}

// 往rn发送执行立即解析信号
func (d *dnsResolver) ResolveNow(opt resolver.ResolveNowOption) {
    select {
    case d.rn <- struct{}{}:
    default:
    }
}

// 关闭解析器
func (d *dnsResolver) Close() {
    d.cancel() // 取消解析过程,取消成功后ctx会收到Done()信号
    d.wg.Wait() // 等待watcher()协程执行完毕
    d.t.Stop() // 停止定时器,不再发送时钟信号
}

func (d *dnsResolver) watcher() {
    defer d.wg.Done() // 通知watcher()协程执行完毕
    for {
        select {
        case <-d.ctx.Done():
            return
        case <-d.t.C:
        case <-d.rn: // 阻塞等待时钟信号和立即执行信号
        }
        result, sc := d.lookup() // 开始解析工作

        d.t.Reset(d.freq) // 重置时钟信号器

        d.cc.NewServiceConfig(string(sc)) // 发送服务配置通知
        d.cc.NewAddress(result) // 发送地址列表通知
    }
}

// 解析目标,返回地址列表(服务器地址和负载均衡器地址)和服务配置
func (d *dnsResolver) lookup() ([]resolver.Address, string) {
    var newAddrs []resolver.Address
    newAddrs = d.lookupSRV() // 查找均衡器地址列表
    newAddrs = append(newAddrs, d.lookupHost()...) // 追加后台服务器地址列表
    sc := d.lookupTXT()
    return newAddrs, canaryingSC(sc) // canaryingSC筛选客户端是go语言并且主机名在配置列表中的服务配置,这里没搞懂chosenByPercentage这个筛选条件的含义
}

// 查找SRV记录,用于查询到的均衡器地址列表。备注: SRV记录了哪台计算机提供了哪个服务这么一个简单的信息。这里的srv记录了提供grpclb服务的主机。
func (d *dnsResolver) lookupSRV() []resolver.Address {
    var newAddrs []resolver.Address
    _, srvs, err := lookupSRV(d.ctx, "grpclb", "tcp", d.host) // 根据SRV记录,获取所有负载均衡节点名称
    if err != nil {
        grpclog.Infof("grpc: failed dns SRV record lookup due to %v.\n", err)
        return nil
    }
    for _, s := range srvs {
        lbAddrs, err := lookupHost(d.ctx, s.Target) // 根据均衡节点名称,查找对应的负载均衡器地址列表
        if err != nil {
            grpclog.Warningf("grpc: failed load banlacer address dns lookup due to %v.\n", err)
            continue
        }
        for _, a := range lbAddrs {
            a, ok := formatIP(a)
            if !ok {
                grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
                continue
            }
            addr := a + ":" + strconv.Itoa(int(s.Port))
            newAddrs = append(newAddrs, resolver.Address{Addr: addr, Type: resolver.GRPCLB, ServerName: s.Target})
        }
    }
    return newAddrs
}

// 查找TXT记录,用于返回查询到的服务配置
func (d *dnsResolver) lookupTXT() string {
    ss, err := lookupTXT(d.ctx, d.host)
    if err != nil {
        grpclog.Warningf("grpc: failed dns TXT record lookup due to %v.\n", err)
        return ""
    }
    var res string
    for _, s := range ss {
        res += s
    }

    // TXT record must have "grpc_config=" attribute in order to be used as service config.
    if !strings.HasPrefix(res, txtAttribute) {
        grpclog.Warningf("grpc: TXT record %v missing %v attribute", res, txtAttribute)
        return ""
    }
    return strings.TrimPrefix(res, txtAttribute)
}

// 查找A记录,用于返回查询到的后台服务器地址列表
func (d *dnsResolver) lookupHost() []resolver.Address {
    var newAddrs []resolver.Address
    addrs, err := lookupHost(d.ctx, d.host)
    if err != nil {
        grpclog.Warningf("grpc: failed dns A record lookup due to %v.\n", err)
        return nil
    }
    for _, a := range addrs {
        a, ok := formatIP(a)
        if !ok {
            grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
            continue
        }
        addr := a + ":" + d.port
        newAddrs = append(newAddrs, resolver.Address{Addr: addr})
    }
    return newAddrs
}

原文链接: gRPC Name Resolution & gRPC-Go1.8.0#resolver.go & gRPC-Go1.8.0#dns_resolver.go & DNS记录详解

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值