小米出品——gRPC Name Resolver 原理及实践

本文字数:5380 字

精读时间:12 分钟

也可在 6 分钟内完成速读

作者郑伟,小米信息技术部架构组

01

前言

gRPC Name Resolver(名称解析)是 gRPC 核心功能之一,目前大部分 gRPC Name Resolver 都采用 ETCD 来实现,通过引入 ETCD Client sdk,和 ETCD Server 之间通过 gRPC 双向流的方式进行数据交互。服务端定时上报服务名称、实例数据至 ETCD 实现服务注册,客户端进行监听指定服务名称对应实例变化来实现服务发现。

基于 ETCD 实现的 Name Resolver 已有很多相关的文章,同时 github 上也有很多相关类库,本文不做赘述。

有些公司内部已有成熟的 Name Server,比如我们小米内部 SOA 平台,已稳定运行多年。所以我们没有采用直连 ETCD 的方案,而是基于该 Name Server 来做适配。下文会给大家介绍下实现原理。

02

实现自定义 Name Resolver

gRPC 支持将 DNS 作为默认的 Name System,同时也提供了一些 API 方便开发者构建和使用自定义的 Resolver。本文所有代码均基于 [email protected] 实现。自定义 gRPC Name Resolver 源码结构大概如下所示:

整个 resolver 代码比较简单,包含三个 go 文件:resolver.goresolver_build.godail.go

ns                        # 自定义 resolver 包名
├── dial.go               # 封装了 gRPC 包的 grpc.DialContext() 方法,严格来说 dail.go 不应该放在 ns 包下,本例中这么做只是为简化包布局,方便读者理解
├── resolver.go           # 实现了 gRPC resolver 包 Resolver 接口的 nsResolver
└── resolver_builder.go   # 实现了 gRPC resolver 包 ResolverBuilder 接口的 nsResolverBuilder

03

定义 nsResolver

主要逻辑在 resolver.go :

package ns


import (
  "context"
  "encoding/json"
  "fmt"
  "strings"
  "time"


  "mypkg/internal/logz"        // 私有日志包,基于 uber 开源的 zap 实现
  sdk "mypkg/internal/soa-sdk" // 私有 ns sdk 包,封装了内部 soa 平台进行服务发现的 sdk


  _ "google.golang.org/grpc"
  "google.golang.org/grpc/resolver"
  "google.golang.org/grpc/serviceconfig"
)


const (
  // syncNSInterval 定义了从 NS 服务同步实例列表的周期
  syncNSInterval = 1 * time.Second
)


// nsResolver 实现了 resolver.Resolver 接口
type nsResolver struct {
  target    resolver.Target
  cc        resolver.ClientConn
  ctx       context.Context
  cancel    context.CancelFunc
  ...
}


// watcher 轮询并更新指定 CalleeService 服务的实例变化
func (r *nsResolver) watcher() {
  r.updateCC()
  ticker := time.NewTicker(syncNSInterval)
  for {
    select {
    // 当* nsResolver Close 时退出监听
    case <-r.ctx.Done():
      ticker.Stop()
      return
    case <-ticker.C:
      // 调用* nsResolver.updagteCC() 方法,更新实例地址
      r.updateCC()
    }
  }
}


// updateCC 更新 resolver.Resolver.ClientConn 配置
func (r *nsResolver) updateCC() {
  // 从 NS 服务获取指定 target 的实例列表
  instances, err := r.getInstances(r.target)
  // 如果获取实例列表失败,或者实例列表为空,则不更新 resolver 中实例列表
  if err != nil || len(instances.CalleeIns) == 0 {
    logz.Warn("[mis] error retrieving instances from Mis", logz.Any("target", r.target), logz.Error(err))
    return
  }
  ...


  // 组装实例列表 []resolver.Address
  // resolver.Address 结构体表示 grpc server 端实例地址
  var newAddrs []resolver.Address
  for k := range instances.CalleeIns {
    newAddrs = append(newAddrs, instances.CalleeIns)
  }
  ...


  // 更新实例列表
  // grpc 底层 LB 组件对每个服务端实例创建一个 subConnection。并根据设定的 LB 策略,选择合适的 subConnection 处理某次 RPC 请求。
  // 此处代码比较复杂,后续在 LB 相关原理文章中再做概述
  r.cc.UpdateState(resolver.State{Add
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值