This document covers name resolution in gRPC-Go, including built-in resolvers, custom resolver implementation, target syntax, and service config integration.
The resolver package defines APIs for name resolution in gRPC. Resolvers translate target names into addresses and service configurations.
import "google.golang.org/grpc/resolver"All APIs in the resolver package are experimental.
gRPC target names follow the syntax defined in gRPC naming spec:
scheme://authority/endpointExamples:
dns:///example.com:8080 - DNS resolverdns://8.8.8.8/example.com:8080 - DNS with custom DNS serverunix:///tmp/grpc.sock - Unix domain socketunix-abstract:my-service - Unix abstract namespace socketxds:///myservice - xDS-based resolutionexample.com:8080 - Uses default scheme (typically "dns")// Target represents a parsed dial target
type Target struct {
// URL contains the parsed dial target with optional default scheme
URL url.URL
}
// Endpoint retrieves endpoint without leading "/" from URL.Path or URL.Opaque
func (t Target) Endpoint() string
// String returns canonical string representation
func (t Target) String() string// SetDefaultScheme sets the default scheme used by grpc.NewClient
// Must be called during initialization (in init()) - not thread-safe
// Initial default is "dns"
func SetDefaultScheme(scheme string)
// GetDefaultScheme gets the current default scheme
func GetDefaultScheme() stringExample:
import (
"google.golang.org/grpc"
"google.golang.org/grpc/resolver"
)
func init() {
// Change default scheme to passthrough
resolver.SetDefaultScheme("passthrough")
}
// Now "localhost:8080" uses passthrough instead of dns
conn, err := grpc.NewClient("localhost:8080", opts...)The DNS resolver is the default resolver. It performs DNS lookups to resolve hostnames.
import (
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/resolver/dns"
)
// Configure DNS resolver parameters
func SetMinResolutionInterval(d time.Duration)
func SetResolvingTimeout(timeout time.Duration)
// SetMinResolutionInterval prevents excessive re-resolution
// Must be called at application startup
// Example: prevent DNS lookups more frequent than every 5 seconds
dns.SetMinResolutionInterval(5 * time.Second)
// SetResolvingTimeout sets maximum duration for DNS resolution
// Default is 30 seconds
dns.SetResolvingTimeout(10 * time.Second)DNS Resolver Examples:
import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// Basic DNS resolution
creds, _ := credentials.NewClientTLSFromFile("ca.pem", "")
conn, err := grpc.NewClient("dns:///example.com:443",
grpc.WithTransportCredentials(creds))
// DNS with service name (SRV records)
conn, err := grpc.NewClient("dns:///myservice.example.com",
grpc.WithTransportCredentials(creds))
// DNS with custom nameserver
conn, err := grpc.NewClient("dns://8.8.8.8/example.com:443",
grpc.WithTransportCredentials(creds))
// Multiple A/AAAA records -> used with load balancer
serviceConfig := `{"loadBalancingPolicy":"round_robin"}`
conn, err := grpc.NewClient("dns:///example.com:443",
grpc.WithDefaultServiceConfig(serviceConfig),
grpc.WithTransportCredentials(creds))The passthrough resolver passes the target string directly to the dialer without modification.
// Passthrough - no resolution
conn, err := grpc.NewClient("passthrough:///localhost:8080",
grpc.WithTransportCredentials(insecure.NewCredentials()))
// Useful for direct IP connections
conn, err := grpc.NewClient("passthrough:///10.0.0.1:8080",
grpc.WithTransportCredentials(creds))The manual resolver allows programmatic control of resolved addresses, useful for testing:
import (
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
)
// Create manual resolver
r := manual.NewBuilderWithScheme("mytest")
resolver.Register(r)
// Use in dial
conn, err := grpc.NewClient("mytest:///unused",
grpc.WithResolvers(r),
grpc.WithTransportCredentials(insecure.NewCredentials()))
// Manually update addresses
r.UpdateState(resolver.State{
Addresses: []resolver.Address{
{Addr: "localhost:8080"},
{Addr: "localhost:8081"},
},
})
// Update again later
r.UpdateState(resolver.State{
Addresses: []resolver.Address{
{Addr: "localhost:8080"},
{Addr: "localhost:8082"},
},
})type Resolver struct {
// BuildCallback is called when Build method is called
BuildCallback func(resolver.Target, resolver.ClientConn, resolver.BuildOptions)
// UpdateStateCallback is called when UpdateState is called
UpdateStateCallback func(err error)
// ResolveNowCallback is called when ResolveNow is called
ResolveNowCallback func(resolver.ResolveNowOptions)
// CloseCallback is called when Close is called
CloseCallback func()
}
// NewBuilderWithScheme creates a new manual resolver builder
// Each instance should only be used with a single ClientConn
func NewBuilderWithScheme(scheme string) *Resolver
// Build returns itself (implements Builder interface)
func (r *Resolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error)
// Close is a no-op for Resolver and is provided to implement the resolver.Resolver interface
func (r *Resolver) Close()
// InitialState adds initial state to resolver and is used to update the state of ClientConn
func (r *Resolver) InitialState(s resolver.State)
// ResolveNow is a no-op for Resolver
func (r *Resolver) ResolveNow(resolver.ResolveNowOptions)
// Scheme returns the scheme name used by this resolver
func (r *Resolver) Scheme() string
// UpdateState updates the state of ClientConn with s
func (r *Resolver) UpdateState(s resolver.State) errortype Resolver interface {
// ResolveNow is called by gRPC to attempt to resolve the target name again
// This is a hint - resolver can ignore if not necessary
// May be called multiple times concurrently
ResolveNow(ResolveNowOptions)
// Close closes the resolver
Close()
}
type ResolveNowOptions struct{}type Builder interface {
// Build creates a new resolver for the given target
// gRPC dial calls Build synchronously and fails if error is not nil
Build(target Target, cc ClientConn, opts BuildOptions) (Resolver, error)
// Scheme returns the scheme supported by this resolver
// Should not contain uppercase characters
Scheme() string
}
// Register registers the resolver builder
// Builder.Scheme() is used as the registered scheme (case-sensitive)
// Must only be called during initialization (in init()) - not thread-safe
func Register(b Builder)
// Get returns the resolver builder registered with the given scheme
// Returns nil if no builder is registered
func Get(scheme string) Buildertype BuildOptions struct {
// DisableServiceConfig indicates whether resolver should fetch service config
DisableServiceConfig bool
// DialCreds is transport credentials from ClientConn
// May be used if name resolution service requires same credentials
DialCreds credentials.TransportCredentials
// CredsBundle is credentials bundle from ClientConn
CredsBundle credentials.Bundle
// Dialer is custom dialer from ClientConn
Dialer func(context.Context, string) (net.Conn, error)
// Authority is the effective authority of the ClientConn
Authority string
// MetricsRecorder for recording metrics
MetricsRecorder stats.MetricsRecorder
}The ClientConn interface provided to resolvers:
type ClientConn interface {
// UpdateState updates the state of the ClientConn
// If error returned, resolver should try to resolve again
// Use backoff timer to prevent overloading
// If resolved State is same as last reported, calling UpdateState can be omitted
UpdateState(State) error
// ReportError notifies ClientConn that Resolver encountered an error
// ClientConn forwards this to the load balancing policy
ReportError(error)
// NewAddress notifies ClientConn of new resolved addresses
// Deprecated: Use UpdateState instead
NewAddress(addresses []Address)
// ParseServiceConfig parses the provided service config
// Returns parsed config object
ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult
}type State struct {
// Addresses is the latest set of resolved addresses for the target
// Soon to be deprecated and replaced by Endpoints
Addresses []Address
// Endpoints is the latest set of resolved endpoints for the target
// If resolver produces Endpoints but not Addresses, must ensure
// LB policies selected support Endpoints
Endpoints []Endpoint
// ServiceConfig contains result from parsing latest service config
// nil indicates no service config or resolver doesn't provide configs
ServiceConfig *serviceconfig.ParseResult
// Attributes contains arbitrary data about the resolver
// Intended for consumption by load balancing policy
Attributes *attributes.Attributes
}type Address struct {
// Addr is the server address for connection establishment
Addr string
// ServerName is the name of this address
// If non-empty, used as TLS authority instead of hostname from dial target
// WARNING: ServerName must only be populated with trusted values
// Insecure to populate with untrusted data - bypasses TLS authority checks
ServerName string
// Attributes contains arbitrary data about this address
// Intended for consumption by SubConn
Attributes *attributes.Attributes
// BalancerAttributes contains arbitrary data for LB policy
// Does not affect SubConn creation, connection establishment, etc.
// Deprecated: when Address is inside Endpoint, don't use this field
BalancerAttributes *attributes.Attributes
// Metadata associated with Addr, used for load balancing decisions
// Deprecated: use Attributes instead
Metadata any
}
// Equal returns whether addresses are identical
// Metadata compared directly, not with recursive introspection
func (a Address) Equal(o Address) bool
// String returns JSON formatted string representation
func (a Address) String() stringtype Endpoint struct {
// Addresses contains list of addresses to access this endpoint
Addresses []Address
// Attributes contains arbitrary data about this endpoint
// Intended for consumption by LB policy
Attributes *attributes.Attributes
}
// ValidateEndpoints validates endpoints from petiole policy's perspective
// Petiole policies should call this before calling into children
func ValidateEndpoints(endpoints []Endpoint) errortype AddressMapV2[T any] struct {
// Has unexported fields
}
// NewAddressMapV2 creates a new AddressMapV2
// Map takes into account Attributes but ignores BalancerAttributes, Metadata, Type
// Not thread-safe - multiple accesses may not be performed concurrently
func NewAddressMapV2[T any]() *AddressMapV2[T]
// Set updates or adds value for address
func (a *AddressMapV2[T]) Set(addr Address, value T)
// Get returns value for address if present
func (a *AddressMapV2[T]) Get(addr Address) (value T, ok bool)
// Delete removes address from map
func (a *AddressMapV2[T]) Delete(addr Address)
// Len returns number of entries
func (a *AddressMapV2[T]) Len() int
// Keys returns slice of all current map keys
func (a *AddressMapV2[T]) Keys() []Address
// Values returns slice of all current map values
func (a *AddressMapV2[T]) Values() []Ttype EndpointMap[T any] struct {
// Has unexported fields
}
// NewEndpointMap creates a new EndpointMap
// Keyed on unordered set of address strings within endpoint
// Not thread-safe
func NewEndpointMap[T any]() *EndpointMap[T]
// Set updates or adds value for endpoint
func (em *EndpointMap[T]) Set(e Endpoint, value T)
// Get returns value for endpoint if present
func (em *EndpointMap[T]) Get(e Endpoint) (value T, ok bool)
// Delete removes endpoint from map
func (em *EndpointMap[T]) Delete(e Endpoint)
// Len returns number of entries
func (em *EndpointMap[T]) Len() int
// Keys returns slice of all current map keys
func (em *EndpointMap[T]) Keys() []Endpoint
// Values returns slice of all current map values
func (em *EndpointMap[T]) Values() []Timport (
"context"
"sync"
"time"
"google.golang.org/grpc/resolver"
)
// Custom resolver that periodically updates addresses
type myResolver struct {
target resolver.Target
cc resolver.ClientConn
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
func (r *myResolver) start() {
r.wg.Add(1)
go r.watcher()
}
func (r *myResolver) watcher() {
defer r.wg.Done()
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
// Initial resolution
r.resolve()
for {
select {
case <-r.ctx.Done():
return
case <-ticker.C:
r.resolve()
}
}
}
func (r *myResolver) resolve() {
// Fetch addresses from custom source
addrs := r.fetchAddresses()
// Update ClientConn with new addresses
err := r.cc.UpdateState(resolver.State{
Addresses: addrs,
})
if err != nil {
r.cc.ReportError(err)
}
}
func (r *myResolver) fetchAddresses() []resolver.Address {
// Custom logic to fetch addresses
// Could query a database, config service, etc.
return []resolver.Address{
{Addr: "10.0.0.1:8080"},
{Addr: "10.0.0.2:8080"},
{Addr: "10.0.0.3:8080"},
}
}
func (r *myResolver) ResolveNow(opts resolver.ResolveNowOptions) {
// Trigger immediate resolution
go r.resolve()
}
func (r *myResolver) Close() {
r.cancel()
r.wg.Wait()
}
// Builder for custom resolver
type myResolverBuilder struct{}
func (b *myResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
ctx, cancel := context.WithCancel(context.Background())
r := &myResolver{
target: target,
cc: cc,
ctx: ctx,
cancel: cancel,
}
r.start()
return r, nil
}
func (b *myResolverBuilder) Scheme() string {
return "myresolver"
}
// Register resolver
func init() {
resolver.Register(&myResolverBuilder{})
}
// Usage
conn, err := grpc.NewClient("myresolver:///myservice",
grpc.WithTransportCredentials(creds))func (r *myResolver) resolve() {
addrs := r.fetchAddresses()
// Provide service config
serviceConfigJSON := `{
"loadBalancingPolicy": "round_robin",
"methodConfig": [{
"name": [{"service": "myservice"}],
"waitForReady": true,
"timeout": "10s"
}]
}`
serviceConfig := r.cc.ParseServiceConfig(serviceConfigJSON)
err := r.cc.UpdateState(resolver.State{
Addresses: addrs,
ServiceConfig: serviceConfig,
})
if err != nil {
r.cc.ReportError(err)
}
}import "google.golang.org/grpc/attributes"
func (r *myResolver) resolve() {
addrs := []resolver.Address{
{
Addr: "10.0.0.1:8080",
Attributes: attributes.New("weight", 100),
},
{
Addr: "10.0.0.2:8080",
Attributes: attributes.New("weight", 50),
},
}
// Resolver-level attributes
resolverAttrs := attributes.New("datacenter", "us-east-1")
err := r.cc.UpdateState(resolver.State{
Addresses: addrs,
Attributes: resolverAttrs,
})
if err != nil {
r.cc.ReportError(err)
}
}Implement AuthorityOverrider to customize authority used for ClientConn:
type AuthorityOverrider interface {
// OverrideAuthority returns authority to use for ClientConn
// Must generate without blocking (typically inline)
// Must keep returned string unchanged
// Must return valid ":authority" header value (RFC3986 encoded)
OverrideAuthority(Target) string
}
// Example implementation
type myResolverBuilder struct{}
func (b *myResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
// Build resolver...
}
func (b *myResolverBuilder) Scheme() string {
return "myscheme"
}
func (b *myResolverBuilder) OverrideAuthority(target resolver.Target) string {
// Custom authority logic
return "custom-authority.example.com"
}import "google.golang.org/grpc"
// Method 1: Register globally and use scheme
resolver.Register(&myResolverBuilder{})
conn, err := grpc.NewClient("myscheme:///myservice",
grpc.WithTransportCredentials(creds))
// Method 2: Pass resolver directly
builder := &myResolverBuilder{}
conn, err := grpc.NewClient("myservice",
grpc.WithResolvers(builder),
grpc.WithTransportCredentials(creds))// WithResolvers allows passing custom resolver builders
// Experimental
func WithResolvers(rs ...resolver.Builder) DialOption
// WithDisableServiceConfig disables service config from resolver
func WithDisableServiceConfig() DialOption
// WithDefaultServiceConfig provides fallback service config
func WithDefaultServiceConfig(s string) DialOptionReportError for transient failuresWithDefaultServiceConfig for fallbackWithDisableServiceConfig for testingimport (
"testing"
"google.golang.org/grpc/resolver/manual"
)
func TestWithCustomResolver(t *testing.T) {
// Use manual resolver for testing
r := manual.NewBuilderWithScheme("test")
conn, err := grpc.NewClient("test:///unused",
grpc.WithResolvers(r),
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
defer conn.Close()
// Update addresses programmatically
r.UpdateState(resolver.State{
Addresses: []resolver.Address{
{Addr: "localhost:8080"},
},
})
// Test RPCs...
}resolver.Register() in package init function