HBASE-28500 Rest Java client library assumes stateless servers (#5804)
add a sticky flag which forces using the same server for all requests
Signed-off-by: Peter Somogyi <psomogyi@apache.org>
Signed-off-by: Duo Zhang <zhangduo@apache.org>
diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/Client.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/Client.java
index 9283cb9..96cb5f2 100644
--- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/Client.java
+++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/Client.java
@@ -79,6 +79,8 @@
private HttpClient httpClient;
private Cluster cluster;
+ private Integer lastNodeId;
+ private boolean sticky = false;
private Configuration conf;
private boolean sslEnabled;
private HttpResponse resp;
@@ -249,10 +251,12 @@
}
/**
- * Execute a transaction method given only the path. Will select at random one of the members of
- * the supplied cluster definition and iterate through the list until a transaction can be
- * successfully completed. The definition of success here is a complete HTTP transaction,
- * irrespective of result code.
+ * Execute a transaction method given only the path. If sticky is false: Will select at random one
+ * of the members of the supplied cluster definition and iterate through the list until a
+ * transaction can be successfully completed. The definition of success here is a complete HTTP
+ * transaction, irrespective of result code. If sticky is true: For the first request it will
+ * select a random one of the members of the supplied cluster definition. For subsequent requests
+ * it will use the same member, and it will not automatically re-try if the call fails.
* @param cluster the cluster definition
* @param method the transaction method
* @param headers HTTP header values to send
@@ -265,10 +269,12 @@
if (cluster.nodes.size() < 1) {
throw new IOException("Cluster is empty");
}
- int start = (int) Math.round((cluster.nodes.size() - 1) * Math.random());
- int i = start;
+ if (lastNodeId == null || !sticky) {
+ lastNodeId = (int) (cluster.nodes.size() * Math.random());
+ }
+ int start = lastNodeId;
do {
- cluster.lastHost = cluster.nodes.get(i);
+ cluster.lastHost = cluster.nodes.get(lastNodeId);
try {
StringBuilder sb = new StringBuilder();
if (sslEnabled) {
@@ -302,7 +308,11 @@
} catch (URISyntaxException use) {
lastException = new IOException(use);
}
- } while (++i != start && i < cluster.nodes.size());
+ if (!sticky) {
+ lastNodeId = (++lastNodeId) % cluster.nodes.size();
+ }
+ // Do not retry if sticky. Let the caller handle the error.
+ } while (!sticky && lastNodeId != start);
throw lastException;
}
@@ -407,6 +417,28 @@
}
/**
+ * The default behaviour is load balancing by sending each request to a random host. This DOES NOT
+ * work with scans, which have state on the REST servers. Make sure sticky is set to true before
+ * attempting Scan related operations if more than one host is defined in the cluster.
+ * @return whether subsequent requests will use the same host
+ */
+ public boolean isSticky() {
+ return sticky;
+ }
+
+ /**
+ * The default behaviour is load balancing by sending each request to a random host. This DOES NOT
+ * work with scans, which have state on the REST servers. Set sticky to true before attempting
+ * Scan related operations if more than one host is defined in the cluster. Nodes must not be
+ * added or removed from the Cluster object while sticky is true.
+ * @param sticky whether subsequent requests will use the same host
+ */
+ public void setSticky(boolean sticky) {
+ lastNodeId = null;
+ this.sticky = sticky;
+ }
+
+ /**
* Send a HEAD request
* @param path the path or URI
* @return a Response object with response detail