Skip to content

Commit

Permalink
Client side load balancing for Web Client
Browse files Browse the repository at this point in the history
  • Loading branch information
vietj committed May 13, 2024
1 parent 03f75d5 commit 4e9e2c4
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 4 deletions.
29 changes: 29 additions & 0 deletions vertx-web-client/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,35 @@ The client will follow at most `16` requests redirections, it can be changed in

NOTE: For security reason, client won't follow redirects for request with methods different from GET or HEAD

== Client side load balancing

By default, when the client resolves a hostname to a list of several IP addresses, the client uses the first returned IP address.

The client can be configured to perform client side load balancing instead

[source,$lang]
----
{@link examples.WebClientExamples#clientSideLoadBalancing}
----

Vert.x provides out of the box several load balancing policies you can use

- {@link io.vertx.core.loadbalancing.LoadBalancer#ROUND_ROBIN Round-robin}
- {@link io.vertx.core.loadbalancing.LoadBalancer#LEAST_REQUESTS Least requests}
- {@link io.vertx.core.loadbalancing.LoadBalancer#POWER_OF_TWO_CHOICES Power of two choices}
- {@link io.vertx.core.loadbalancing.LoadBalancer#CONSISTENT_HASHING Consistent hashing}

Most load balancing policies are pretty much self-explanatory.

Hash based routing can be achieved with the {@link io.vertx.core.loadbalancing.LoadBalancer#CONSISTENT_HASHING} policy.

[source,$lang]
----
{@link examples.WebClientExamples#clientSideLoadBalancing}
----

You can read more details about client side load balancing in the Vert.x Core HTTP client documentation.

== HTTP response caching

Vert.x web offers an HTTP response caching facility; to use it, you create a {@link io.vertx.ext.web.client.CachingWebClient}.
Expand Down
43 changes: 39 additions & 4 deletions vertx-web-client/src/main/java/examples/WebClientExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.FileSystem;
import io.vertx.core.file.OpenOptions;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpResponseExpectation;
import io.vertx.core.http.HttpResponseHead;
import io.vertx.core.http.*;
import io.vertx.core.json.JsonObject;
import io.vertx.core.loadbalancing.LoadBalancer;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.parsetools.JsonParser;
import io.vertx.core.spi.loadbalancing.Endpoint;
import io.vertx.core.streams.ReadStream;
import io.vertx.core.streams.WriteStream;
import io.vertx.ext.auth.authentication.TokenCredentials;
Expand All @@ -44,6 +43,7 @@
import io.vertx.uritemplate.UriTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -727,4 +727,39 @@ public void testSocketAddress(WebClient client) {
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
}

public static void clientSideLoadBalancing(Vertx vertx) {
WebClient client = WebClient.wrap(vertx
.httpClientBuilder()
.withLoadBalancer(LoadBalancer.ROUND_ROBIN)
.build());
}

public static void clientSideConsistentHashing(Vertx vertx, int servicePort) {
WebClient client = WebClient.wrap(vertx
.httpClientBuilder()
.withLoadBalancer(LoadBalancer.CONSISTENT_HASHING)
.build());

HttpServer server = vertx.createHttpServer()
.requestHandler(inboundReq -> {

// Get a routing key, in this example we will hash the incoming request host/ip
// it could be anything else, e.g. user id, request id, ...
String routingKey = inboundReq.remoteAddress().hostAddress();

client
.get("/test")
.host("example.com")
.routingKey(routingKey)
.send()
.expecting(HttpResponseExpectation.SC_OK)
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
});

server.listen(servicePort);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.RequestOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.streams.ReadStream;
Expand Down Expand Up @@ -393,6 +394,24 @@ default HttpRequest<T> bearerTokenAuthentication(String bearerToken) {
*/
boolean followRedirects();

/**
* Set the routing key, the routing key can be used by a Vert.x client side sticky load balancer
* to pin the request to a remote HTTP server.
*
* @param key the routing key
* @return a reference to this, so the API can be used fluently
*/
@Fluent
HttpRequest<T> routingKey(String key);

/**
* Return the routing key, the routing key can be used by a Vert.x client side sticky load balancer to
* pin the request to a remote HTTP server.
*
* @return the routing key
*/
String routingKey();

/**
* Configure the request to set a proxy for this request.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class HttpRequestImpl<T> implements HttpRequest<T> {
private long timeout = -1;
private long idleTimeout = -1;
private long connectTimeout = -1;
private String routingKey;
private boolean followRedirects;
private Boolean ssl;
private boolean multipartMixed = true;
Expand Down Expand Up @@ -134,6 +135,7 @@ private HttpRequestImpl(HttpRequestImpl<T> other) {
this.timeout = other.timeout;
this.idleTimeout = other.idleTimeout;
this.connectTimeout = other.connectTimeout;
this.routingKey = other.routingKey;
this.queryParams = other.queryParams != null ? MultiMap.caseInsensitiveMultiMap().addAll(other.queryParams) : null;
this.multipartMixed = other.multipartMixed;
this.virtualHost = other.virtualHost;
Expand Down Expand Up @@ -326,6 +328,17 @@ public boolean followRedirects() {
return followRedirects;
}

@Override
public HttpRequest<T> routingKey(String key) {
routingKey = key;
return this;
}

@Override
public String routingKey() {
return routingKey;
}

@Override
public HttpRequest<T> proxy(ProxyOptions proxyOptions) {
this.proxyOptions = proxyOptions;
Expand Down Expand Up @@ -509,6 +522,7 @@ RequestOptions buildRequestOptions() throws URISyntaxException, MalformedURLExce
if (connectTimeout >= 0) {
requestOptions.setConnectTimeout(connectTimeout);
}
requestOptions.setRoutingKey(this.routingKey);
requestOptions.setProxyOptions(this.proxyOptions);
requestOptions.setTraceOperation(this.traceOperation);
return requestOptions;
Expand Down

0 comments on commit 4e9e2c4

Please sign in to comment.