gRPC 客户端负载均衡
gRPC client-side load balancing
我不确定我是否正确理解了通道和客户端负载平衡在 grpc 中的工作方式。我做的一切都是基于 one tutorial.
我有几台服务器,我想处理请求。我写了一个简单的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;
}
}
然后我写了一个简单的客户端。
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();
}
但在我查看的所有手册中,要么为通道 (forAddress) 指定了一个主机和端口,要么在“forTarget()”中指定了某个名称。
但是我有好几台服务器,怎么才能全部指定呢?
什么时候选择服务器?我正确理解 NameResolverProvider 参与其中,我在其中指定了服务器列表
我使用 round_robin 政策。也许我不需要 NameResolverProvider?
编辑
为了使用dns,我添加了DnsNameResolverProvider
NameResolverRegistry.getDefaultRegistry().register(new DnsNameResolverProvider());
但是我还不明白如何在forAddress
或forTarget
中指定两个服务器,例如地址first.example.com:5000
和second.example.com:5001
。它会是什么样子?
解析多个地址的最简单方法是仅利用 DNS 或您的 /etc/hosts 文件。默认 DNS 名称解析器将加载所有地址,您可以调用 managedChannel.defaultLoadBalancingPolicy("round_robin")
连接到所有地址,而不是仅连接到第一个有效的地址。
根据传递给 forTarget()
的目标字符串的方案选择名称解析器。因此,如果 getScheme()
用于您的解析器 returns fixed-replicas
,您将传递一个类似于 fixed-replicas:///
的字符串作为目标字符串。如果目标字符串中没有方案,则使用默认名称解析器。
forAddress()
是一种转换为 host:port
目标字符串的便利方法,但具有管理 IPv6 地址的逻辑,需要在 URI 中进行百分比编码。它仅在使用默认名称解析器(通常是 dns)时有用。
DnsNameResolver 使用优先级 5,您可能不想覆盖它,因此您应该为您的提供商使用较低的优先级,例如 4。
创建 InetSocketAddress
时要小心,确保只传递 IP 地址。如果你给它传递一个主机名,它会在构造函数中进行 DNS 解析。 NameResolvers 不应在调用它们的普通线程中执行 I/O 或阻塞操作。 NameResolver 可以将 Args.getOffloadExecutor()
用于 I/O 等。如果您在这里使用主机名,那么您最终会将它们分别解析为一个 IP 地址,并且永远不会重新解析它们,这意味着如果它们发生变化,您需要重新启动二进制文件。
我不确定我是否正确理解了通道和客户端负载平衡在 grpc 中的工作方式。我做的一切都是基于 one tutorial.
我有几台服务器,我想处理请求。我写了一个简单的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;
}
}
然后我写了一个简单的客户端。
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();
}
但在我查看的所有手册中,要么为通道 (forAddress) 指定了一个主机和端口,要么在“forTarget()”中指定了某个名称。
但是我有好几台服务器,怎么才能全部指定呢?
什么时候选择服务器?我正确理解 NameResolverProvider 参与其中,我在其中指定了服务器列表
我使用 round_robin 政策。也许我不需要 NameResolverProvider?
编辑
为了使用dns,我添加了DnsNameResolverProvider
NameResolverRegistry.getDefaultRegistry().register(new DnsNameResolverProvider());
但是我还不明白如何在forAddress
或forTarget
中指定两个服务器,例如地址first.example.com:5000
和second.example.com:5001
。它会是什么样子?
解析多个地址的最简单方法是仅利用 DNS 或您的 /etc/hosts 文件。默认 DNS 名称解析器将加载所有地址,您可以调用 managedChannel.defaultLoadBalancingPolicy("round_robin")
连接到所有地址,而不是仅连接到第一个有效的地址。
根据传递给 forTarget()
的目标字符串的方案选择名称解析器。因此,如果 getScheme()
用于您的解析器 returns fixed-replicas
,您将传递一个类似于 fixed-replicas:///
的字符串作为目标字符串。如果目标字符串中没有方案,则使用默认名称解析器。
forAddress()
是一种转换为 host:port
目标字符串的便利方法,但具有管理 IPv6 地址的逻辑,需要在 URI 中进行百分比编码。它仅在使用默认名称解析器(通常是 dns)时有用。
DnsNameResolver 使用优先级 5,您可能不想覆盖它,因此您应该为您的提供商使用较低的优先级,例如 4。
创建 InetSocketAddress
时要小心,确保只传递 IP 地址。如果你给它传递一个主机名,它会在构造函数中进行 DNS 解析。 NameResolvers 不应在调用它们的普通线程中执行 I/O 或阻塞操作。 NameResolver 可以将 Args.getOffloadExecutor()
用于 I/O 等。如果您在这里使用主机名,那么您最终会将它们分别解析为一个 IP 地址,并且永远不会重新解析它们,这意味着如果它们发生变化,您需要重新启动二进制文件。