Nitish Bhardwaj
Nitish Bhardwaj

Reputation: 1187

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)?

Upvotes: 5

Views: 2911

Answers (2)

Mohammed Fataka
Mohammed Fataka

Reputation: 150

just one thing to add to Nitish Bhardwaj answer, for me method

int priority()

did not work with value 0, because io.grpc.internal.DnsNameResolverProvider has the value of 5 and io.grpc.netty.shaded.io.grpc.netty.UdsNameResolverProvider has the value of 3, which in my case was not setting my custom NameResolverProvider, I could fix it to be automatically picking up my custom NameResolverProvider by overriding method priority() to anything more than 5.

full code:

package iam.mfa.grpc.client.resolvers;

import java.net.URI;
import java.util.List;

import io.grpc.Attributes;
import io.grpc.EquivalentAddressGroup;
import io.grpc.NameResolver;
import io.grpc.NameResolverProvider;
import lombok.RequiredArgsConstructor;

/**
 * @author HAMMA FATAKA ([email protected])
 * @project gRPC
 * @date 07.04.2023 21:54
 */
@RequiredArgsConstructor(staticName = "of")
public class MultipleAddressNameResolverProvider extends NameResolverProvider {
    private final List<EquivalentAddressGroup> addresses;

    @Override
    public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
        return new NameResolver() {
            @Override
            public String getServiceAuthority() {
                return "noAuthority";
            }


            @Override
            public void start(Listener2 listener) {
                listener.onResult(ResolutionResult.newBuilder().setAddresses(addresses).setAttributes(Attributes.EMPTY).build());
            }

            @Override
            public void shutdown() {

            }
        };
    }

    @Override
    public String getDefaultScheme() {
        return "multiTarget";
    }

    @Override
    protected boolean isAvailable() {
        return true;
    }

    @Override
    protected int priority() {
        return 6;
    }
}

Client config:


package iam.mfa.grpc.client.config;

import java.net.InetSocketAddress;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import iam.mfa.grpc.client.resolvers.MultipleAddressNameResolver;
import io.grpc.EquivalentAddressGroup;
import io.grpc.ManagedChannel;
import io.grpc.NameResolverRegistry;
import io.grpc.internal.DnsNameResolver;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;

/**
 * @author HAMMA FATAKA ([email protected])
 * @project gRPC
 * @date 26.02.2023 16:03
 */
@Configuration
public class ClientConfig {

    @Value("#{'${grpc.client.targets}'.split(',')}")
    private List<String> targets;


    @PostConstruct
    public void initNameResolver(){
        final var addresses = targets.stream()
                .map(target -> {
                    final var splitTarget = target.split(":");
                    final var host = splitTarget[0];
                    final var port = splitTarget[1];
                    return new EquivalentAddressGroup(new InetSocketAddress(host, Integer.parseInt(port)));
                })
                .toList();
        final var nameResolverFactory = MultipleAddressNameResolver.of(addresses);
        NameResolverRegistry.getDefaultRegistry().register(nameResolverFactory);
    }


    @Bean
    public ManagedChannel channel() {
        return NettyChannelBuilder.forTarget("localhost")
                .defaultLoadBalancingPolicy("round_robin")
                .usePlaintext()
                .build();
    }

}

hope this helps.

Upvotes: 0

Nitish Bhardwaj
Nitish Bhardwaj

Reputation: 1187

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.

Upvotes: 4

Related Questions