mirror of https://github.com/grpc/grpc-java.git
Dualstack example (#11451)
This commit is contained in:
parent
40e2b165b7
commit
72a977bf7f
|
|
@ -0,0 +1,54 @@
|
||||||
|
# gRPC Dualstack Example
|
||||||
|
|
||||||
|
The dualstack example uses a custom name resolver that provides both IPv4 and IPv6 localhost
|
||||||
|
endpoints for each of 3 server instances. The client will first use the default name resolver and
|
||||||
|
load balancers which will only connect tot he first server. It will then use the
|
||||||
|
custom name resolver with round robin to connect to each of the servers in turn. The 3 instances
|
||||||
|
of the server will bind respectively to: both IPv4 and IPv6, IPv4 only, and IPv6 only.
|
||||||
|
|
||||||
|
The example requires grpc-java to already be built. You are strongly encouraged
|
||||||
|
to check out a git release tag, since there will already be a build of grpc
|
||||||
|
available. Otherwise, you must follow [COMPILING](../../COMPILING.md).
|
||||||
|
|
||||||
|
### Build the example
|
||||||
|
|
||||||
|
To build the dualstack example server and client. From the
|
||||||
|
`grpc-java/examples/example-dualstack` directory run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ../gradlew installDist
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates the scripts
|
||||||
|
`build/install/example-dualstack/bin/dual-stack-server`
|
||||||
|
and `build/install/example-dualstack/bin/dual-stack-client`.
|
||||||
|
|
||||||
|
To run the dualstack example, run the server with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./build/install/example-dualstack/bin/dual-stack-server
|
||||||
|
```
|
||||||
|
|
||||||
|
And in a different terminal window run the client.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./build/install/example-dualstack/bin/dual-stack-client
|
||||||
|
```
|
||||||
|
|
||||||
|
### Maven
|
||||||
|
|
||||||
|
If you prefer to use Maven:
|
||||||
|
|
||||||
|
Run in the example-debug directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mvn verify
|
||||||
|
$ # Run the server in one terminal
|
||||||
|
$ mvn exec:java -Dexec.mainClass=io.grpc.examples.dualstack.DualStackServer
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ # In another terminal run the client
|
||||||
|
$ mvn exec:java -Dexec.mainClass=io.grpc.examples.dualstack.DualStackClient
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
plugins {
|
||||||
|
id 'application' // Provide convenience executables for trying out the examples.
|
||||||
|
id 'java'
|
||||||
|
|
||||||
|
id "com.google.protobuf" version "0.9.4"
|
||||||
|
|
||||||
|
// Generate IntelliJ IDEA's .idea & .iml project files
|
||||||
|
id 'idea'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { // The google mirror is less flaky than mavenCentral()
|
||||||
|
url "https://maven-central.storage-download.googleapis.com/maven2/" }
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
|
||||||
|
// are looking at a tagged version of the example and not "master"!
|
||||||
|
|
||||||
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
|
// updating the version in our release process.
|
||||||
|
def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
|
def protobufVersion = '3.25.3'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
implementation "io.grpc:grpc-netty:${grpcVersion}"
|
||||||
|
implementation "io.grpc:grpc-stub:${grpcVersion}"
|
||||||
|
implementation "io.grpc:grpc-services:${grpcVersion}"
|
||||||
|
compileOnly "org.apache.tomcat:annotations-api:6.0.53"
|
||||||
|
}
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = "com.google.protobuf:protoc:${protobufVersion}"
|
||||||
|
}
|
||||||
|
plugins {
|
||||||
|
grpc {
|
||||||
|
artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generateProtoTasks {
|
||||||
|
all()*.plugins {
|
||||||
|
grpc {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startScripts.enabled = false
|
||||||
|
|
||||||
|
task DualStackClient(type: CreateStartScripts) {
|
||||||
|
mainClass = 'io.grpc.examples.dualstack.DualStackClient'
|
||||||
|
applicationName = 'dual-stack-client'
|
||||||
|
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
|
||||||
|
classpath = startScripts.classpath
|
||||||
|
}
|
||||||
|
|
||||||
|
task DualStackServer(type: CreateStartScripts) {
|
||||||
|
mainClass = 'io.grpc.examples.dualstack.DualStackServer'
|
||||||
|
applicationName = 'dual-stack-server'
|
||||||
|
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
|
||||||
|
classpath = startScripts.classpath
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
applicationDistribution.into('bin') {
|
||||||
|
from(DualStackClient)
|
||||||
|
from(DualStackServer)
|
||||||
|
filePermissions {
|
||||||
|
unix(0755)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>example-dualstack</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
|
for safely updating the version in our release process. -->
|
||||||
|
<version>1.67.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
|
<name>example-dualstack</name>
|
||||||
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<grpc.version>1.67.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
|
<protoc.version>3.25.3</protoc.version>
|
||||||
|
<!-- required for jdk9 -->
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-bom</artifactId>
|
||||||
|
<version>${grpc.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-services</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-stub</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-netty</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat</groupId>
|
||||||
|
<artifactId>annotations-api</artifactId>
|
||||||
|
<version>6.0.53</version>
|
||||||
|
<scope>provided</scope> <!-- not needed at runtime -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-netty-shaded</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-testing</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>kr.motd.maven</groupId>
|
||||||
|
<artifactId>os-maven-plugin</artifactId>
|
||||||
|
<version>1.7.1</version>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.xolstice.maven.plugins</groupId>
|
||||||
|
<artifactId>protobuf-maven-plugin</artifactId>
|
||||||
|
<version>0.6.1</version>
|
||||||
|
<configuration>
|
||||||
|
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
|
||||||
|
<pluginId>grpc-java</pluginId>
|
||||||
|
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
<goal>compile-custom</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
|
<version>1.4.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>enforce</id>
|
||||||
|
<goals>
|
||||||
|
<goal>enforce</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<rules>
|
||||||
|
<requireUpperBoundDeps/>
|
||||||
|
</rules>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven { // The google mirror is less flaky than mavenCentral()
|
||||||
|
url "https://maven-central.storage-download.googleapis.com/maven2/"
|
||||||
|
}
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = 'example-dualstack'
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.examples.dualstack;
|
||||||
|
|
||||||
|
import io.grpc.Channel;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.ManagedChannelBuilder;
|
||||||
|
import io.grpc.NameResolverRegistry;
|
||||||
|
import io.grpc.StatusRuntimeException;
|
||||||
|
import io.grpc.examples.helloworld.GreeterGrpc;
|
||||||
|
import io.grpc.examples.helloworld.HelloReply;
|
||||||
|
import io.grpc.examples.helloworld.HelloRequest;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client that requests greetings from the {@link DualStackServer}.
|
||||||
|
* First it sends 5 requests using the default nameresolver and load balancer.
|
||||||
|
* Then it sends 10 requests using the example nameresolver and round robin load balancer. These
|
||||||
|
* requests are evenly distributed among the 3 servers rather than favoring the server listening
|
||||||
|
* on both addresses because the ExampleDualStackNameResolver groups the 3 servers as 3 endpoints
|
||||||
|
* each with 2 addresses.
|
||||||
|
*/
|
||||||
|
public class DualStackClient {
|
||||||
|
public static final String channelTarget = "example:///lb.example.grpc.io";
|
||||||
|
private static final Logger logger = Logger.getLogger(DualStackClient.class.getName());
|
||||||
|
private final GreeterGrpc.GreeterBlockingStub blockingStub;
|
||||||
|
|
||||||
|
public DualStackClient(Channel channel) {
|
||||||
|
blockingStub = GreeterGrpc.newBlockingStub(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
NameResolverRegistry.getDefaultRegistry()
|
||||||
|
.register(new ExampleDualStackNameResolverProvider());
|
||||||
|
|
||||||
|
logger.info("\n **** Use default DNS resolver ****");
|
||||||
|
ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:50051")
|
||||||
|
.usePlaintext()
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
DualStackClient client = new DualStackClient(channel);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
client.greet("request:" + i);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("\n **** Change to use example name resolver ****");
|
||||||
|
/*
|
||||||
|
Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection
|
||||||
|
"resolver.example.grpc.io" is converted to {@link java.net.URI.path}
|
||||||
|
*/
|
||||||
|
channel = ManagedChannelBuilder.forTarget(channelTarget)
|
||||||
|
.defaultLoadBalancingPolicy("round_robin")
|
||||||
|
.usePlaintext()
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
DualStackClient client = new DualStackClient(channel);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
client.greet("request:" + i);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void greet(String name) {
|
||||||
|
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
|
||||||
|
HelloReply response;
|
||||||
|
try {
|
||||||
|
response = blockingStub.sayHello(request);
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.info("Greeting: " + response.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.examples.dualstack;
|
||||||
|
|
||||||
|
import io.grpc.Server;
|
||||||
|
import io.grpc.ServerBuilder;
|
||||||
|
import io.grpc.examples.helloworld.GreeterGrpc;
|
||||||
|
import io.grpc.examples.helloworld.HelloReply;
|
||||||
|
import io.grpc.examples.helloworld.HelloRequest;
|
||||||
|
import io.grpc.netty.NettyServerBuilder;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts 3 different greeter services each on its own port, but all for localhost.
|
||||||
|
* The first service listens on both IPv4 and IPv6,
|
||||||
|
* the second on just IPv4, and the third on just IPv6.
|
||||||
|
*/
|
||||||
|
public class DualStackServer {
|
||||||
|
private static final Logger logger = Logger.getLogger(DualStackServer.class.getName());
|
||||||
|
private List<Server> servers;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
|
final DualStackServer server = new DualStackServer();
|
||||||
|
server.start();
|
||||||
|
server.blockUntilShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start() throws IOException {
|
||||||
|
InetSocketAddress inetSocketAddress;
|
||||||
|
|
||||||
|
servers = new ArrayList<>();
|
||||||
|
int[] serverPorts = ExampleDualStackNameResolver.SERVER_PORTS;
|
||||||
|
for (int i = 0; i < serverPorts.length; i++ ) {
|
||||||
|
String addressType;
|
||||||
|
int port = serverPorts[i];
|
||||||
|
ServerBuilder<?> serverBuilder;
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
serverBuilder = ServerBuilder.forPort(port); // bind to both IPv4 and IPv6
|
||||||
|
addressType = "both IPv4 and IPv6";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// bind to IPv4 only
|
||||||
|
inetSocketAddress = new InetSocketAddress("127.0.0.1", port);
|
||||||
|
serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress);
|
||||||
|
addressType = "IPv4 only";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// bind to IPv6 only
|
||||||
|
inetSocketAddress = new InetSocketAddress("::1", port);
|
||||||
|
serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress);
|
||||||
|
addressType = "IPv6 only";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected value: " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.add(serverBuilder
|
||||||
|
.addService(new GreeterImpl(port, addressType))
|
||||||
|
.build()
|
||||||
|
.start());
|
||||||
|
logger.info("Server started, listening on " + port);
|
||||||
|
}
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
System.err.println("*** shutting down gRPC server since JVM is shutting down");
|
||||||
|
try {
|
||||||
|
DualStackServer.this.stop();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
System.err.println("*** server shut down");
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() throws InterruptedException {
|
||||||
|
for (Server server : servers) {
|
||||||
|
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void blockUntilShutdown() throws InterruptedException {
|
||||||
|
for (Server server : servers) {
|
||||||
|
server.awaitTermination();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
|
||||||
|
|
||||||
|
int port;
|
||||||
|
String addressType;
|
||||||
|
|
||||||
|
public GreeterImpl(int port, String addressType) {
|
||||||
|
this.port = port;
|
||||||
|
this.addressType = addressType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
|
||||||
|
String msg = String.format("Hello %s from server<%d> type: %s",
|
||||||
|
req.getName(), this.port, addressType);
|
||||||
|
HelloReply reply = HelloReply.newBuilder().setMessage(msg).build();
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.examples.dualstack;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import io.grpc.EquivalentAddressGroup;
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fake name resolver that resolves to a hard-coded list of 3 endpoints (EquivalentAddressGropu)
|
||||||
|
* each with 2 addresses (one IPv4 and one IPv6).
|
||||||
|
*/
|
||||||
|
public class ExampleDualStackNameResolver extends NameResolver {
|
||||||
|
static public final int[] SERVER_PORTS = {50051, 50052, 50053};
|
||||||
|
|
||||||
|
// This is a fake name resolver, so we just hard code the address here.
|
||||||
|
private static final ImmutableMap<String, List<List<SocketAddress>>> addrStore =
|
||||||
|
ImmutableMap.<String, List<List<SocketAddress>>>builder()
|
||||||
|
.put("lb.example.grpc.io",
|
||||||
|
Arrays.stream(SERVER_PORTS)
|
||||||
|
.mapToObj(port -> getLocalAddrs(port))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private Listener2 listener;
|
||||||
|
|
||||||
|
private final URI uri;
|
||||||
|
|
||||||
|
public ExampleDualStackNameResolver(URI targetUri) {
|
||||||
|
this.uri = targetUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<SocketAddress> getLocalAddrs(int port) {
|
||||||
|
return Arrays.asList(
|
||||||
|
new InetSocketAddress("127.0.0.1", port),
|
||||||
|
new InetSocketAddress("::1", port));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceAuthority() {
|
||||||
|
return uri.getPath().substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Listener2 listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
this.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolve() {
|
||||||
|
List<List<SocketAddress>> addresses = addrStore.get(uri.getPath().substring(1));
|
||||||
|
try {
|
||||||
|
List<EquivalentAddressGroup> eagList = new ArrayList<>();
|
||||||
|
for (List<SocketAddress> endpoint : addresses) {
|
||||||
|
// every server is an EquivalentAddressGroup, so they can be accessed randomly
|
||||||
|
eagList.add(new EquivalentAddressGroup(endpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listener.onResult(ResolutionResult.newBuilder().setAddresses(eagList).build());
|
||||||
|
} catch (Exception e){
|
||||||
|
// when error occurs, notify listener
|
||||||
|
this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.examples.dualstack;
|
||||||
|
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.NameResolverProvider;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
public class ExampleDualStackNameResolverProvider extends NameResolverProvider {
|
||||||
|
public static final String exampleScheme = "example";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
|
||||||
|
return new ExampleDualStackNameResolver(targetUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int priority() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
// gRPC choose the first NameResolverProvider that supports the target URI scheme.
|
||||||
|
public String getDefaultScheme() {
|
||||||
|
return exampleScheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2015 The gRPC Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.examples.helloworld";
|
||||||
|
option java_outer_classname = "HelloWorldProto";
|
||||||
|
option objc_class_prefix = "HLW";
|
||||||
|
|
||||||
|
package helloworld;
|
||||||
|
|
||||||
|
// The greeting service definition.
|
||||||
|
service Greeter {
|
||||||
|
// Sends a greeting
|
||||||
|
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
message HelloRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings
|
||||||
|
message HelloReply {
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
|
|
@ -28,12 +28,12 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName;
|
import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName;
|
||||||
|
|
||||||
public class ExampleNameResolver extends NameResolver {
|
public class ExampleNameResolver extends NameResolver {
|
||||||
|
|
||||||
|
static private final int[] SERVER_PORTS = {50051, 50052, 50053};
|
||||||
private Listener2 listener;
|
private Listener2 listener;
|
||||||
|
|
||||||
private final URI uri;
|
private final URI uri;
|
||||||
|
|
@ -44,12 +44,11 @@ public class ExampleNameResolver extends NameResolver {
|
||||||
this.uri = targetUri;
|
this.uri = targetUri;
|
||||||
// This is a fake name resolver, so we just hard code the address here.
|
// This is a fake name resolver, so we just hard code the address here.
|
||||||
addrStore = ImmutableMap.<String,List<InetSocketAddress>>builder()
|
addrStore = ImmutableMap.<String,List<InetSocketAddress>>builder()
|
||||||
.put(exampleServiceName,
|
.put(exampleServiceName,
|
||||||
Stream.iterate(LoadBalanceServer.startPort,p->p+1)
|
Arrays.stream(SERVER_PORTS)
|
||||||
.limit(LoadBalanceServer.serverCount)
|
.mapToObj(port->new InetSocketAddress("localhost",port))
|
||||||
.map(port->new InetSocketAddress("localhost",port))
|
.collect(Collectors.toList())
|
||||||
.collect(Collectors.toList())
|
)
|
||||||
)
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,23 +24,24 @@ import io.grpc.examples.helloworld.HelloRequest;
|
||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class LoadBalanceServer {
|
public class LoadBalanceServer {
|
||||||
private static final Logger logger = Logger.getLogger(LoadBalanceServer.class.getName());
|
private static final Logger logger = Logger.getLogger(LoadBalanceServer.class.getName());
|
||||||
static public final int serverCount = 3;
|
static final int[] SERVER_PORTS = {50051, 50052, 50053};
|
||||||
static public final int startPort = 50051;
|
private List<Server> servers;
|
||||||
private Server[] servers;
|
|
||||||
|
|
||||||
private void start() throws IOException {
|
private void start() throws IOException {
|
||||||
servers = new Server[serverCount];
|
servers = new ArrayList<>();
|
||||||
for (int i = 0; i < serverCount; i++) {
|
for (int port : SERVER_PORTS) {
|
||||||
int port = startPort + i;
|
servers.add(
|
||||||
servers[i] = ServerBuilder.forPort(port)
|
ServerBuilder.forPort(port)
|
||||||
.addService(new GreeterImpl(port))
|
.addService(new GreeterImpl(port))
|
||||||
.build()
|
.build()
|
||||||
.start();
|
.start());
|
||||||
logger.info("Server started, listening on " + port);
|
logger.info("Server started, listening on " + port);
|
||||||
}
|
}
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
|
@ -55,18 +56,14 @@ public class LoadBalanceServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stop() throws InterruptedException {
|
private void stop() throws InterruptedException {
|
||||||
for (int i = 0; i < serverCount; i++) {
|
for (Server server : servers) {
|
||||||
if (servers[i] != null) {
|
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
|
||||||
servers[i].shutdown().awaitTermination(30, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void blockUntilShutdown() throws InterruptedException {
|
private void blockUntilShutdown() throws InterruptedException {
|
||||||
for (int i = 0; i < serverCount; i++) {
|
for (Server server : servers) {
|
||||||
if (servers[i] != null) {
|
server.awaitTermination();
|
||||||
servers[i].awaitTermination();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,7 +83,8 @@ public class LoadBalanceServer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
|
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
|
||||||
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build();
|
HelloReply reply = HelloReply.newBuilder()
|
||||||
|
.setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build();
|
||||||
responseObserver.onNext(reply);
|
responseObserver.onNext(reply);
|
||||||
responseObserver.onCompleted();
|
responseObserver.onCompleted();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,7 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class NameResolveClient {
|
public class NameResolveClient {
|
||||||
public static final String exampleScheme = "example";
|
public static final String channelTarget = "example:///lb.example.grpc.io";
|
||||||
public static final String exampleServiceName = "lb.example.grpc.io";
|
|
||||||
private static final Logger logger = Logger.getLogger(NameResolveClient.class.getName());
|
private static final Logger logger = Logger.getLogger(NameResolveClient.class.getName());
|
||||||
private final GreeterGrpc.GreeterBlockingStub blockingStub;
|
private final GreeterGrpc.GreeterBlockingStub blockingStub;
|
||||||
|
|
||||||
|
|
@ -56,11 +55,10 @@ public class NameResolveClient {
|
||||||
Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection
|
Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection
|
||||||
"resolver.example.grpc.io" is converted to {@link java.net.URI.path}
|
"resolver.example.grpc.io" is converted to {@link java.net.URI.path}
|
||||||
*/
|
*/
|
||||||
channel = ManagedChannelBuilder.forTarget(
|
channel = ManagedChannelBuilder.forTarget(channelTarget)
|
||||||
String.format("%s:///%s", exampleScheme, exampleServiceName))
|
.defaultLoadBalancingPolicy("round_robin")
|
||||||
.defaultLoadBalancingPolicy("round_robin")
|
.usePlaintext()
|
||||||
.usePlaintext()
|
.build();
|
||||||
.build();
|
|
||||||
try {
|
try {
|
||||||
NameResolveClient client = new NameResolveClient(channel);
|
NameResolveClient client = new NameResolveClient(channel);
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue