简体   繁体   中英

Dial multiple IP addresses with golang gRPC client

I have grpc server 192.168.1.12:8800 and 192.168.1.13:8800 , I want to connect them use grpc.Dial with ip list, not server discover, How can I do?

conn, err = grpc.Dial("192.168.1.12:8800,192.168.1.13:8800", grpc.WithInsecure())

with error

rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = \"transport: Error while dialing dial tcp: too many colons in address 192.168.1.12:8800,192.168.1.13:8800

Unfortunately, you aren't able to pass multiple IP addresses using grpc.Dial(...) , it only takes a single argument.

gRPC in Go does have an "experimental" load balancer api that you should be able to take advantage of.

An example of the resolver you would need to write can be found here . It creates a fake resolver that will load balance across the multiple IP addresses.

So once you have a resolver such as that, the code you would want would look something like this:

conn, err := grpc.Dial(
    "",
    grpc.WithInsecure(),
    grpc.WithBalancer(grpc.RoundRobin(resolver.NewPseudoResolver([]string{
        "10.0.0.1:10000",
        "10.0.0.2:10000",
    }))),
)

I wrote a library for this: https://github.com/Jille/grpc-multi-resolver

Usage is easy:

import _ "github.com/Jille/grpc-multi-resolver"

grpc.Dial("multi:///192.168.1.12:8800,192.168.1.13:8800")

Note the triple slash at the beginning.

I recently implemented this kind of gRPC resolver in https://github.com/letsencrypt/boulder :

resolver implementation:

package grpc

import (
    "fmt"
    "net"
    "strings"

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

// staticBuilder implements the `resolver.Builder` interface.
type staticBuilder struct{}

// newStaticBuilder creates a `staticBuilder` used to construct static DNS
// resolvers.
func newStaticBuilder() resolver.Builder {
    return &staticBuilder{}
}

// Build implements the `resolver.Builder` interface and is usually called by
// the gRPC dialer. It takes a target containing a comma separated list of
// IPv4/6 addresses and a `resolver.ClientConn` and returns a `staticResolver`
// which implements the `resolver.Resolver` interface.
func (sb *staticBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
    var resolverAddrs []resolver.Address
    for _, address := range strings.Split(target.Endpoint, ",") {
        parsedAddress, err := parseResolverIPAddress(address)
        if err != nil {
            return nil, err
        }
        resolverAddrs = append(resolverAddrs, *parsedAddress)
    }
    return newStaticResolver(cc, resolverAddrs), nil
}

// Scheme returns the scheme that `staticBuilder` will be registered for, for
// example: `static:///`.
func (sb *staticBuilder) Scheme() string {
    return "static"
}

// staticResolver is used to wrap an inner `resolver.ClientConn` and implements
// the `resolver.Resolver` interface.
type staticResolver struct {
    cc resolver.ClientConn
}

// newStaticResolver takes a `resolver.ClientConn` and a list of
// `resolver.Addresses`. It updates the state of the `resolver.ClientConn` with
// the provided addresses and returns a `staticResolver` which wraps the
// `resolver.ClientConn` and implements the `resolver.Resolver` interface.
func newStaticResolver(cc resolver.ClientConn, resolverAddrs []resolver.Address) resolver.Resolver {
    cc.UpdateState(resolver.State{Addresses: resolverAddrs})
    return &staticResolver{cc: cc}
}

// ResolveNow is a no-op necessary for `staticResolver` to implement the
// `resolver.Resolver` interface. This resolver is constructed once by
// staticBuilder.Build and the state of the inner `resolver.ClientConn` is never
// updated.
func (sr *staticResolver) ResolveNow(_ resolver.ResolveNowOptions) {}

// Close is a no-op necessary for `staticResolver` to implement the
// `resolver.Resolver` interface.
func (sr *staticResolver) Close() {}

// parseResolverIPAddress takes an IPv4/6 address (ip:port, [ip]:port, or :port)
// and returns a properly formatted `resolver.Address` object. The `Addr` and
// `ServerName` fields of the returned `resolver.Address` will both be set to
// host:port or [host]:port if the host is an IPv6 address.
func parseResolverIPAddress(addr string) (*resolver.Address, error) {
    host, port, err := net.SplitHostPort(addr)
    if err != nil {
        return nil, fmt.Errorf("splitting host and port for address %q: %w", addr, err)
    }
    if port == "" {
        // If the port field is empty the address ends with colon (e.g.
        // "[::1]:").
        return nil, fmt.Errorf("address %q missing port after port-separator colon", addr)
    }
    if host == "" {
        // Address only has a port (i.e ipv4-host:port, [ipv6-host]:port,
        // host-name:port). Keep consistent with net.Dial(); if the host is
        // empty (e.g. :80), the local system is assumed.
        host = "127.0.0.1"
    }
    if net.ParseIP(host) == nil {
        // Host is a DNS name or an IPv6 address without brackets.
        return nil, fmt.Errorf("address %q is not an IP address", addr)
    }
    parsedAddr := net.JoinHostPort(host, port)
    return &resolver.Address{
        Addr:       parsedAddr,
        ServerName: parsedAddr,
    }, nil
}

// init registers the `staticBuilder` with the gRPC resolver registry.
func init() {
    resolver.Register(newStaticBuilder())
}

Usage example:

    return grpc.Dial(
        "static:///192.168.1.12:8800,192.168.1.13:8800",
        grpc.WithBalancerName("round_robin"),
    )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM