diff --git a/ChangeLog b/ChangeLog
index ba2ed67..5ba3a05 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+v4.3.0 (2017-11-23)
+---------------------------
+* added load balancing (ArangoDBAsync.Builder.loadBalancingStrategy())
+* added automatic acquiring of hosts for load balancing or as fallback (ArangoDBAsync.Builder.acquireHostList())
+
v4.2.7 (2017-11-03)
---------------------------
* added ArangoGraphAsync.exists()
diff --git a/README.md b/README.md
index e879a18..06145dc 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,8 @@
arangodb-java-driver | ArangoDB | network protocol | Java version |
-4.2.x | 3.0.x, 3.1.x, 3.2.x | VelocyStream | 1.8+ |
+4.3.x | 3.0.0+ | VelocyStream | 1.8+ |
+4.2.x | 3.0.0+ | VelocyStream | 1.8+ |
4.1.x | 3.1.x | VelocyStream | 1.8+ |
@@ -24,18 +25,18 @@
To add the driver to your project with maven, add the following code to your pom.xml
(please use a driver with a version number compatible to your ArangoDB server's version):
-ArangoDB 3.2.X
+ArangoDB 3.x.x
```XML
com.arangodb
arangodb-java-driver-async
- 4.2.0
+ 4.3.0
```
-If you want to test with a snapshot version (e.g. 4.2.0-SNAPSHOT), add the staging repository of oss.sonatype.org to your pom.xml:
+If you want to test with a snapshot version (e.g. 4.3.0-SNAPSHOT), add the staging repository of oss.sonatype.org to your pom.xml:
```XML
@@ -58,8 +59,10 @@ mvn clean install -DskipTests=true -Dgpg.skip=true -Dmaven.javadoc.skip=true -B
* [Network protocol](#network-protocol)
* [SSL](#ssl)
* [Connection pooling](#connection-pooling)
+ * [Fallback hosts](#fallback-hosts)
+ * [Load Balancing](#load-balancing)
* [configure VelocyPack serialization](#configure-velocypack-serialization)
- * [Java 8 types](#java-8-types)
+ * [Java 8 types](#java-8-types)
* [Scala types](#scala-types)
* [Joda-Time](#joda-time)
* [custom serializer](#custom-serializer)
@@ -114,7 +117,7 @@ Setup with default configuration, this automatically loads a properties file ara
``` Java
// this instance is thread-safe
- ArangoDB arangoDB = new ArangoDB.Builder().build();
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build();
```
@@ -135,15 +138,15 @@ The driver is configured with some default values:
To customize the configuration the parameters can be changed in the code...
``` Java
- ArangoDB arangoDB = new ArangoDB.Builder().host("192.168.182.50", 8888).build();
-
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().host("192.168.182.50", 8888).build();
+
```
... or with a custom properties file (my.properties)
``` Java
InputStream in = MyClass.class.getResourceAsStream("my.properties");
- ArangoDB arangoDB = new ArangoDB.Builder().loadProperties(in).build();
-
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().loadProperties(in).build();
+
```
Example for arangodb.properties:
@@ -159,9 +162,9 @@ Example for arangodb.properties:
The drivers default used network protocol is the binary protocol VelocyStream which offers the best performance within the driver. To use HTTP, you have to set the configuration `useProtocol` to `Protocol.HTTP_JSON` for HTTP with Json content or `Protocol.HTTP_VPACK` for HTTP with [VelocyPack](https://github.com/arangodb/velocypack/blob/master/VelocyPack.md) content.
``` Java
-
- ArangoDB arangoDB = new ArangoDB.Builder().useProtocol(Protocol.VST).build();
-
+
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().useProtocol(Protocol.VST).build();
+
```
**Note**: If you are using ArangoDB 3.0.x you have to set the protocol to `Protocol.HTTP_JSON` because it is the only one supported.
@@ -171,9 +174,9 @@ The drivers default used network protocol is the binary protocol VelocyStream wh
To use SSL, you have to set the configuration `useSsl` to `true` and set a `SSLContext`. (see [example code](../src/test/java/com/arangodb/example/ssl/SslExample.java))
``` Java
-
- ArangoDB arangoDB = new ArangoDB.Builder().useSsl(true).sslContext(sc).build();
-
+
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().useSsl(true).sslContext(sc).build();
+
```
## Connection Pooling
@@ -182,9 +185,57 @@ The driver supports connection pooling with a default of 1 maximum connections.
``` Java
- ArangoDB arangoDB = new ArangoDB.Builder().maxConnections(8).build();
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().maxConnections(8).build();
+
+```
+
+## Fallback hosts
+
+The driver supports configuring multiple hosts. The first host is used to open a connection to. When this host is not reachable the next host from the list is used. To use this feature just call the method `host(String, int)` multiple times.
+
+``` Java
+
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().host("host1", 8529).host("host2", 8529).build();
```
+
+Since version 4.3 the driver support acquiring a list of known hosts in a cluster setup or a single server setup with followers. For this the driver has to be able to successfully open a connection to at least one host to get the list of hosts. Then it can use this list when fallback is needed. To use this feature just pass `true` to the method `acquireHostList(boolean)`.
+
+``` Java
+
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().acquireHostList(true).build();
+
+```
+
+## Load Balancing
+
+Since version 4.3 the driver supports load balancing for cluster setups in two different ways.
+
+The first one is a round robin load balancing where the driver iterates through a list of known hosts and performs every request on a different host than the request before. This load balancing strategy only work when the maximun of connections is greater 1.
+
+``` Java
+
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().loadBalancingStrategy(LoadBalancingStrategy.ROUND_ROBIN).maxConnections(8).build();
+
+```
+
+Just like the Fallback hosts feature the round robin load balancing strategy can use the `acquireHostList` configuration to acquire a list of all known hosts in the cluster. Do so only requires the manually configuration of only one host. Because this list is updated frequently it makes load balancing over the whole cluster very comfortable.
+
+``` Java
+
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().loadBalancingStrategy(LoadBalancingStrategy.ROUND_ROBIN).maxConnections(8).acquireHostList(true).build();
+
+```
+
+The second load balancing strategy allows to pick a random host from the configured or acquired list of hosts and sticks to that host as long as the connection is open. This strategy is useful for an application - using the driver - which provides a session management where each session has its own instance of `ArangoDB` build from a global configured list of hosts. In this case it could be wanted that every sessions sticks with all its requests to the same host but not all sessions should use the same host. This load balancing strategy also works together with `acquireHostList`.
+
+
+``` Java
+
+ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().loadBalancingStrategy(LoadBalancingStrategy.ONE_RANDOM).acquireHostList(true).build();
+
+```
+
## configure VelocyPack serialization
Since version `4.1.11` you can extend the VelocyPack serialization by registering additional `VPackModule`s on `ArangoDB.Builder`.
@@ -211,8 +262,8 @@ Added support for:
```
``` Java
-ArangoDB arangoDB = new ArangoDB.Builder().registerModule(new VPackJdk8Module()).build();
-```
+ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().registerModule(new VPackJdk8Module()).build();
+```
### Scala types
@@ -233,7 +284,7 @@ Added support for:
``` Scala
val arangoDB: ArangoDB = new ArangoDB.Builder().registerModule(new VPackScalaModule).build
-```
+```
### Joda-Time
@@ -255,7 +306,7 @@ Added support for:
``` Java
ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().registerModule(new VPackJodaModule()).build();
-```
+```
## custom serializer
``` Java
@@ -282,23 +333,23 @@ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().registerModule(new VPackJod
});
}
}).build();
-```
+```
# Manipulating databases
## create database
``` Java
- // create database
+ // create database
arangoDB.createDatabase("myDatabase");
-
+
```
## drop database
``` Java
- // drop database
+ // drop database
arangoDB.db("myDatabase").drop();
-
+
```
# Manipulating collections
@@ -307,14 +358,14 @@ ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().registerModule(new VPackJod
``` Java
// create collection
arangoDB.db("myDatabase").createCollection("myCollection", null);
-
+
```
## drop collection
``` Java
- // delete collection
+ // delete collection
arangoDB.db("myDatabase").collection("myCollection").drop();
-
+
```
## truncate collection
@@ -348,15 +399,15 @@ For the next examples we use a small object:
/*
* + getter and setter
*/
-
- }
+
+ }
```
## insert document
``` Java
MyObject myObject = new MyObject("Homer", 38);
arangoDB.db("myDatabase").collection("myCollection").insertDocument(myObject);
-
+
```
When creating a document, the attributes of the object will be stored as key-value pair
@@ -369,19 +420,19 @@ E.g. in the previous example the object was stored as follows:
## delete document
``` Java
arangoDB.db("myDatabase").collection("myCollection").deleteDocument(myObject.getKey());
-
+
```
## update document
``` Java
arangoDB.db("myDatabase").collection("myCollection").updateDocument(myObject.getKey(), myUpdatedObject);
-
+
```
## replace document
``` Java
arangoDB.db("myDatabase").collection("myCollection").replaceDocument(myObject.getKey(), myObject2);
-
+
```
## read document as JavaBean
@@ -390,7 +441,7 @@ E.g. in the previous example the object was stored as follows:
document.getName();
document.getAge();
}
-
+
```
## read document as VelocyPack
@@ -399,26 +450,26 @@ E.g. in the previous example the object was stored as follows:
document.get("name").getAsString();
document.get("age").getAsInt();
}
-
+
```
## read document as Json
``` Java
arangoDB.db("myDatabase").collection("myCollection").getDocument(myObject.getKey(), String.class).thenAccept(document -> {
}
-
+
```
## read document by key
``` Java
arangoDB.db("myDatabase").collection("myCollection").getDocument("myKey", MyObject.class);
-
+
```
## read document by id
``` Java
arangoDB.db("myDatabase").getDocument("myCollection/myKey", MyObject.class);
-
+
```
# Multi Document operations
@@ -430,7 +481,7 @@ E.g. in the previous example the object was stored as follows:
documents.add(myObject2);
documents.add(myObject3);
arangoDB.db("myDatabase").collection("myCollection").insertDocuments(documents);
-
+
```
## delete documents
@@ -440,7 +491,7 @@ E.g. in the previous example the object was stored as follows:
keys.add(myObject2.getKey());
keys.add(myObject3.getKey());
arangoDB.db("myDatabase").collection("myCollection").deleteDocuments(keys);
-
+
```
## update documents
@@ -450,7 +501,7 @@ E.g. in the previous example the object was stored as follows:
documents.add(myObject2);
documents.add(myObject3);
arangoDB.db("myDatabase").collection("myCollection").updateDocuments(documents);
-
+
```
## replace documents
@@ -460,7 +511,7 @@ E.g. in the previous example the object was stored as follows:
documents.add(myObject2);
documents.add(myObject3);
arangoDB.db("myDatabase").collection("myCollection").replaceDocuments(documents);
-
+
```
# AQL
@@ -474,19 +525,19 @@ E.g. get all Simpsons aged 3 or older in ascending order:
``` Java
arangoDB.createDatabase("myDatabase");
ArangoDatabaseAsync db = arangoDB.db("myDatabase");
-
+
db.createCollection("myCollection");
ArangoCollectionAsync collection = db.collection("myCollection");
-
+
collection.insertDocument(new MyObject("Homer", 38)).get();
collection.insertDocument(new MyObject("Marge", 36)).get();
collection.insertDocument(new MyObject("Bart", 10)).get();
collection.insertDocument(new MyObject("Lisa", 8)).get();
collection.insertDocument(new MyObject("Maggie", 2)).get();
-
+
Map bindVars = new HashMap<>();
bindVars.put("age", 3);
-
+
db.query(query, bindVars, null, MyObject.class).thenAccept(cursor -> {
cursor.forEachRemaining(obj -> {
System.out.println(obj.getName());
@@ -500,7 +551,7 @@ or return the AQL result as VelocyPack:
db.query(query, bindVars, null, VPackSlice.class).thenAccept(cursor -> {
cursor.forEachRemaining(obj -> {
System.out.println(obj.get("name").getAsString());
- });
+ });
});
```
@@ -522,19 +573,19 @@ A graph consists of vertices and edges (stored in collections). Which collection
edgeDefinition.collection("myEdgeCollection");
// define a set of collections where an edge is going out...
edgeDefinition.from("myCollection1", "myCollection2");
-
- // repeat this for the collections where an edge is going into
+
+ // repeat this for the collections where an edge is going into
edgeDefinition.to("myCollection1", "myCollection3");
-
+
edgeDefinitions.add(edgeDefinition);
-
+
// A graph can contain additional vertex collections, defined in the set of orphan collections
GraphCreateOptions options = new GraphCreateOptions();
options.orphanCollections("myCollection4", "myCollection5");
-
+
// now it's possible to create a graph
arangoDB.db("myDatabase").createGraph("myGraph", edgeDefinitions, options);
-
+
```
## delete graph
@@ -554,7 +605,7 @@ Vertices are stored in the vertex collections defined above.
MyObject myObject2 = new MyObject("Marge", 36);
arangoDB.db("myDatabase").graph("myGraph").vertexCollection("collection1").insertVertex(myObject1, null);
arangoDB.db("myDatabase").graph("myGraph").vertexCollection("collection3").insertVertex(myObject2, null);
-
+
```
## add edge
@@ -563,8 +614,8 @@ Now an edge can be created to set a relation between vertices
``` Java
arangoDB.db("myDatabase").graph("myGraph").edgeCollection("myEdgeCollection").insertEdge(myEdgeObject, null);
-
-```
+
+```
# Foxx
@@ -572,8 +623,8 @@ Now an edge can be created to set a relation between vertices
``` Java
Request request = new Request("mydb", RequestType.GET, "/my/foxx/service")
CompletableFuture response = arangoDB.executeAsync(request);
-
-```
+
+```
# User management
@@ -625,7 +676,7 @@ The driver can serialize/deserialize JavaBeans. They need at least a constructor
super();
}
- }
+ }
```
## internal fields
@@ -636,7 +687,7 @@ To use Arango-internal fields (like _id, _key, _rev, _from, _to) in your JavaBea
@DocumentField(Type.KEY)
private String key;
-
+
private String name;
private Gender gender;
private int age;
@@ -645,7 +696,7 @@ To use Arango-internal fields (like _id, _key, _rev, _from, _to) in your JavaBea
super();
}
- }
+ }
```
## serialized fieldnames
@@ -656,7 +707,7 @@ To use a different serialized name for a field, use the annotation `SerializedNa
@SerializedName("title")
private String name;
-
+
private Gender gender;
private int age;
@@ -664,7 +715,7 @@ To use a different serialized name for a field, use the annotation `SerializedNa
super();
}
- }
+ }
```
## ignore fields
@@ -683,7 +734,7 @@ To ignore fields at serialization/deserialization, use the annotation `Expose`
super();
}
- }
+ }
```
## custom serializer
@@ -711,7 +762,7 @@ To ignore fields at serialization/deserialization, use the annotation `Expose`
});
}
}).build();
-```
+```
## manually serialization
To de-/serialize from and to VelocyPack before or after a database call, use the `ArangoUtil` from the method `util()` in `ArangoDB`, `ArangoDatabase`, `ArangoCollection`, `ArangoGraph`, `ArangoEdgeCollection`or `ArangoVertexCollection`.
diff --git a/pom.xml b/pom.xml
index a8e50f2..7f5f087 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.arangodb
arangodb-java-driver-async
- 4.2.8-SNAPSHOT
+ 4.3.0-SNAPSHOT
2016
jar
diff --git a/src/main/java/com/arangodb/ArangoDBAsync.java b/src/main/java/com/arangodb/ArangoDBAsync.java
index 38919a5..464432e 100644
--- a/src/main/java/com/arangodb/ArangoDBAsync.java
+++ b/src/main/java/com/arangodb/ArangoDBAsync.java
@@ -25,28 +25,39 @@
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import javax.net.ssl.SSLContext;
import com.arangodb.entity.ArangoDBVersion;
+import com.arangodb.entity.LoadBalancingStrategy;
import com.arangodb.entity.LogEntity;
import com.arangodb.entity.LogLevelEntity;
import com.arangodb.entity.Permissions;
import com.arangodb.entity.ServerRole;
import com.arangodb.entity.UserEntity;
import com.arangodb.internal.ArangoDBConstants;
+import com.arangodb.internal.ArangoExecutor.ResponseDeserializer;
import com.arangodb.internal.ArangoExecutorAsync;
import com.arangodb.internal.CollectionCache;
import com.arangodb.internal.CollectionCache.DBAccess;
-import com.arangodb.internal.CommunicationProtocol;
-import com.arangodb.internal.DefaultHostHandler;
import com.arangodb.internal.DocumentCache;
import com.arangodb.internal.Host;
-import com.arangodb.internal.HostHandler;
import com.arangodb.internal.InternalArangoDB;
+import com.arangodb.internal.net.CommunicationProtocol;
+import com.arangodb.internal.net.ExtendedHostResolver;
+import com.arangodb.internal.net.FallbackHostHandler;
+import com.arangodb.internal.net.HostHandler;
+import com.arangodb.internal.net.HostResolver;
+import com.arangodb.internal.net.HostResolver.EndpointResolver;
+import com.arangodb.internal.net.RandomHostHandler;
+import com.arangodb.internal.net.RoundRobinHostHandler;
+import com.arangodb.internal.net.SimpleHostResolver;
import com.arangodb.internal.util.ArangoDeserializerImpl;
import com.arangodb.internal.util.ArangoSerializerImpl;
import com.arangodb.internal.util.ArangoUtilImpl;
@@ -75,9 +86,12 @@
import com.arangodb.velocypack.VPackParser;
import com.arangodb.velocypack.VPackParserModule;
import com.arangodb.velocypack.VPackSerializer;
+import com.arangodb.velocypack.VPackSlice;
import com.arangodb.velocypack.ValueType;
+import com.arangodb.velocypack.exception.VPackException;
import com.arangodb.velocypack.module.jdk8.VPackJdk8Module;
import com.arangodb.velocystream.Request;
+import com.arangodb.velocystream.RequestType;
import com.arangodb.velocystream.Response;
/**
@@ -101,6 +115,8 @@ public static class Builder {
private final VPackParser.Builder vpackParserBuilder;
private ArangoSerializer serializer;
private ArangoDeserializer deserializer;
+ private Boolean acquireHostList;
+ private LoadBalancingStrategy loadBalancingStrategy;
public Builder() {
super();
@@ -129,6 +145,8 @@ public Builder loadProperties(final InputStream in) {
useSsl = loadUseSsl(properties, useSsl);
chunksize = loadChunkSize(properties, chunksize);
maxConnections = loadMaxConnections(properties, maxConnections);
+ acquireHostList = loadAcquireHostList(properties, acquireHostList);
+ loadBalancingStrategy = loadLoadBalancingStrategy(properties, loadBalancingStrategy);
} catch (final IOException e) {
throw new ArangoDBException(e);
}
@@ -209,6 +227,16 @@ public Builder maxConnections(final Integer maxConnections) {
return this;
}
+ public Builder acquireHostList(final Boolean acquireHostList) {
+ this.acquireHostList = acquireHostList;
+ return this;
+ }
+
+ public Builder loadBalancingStrategy(final LoadBalancingStrategy loadBalancingStrategy) {
+ this.loadBalancingStrategy = loadBalancingStrategy;
+ return this;
+ }
+
public Builder registerSerializer(final Class clazz, final VPackSerializer serializer) {
vpackBuilder.registerSerializer(clazz, serializer);
return this;
@@ -343,9 +371,11 @@ public synchronized ArangoDBAsync build() {
: new ArangoSerializerImpl(vpacker, vpackerNull, vpackParser);
final ArangoDeserializer deserializerTemp = deserializer != null ? deserializer
: new ArangoDeserializerImpl(vpackerNull, vpackParser);
- final HostHandler hostHandler = new DefaultHostHandler(new ArrayList<>(hosts));
+
+ final HostResolver hostResolver = createHostResolver();
+ final HostHandler hostHandler = createHostHandler(hostResolver);
return new ArangoDBAsync(asyncBuilder(hostHandler), new ArangoUtilImpl(serializerTemp, deserializerTemp),
- collectionCache, syncBuilder(hostHandler));
+ collectionCache, syncBuilder(hostHandler), hostResolver);
}
private VstCommunicationAsync.Builder asyncBuilder(final HostHandler hostHandler) {
@@ -358,12 +388,39 @@ private VstCommunicationSync.Builder syncBuilder(final HostHandler hostHandler)
.useSsl(useSsl).sslContext(sslContext).chunksize(chunksize).maxConnections(maxConnections);
}
+ private HostResolver createHostResolver() {
+ return acquireHostList != null && acquireHostList.booleanValue()
+ ? new ExtendedHostResolver(new ArrayList<>(hosts)) : new SimpleHostResolver(new ArrayList<>(hosts));
+ }
+
+ private HostHandler createHostHandler(final HostResolver hostResolver) {
+ final HostHandler hostHandler;
+ if (loadBalancingStrategy != null) {
+ switch (loadBalancingStrategy) {
+ case ONE_RANDOM:
+ hostHandler = new RandomHostHandler(hostResolver, new FallbackHostHandler(hostResolver));
+ break;
+ case ROUND_ROBIN:
+ hostHandler = new RoundRobinHostHandler(hostResolver);
+ break;
+ case NONE:
+ default:
+ hostHandler = new FallbackHostHandler(hostResolver);
+ break;
+ }
+ } else {
+ hostHandler = new FallbackHostHandler(hostResolver);
+ }
+ return hostHandler;
+ }
+
}
private final CommunicationProtocol cp;
public ArangoDBAsync(final VstCommunicationAsync.Builder commBuilder, final ArangoSerialization util,
- final CollectionCache collectionCache, final VstCommunicationSync.Builder syncbuilder) {
+ final CollectionCache collectionCache, final VstCommunicationSync.Builder syncbuilder,
+ final HostResolver hostResolver) {
super(new ArangoExecutorAsync(commBuilder.build(util, collectionCache), util, new DocumentCache()), util);
final VstCommunication cacheCom = syncbuilder.build(util, collectionCache);
cp = new VstProtocol(cacheCom);
@@ -373,6 +430,47 @@ public ArangoDatabase db(final String name) {
return new ArangoDatabase(cp, util, executor.documentCache(), name);
}
});
+ hostResolver.init(new EndpointResolver() {
+ @Override
+ public Collection resolve(final boolean closeConnections) throws ArangoDBException {
+ try {
+ return executor.execute(
+ new Request(ArangoDBConstants.SYSTEM, RequestType.GET, ArangoDBConstants.PATH_ENDPOINTS),
+ new ResponseDeserializer>() {
+ @Override
+ public Collection deserialize(final Response response) throws VPackException {
+ final VPackSlice field = response.getBody().get(ArangoDBConstants.ENDPOINTS);
+ Collection endpoints;
+ if (field.isNone()) {
+ endpoints = Collections. emptyList();
+ } else {
+ final Collection