Search code examples
javaload-balancinggrpcgrpc-java

Clientside load balancing for grpc v1.34.1, nameResolverFactory is deprecated


I am using grpc v1.34.1 with Java and it's hard to configure client-side load balancing since some of the methods are deprecated in this version. It was pretty straightforward to configure client-side load balancing in an earlier version by:

final ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
        .nameResolverFactory(new DnsNameResolverProvider())  // this is on by default
        .loadBalancerFactory(RoundRobinLoadBalancerFactory.getInstance())
        .usePlaintext(true)
        .build();

Or by this https://sultanov.dev/blog/grpc-client-side-load-balancing/

But, there aren't any references available for a newer version that has deprecated nameResolverFactory and removed method loadBalancerFactory.

NameResolver.Factory nameResolverFactory = new MultiAddressNameResolverFactory(
        new InetSocketAddress("localhost", 50000),
        new InetSocketAddress("localhost", 50001),
        new InetSocketAddress("localhost", 50002)
);

channel = ManagedChannelBuilder.forTarget("localhost")
        .nameResolverFactory(nameResolverFactory)
        .defaultLoadBalancingPolicy("round_robin")
        .usePlaintext()
        .build();

Client-side load balancing works. But, the newer API has deprecated nameResolverFactory.

Could anyone please point me towards the alternative of nameResolverFactory in the newer version for client-side load balancing with different servers (hosts and ports)?


Solution

  • After going through grpc-java internal implementation, I found that that the newer version accepts the NameResolver.Factory object in slightly different way. It's encapsulated to NameResolverProvider which is required to be registered to default NameResolverRegistry. Sample code to do this in newer version is shared below:

    NameResolverProvider nameResolverFactory = new MultiAddressNameResolverFactory(
                    new InetSocketAddress("localhost", 50000),
                    new InetSocketAddress("localhost", 50001),
                    new InetSocketAddress("localhost", 50002)
            );
    
    NameResolverRegistry nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
    nameResolverRegistry.register(nameResolverFactory);
    channel = ManagedChannelBuilder.forTarget("localhost")
              .defaultLoadBalancingPolicy("round_robin")
              .usePlaintext()
              .build();
    
    
    public class MultiAddressNameResolverFactory extends NameResolverProvider {
        final List<EquivalentAddressGroup> addresses;
    
        MultiAddressNameResolverFactory(SocketAddress... addresses) {
            this.addresses = Arrays.stream(addresses)
                    .map(EquivalentAddressGroup::new)
                    .collect(Collectors.toList());
        }
    
        public NameResolver newNameResolver(URI notUsedUri, NameResolver.Args args) {
            return new NameResolver() {
                @Override
                public String getServiceAuthority() {
                    return "fakeAuthority";
                }
                public void start(Listener2 listener) {
                    listener.onResult(ResolutionResult.newBuilder().setAddresses(addresses).setAttributes(Attributes.EMPTY).build());
                }
                public void shutdown() {
                }
            };
        }
    
        @Override
        public String getDefaultScheme() {
            return "multiaddress";
        }
    
        @Override
        protected boolean isAvailable() {
            return true;
        }
    
        @Override
        protected int priority() {
            return 0;
        }
    }
    

    By default, your custom implementation for NameResolver.Factory would be picked up by channel to connect to server. Based on the load balancing policy, a SocketAddress would be picked up to connect to server.