I'm not sure I understand correctly how channels and client-side load balancing work in grpc. I did everything based on one tutorial.
I have several servers where I want to go with requests. I wrote a simple NameResolverProvider.
public class BalancingNameResolverProvider extends NameResolverProvider {
private Set<String> replicas;
private Optional<Map<String, Object>> config;
private String schema;
private int priority = 5;
public BalancingNameResolverProvider(Set<String> replicas, Optional<Map<String, Object>> config, String schema, int priority) {
this.replicas = replicas;
this.config = config;
this.schema = schema;
this.priority = priority;
}
@Override
protected boolean isAvailable() {
return true;
}
@Override
protected int priority() {
return priority;
}
@Override
public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
List<EquivalentAddressGroup> delegates = replicas.stream()
.map(x -> new InetSocketAddress(x.split(":")[0], Integer.parseInt(x.split(":")[1])))
.map(EquivalentAddressGroup::new)
.collect(Collectors.toList());
return new NameResolver() {
private Optional<NameResolver.ConfigOrError> parsedConfig = config.map(x ->
args.getServiceConfigParser().parseServiceConfig(x)
);
@Override
public String getServiceAuthority() {
return targetUri.getAuthority();
}
@Override
public void shutdown() {
}
@Override
public void start(final NameResolver.Listener2 listener) {
ResolutionResult.Builder builder = ResolutionResult.newBuilder()
.setAddresses(delegates)
.setAttributes(Attributes.EMPTY);
parsedConfig.ifPresent(builder::setServiceConfig);
listener.onResult(builder.build());
}
};
}
@Override
public String getDefaultScheme() {
return schema;
}
}
And I wrote a simple client.
NameResolverRegistry.getDefaultRegistry().register(resolverConfig.toProvider());
ManagedChannel channel = NettyChannelBuilder
.forTarget("???") //or forAddress("???")
.enableRetry()
.usePlaintext()
.build();
try {
HelloServiceGrpc.HelloServiceBlockingStub client = HelloServiceGrpc.newBlockingStub(channel);
for (int i = 0; i < count; i++) {
System.out.println(client.hello(HelloRequest.newBuilder()
.setFirstName("first_" + i)
.setLastName("lastName_" + i)
.build())
.getGreeting());
}
} finally {
channel.shutdown();
}
But in all the manuals that I looked at, either one host and port are specified for the channel (forAddress), or some name in "forTarget()".
But I have several servers, how can I specify them all?
And at what point is the server selected? I understand correctly that NameResolverProvider is involved in this, where I specified the list of servers
I use the round_robin policy. Maybe I don't need NameResolverProvider?
Edit
In order to use dns, I added DnsNameResolverProvider.
NameResolverRegistry.getDefaultRegistry().register(new DnsNameResolverProvider());
But I don’t understand yet how to specify two servers, for example, with addresses first.example.com:5000
and second.example.com:5001
in forAddress
or forTarget
. How will it look like?
The easiest way to resolve multiple addresses is to just leverage DNS or your /etc/hosts file. The default DNS name resolver will load all the addresses and you can call managedChannel.defaultLoadBalancingPolicy("round_robin")
to connect to all the addresses instead of only the first that works.
The name resolver is selected by scheme of the target string passed to forTarget()
. So if getScheme()
for your resolver returns fixed-replicas
, you'd pass a string like fixed-replicas:///
as the target string. If no scheme is in the target string, then the default name resolver is used.
forAddress()
is a convenience that converts to a target string of host:port
, but with logic to manage IPv6 addresses which need percent-encoding to be within a URI. It's only useful when using the default name resolver (generally dns).
DnsNameResolver uses priority 5, and you probably don't want to override it, so you should probably use a lower priority like 4 for your provider.
Be careful when creating InetSocketAddress
to make sure to only pass it IP addresses. If you pass it a hostname it will do a DNS resolution within the constructor. NameResolvers should not do I/O or blocking operations in the normal threads they are called on. A NameResolver can use Args.getOffloadExecutor()
for I/O and the like. If you were using hostnames here then you'd end up resolving them to a single IP address each and never re-resolve them, which means if they change you'd need to restart your binary.