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 resolvervar (// 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 uint8const (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    stringAuthority stringEndpoint  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():returncase <-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.AddressnewAddrs = 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 stringfor _, 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.Addressaddrs, 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.portnewAddrs = 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记录详解

gRPC Name Resolution相关推荐

  1. grpc 传递上下文_grpc 源码笔记 02:ClientConn

    上篇笔记中梳理了一把 resolver 和 balancer,这里顺着前面的流程走一遍入口的 ClientConn 对象. ClientConn // ClientConn represents a ...

  2. GRPC golang版源码分析之客户端(二)

    Table of Contents 1. 前言 2. 负载均衡 3. 相关链接 1 前言 前面一篇文章分析了一个grpc call的大致调用流程,顺着源码走了一遍,但是grpc中有一些特性并没有进行分 ...

  3. java如何通过grpc连接etcd_grpc通过 etcd 实现服务发现与注册-源码分析

    介绍 下面介绍 jupiter-0.2.7 版本中 grpc 通过 etcd 实现服务发现与注册. 服务发现与注册的实现解析 服务注册 服务注册的流程图: etcd的服务注册代码模块在 jupiter ...

  4. GRPC(5):名字解析器

    上一章学习了 gRPC 截止时间,多路复用和元数据等特性,今天学习名字解析器j及其实现原理. 名字解析器(Name Resolver) 名字解析器用作将给定的服务名称解析为对应的后端 IP 地址和端口 ...

  5. RPC 笔记(03)— gRPC 概念、安装、编译、客户端和服务端示例

    1. gRPC 概念 gRPC 是 Google 开源的一款高性能的 RPC 框架.GitHub 上介绍如下: gRPC is a modern, open source, high-performa ...

  6. etcd 笔记(04)— etcd 网关与 gRPC 网关

    1. etcd 网关 etcd 网关是一个简单的 TCP 代理,可将网络数据转发到 etcd 集群.网关是无状态且透明的,它既不会检查客户端请求,也不会干扰集群响应,支持多个 etcd 服务器实例,并 ...

  7. Google Pixel 超分辨率--Super Resolution Zoom

    Google Pixel 超分辨率–Super Resolution Zoom Google 的Super Res Zoom技术,主要用于在zoom时增强画面细节以及提升在夜景下的效果. 文章的主要贡 ...

  8. php 长连接心跳_支持gRPC长链接,深度解读Nacos2.0架构设计及新模型

    作者 | 杨翊(席翁) Nacos PMC 来源|阿里巴巴云原生公众号 Nacos 简介 Nacos 在阿里巴巴起源于 2008 年五彩石项目,该项目完成了微服务拆分和业务中台建设,随着云计算和开源环 ...

  9. SpringBoot整合Grpc实现跨语言RPC通讯

    什么是gRPC gRPC是谷歌开源的基于go语言的一个现代的开源高性能RPC框架,可以在任何环境中运行.它可以有效地连接数据中心内和跨数据中心的服务,并提供可插拔的支持,以实现负载平衡,跟踪,健康检查 ...

  10. gRPC简介及简单使用(C++)

    gRPC是一个现代的.开源的.高性能远程过程调用(RPC)框架,可以在任何平台运行.gRPC使客户端和服务器端应用程序能够透明地进行通信,并简化了连接系统的构建.gRPC支持的语言包括C++.Ruby ...

最新文章

  1. Win7无法安装程序提示Installer integrity check has failed的解决方法
  2. python真的那么强大嘛-python强大吗
  3. 静态编译qemu_使用QEMU chroot进行固件本地调试
  4. ORACLE TEXT DATASTORE PREFERENCE(三)
  5. wine的sys文件具体位置
  6. golang中创建logger时候踩过的坑
  7. SAP License:SAP中MM与财务的接口配置
  8. 菜鸟网工工作中对Linux系统的一点体会
  9. Q107:Linux系统下GDB对PBRT-V3进行debug
  10. Android8.0使用ninja模块编译Settings
  11. python3编码(encode,decode)
  12. 微信群怎么添加二狗机器人?
  13. linux HUSTOJ 一些页面修改
  14. 手动安装VMware Tools
  15. 软件测试师的工作流程是什么?
  16. 用GitHub做一份精美的在线简历
  17. 公司企业如何制作微信小程序店铺?
  18. C++ 拉格朗日插值法优化 DP
  19. 目标检测+图像分割项目
  20. oracle avg() 绝对平均值

热门文章

  1. java 过载_过载保护【转载】
  2. mysql聚集索引与非聚集索引
  3. w ndows 10关机快捷键,win10怎么关机 win10关机快捷键大全【图文演示】
  4. 优麒麟使用教程第四期:Linux平台U盘启动盘制作(建议收藏)
  5. CDN加速解决VSCode下载速度慢的问题
  6. C++ sting字符串函数详解
  7. html代码广告代码大全,强制弹窗广告代码大全.doc
  8. 传奇服务器文件组成,【教程】传奇服务端(版本)的结构以及重要文件功能的概述-A02...
  9. HDMI九画面视频分割器(MT-SW091)
  10. 【老生谈算法】matlabBOOST电路的设计与仿真——BOOST电路