From a12dc7018d97593a41ee823fafe33193db9c8ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Hartman?= Date: Thu, 17 Oct 2024 17:17:06 +0200 Subject: [PATCH] Document Vert.x integration [DEX-172] (#1280) Co-authored-by: Amanda Lindsay Co-authored-by: Oliver Howell --- docs/modules/ROOT/nav.adoc | 2 + .../architecture/pages/data-partitioning.adoc | 1 + .../pages/get-started-with-vertx.adoc | 461 ++++++++++++++++++ .../integrate/pages/integrate-with-vertx.adoc | 132 +++++ 4 files changed, 596 insertions(+) create mode 100644 docs/modules/integrate/pages/get-started-with-vertx.adoc create mode 100644 docs/modules/integrate/pages/integrate-with-vertx.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 1fe2fc556..9db3c0af5 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -184,6 +184,8 @@ include::wan:partial$nav.adoc[] ** xref:integrate:feast-config.adoc[] ** xref:integrate:feature-engineering-with-feast.adoc[Get started with Feast batch features] ** xref:integrate:streaming-features-with-feast.adoc[Get started with Feast streaming features] +* xref:integrate:integrate-with-vertx.adoc[] +** xref:integrate:get-started-with-vertx.adoc[] // Connectors * Messaging System Connectors ** xref:integrate:messaging-system-connectors.adoc[Overview] diff --git a/docs/modules/architecture/pages/data-partitioning.adoc b/docs/modules/architecture/pages/data-partitioning.adoc index e4c23a70d..7ded99527 100644 --- a/docs/modules/architecture/pages/data-partitioning.adoc +++ b/docs/modules/architecture/pages/data-partitioning.adoc @@ -201,6 +201,7 @@ Please note that `IndeterminateOperationStateException` does not apply to read-only operations, such as `map.get()`. If a partition primary replica member crashes before replying to a read-only operation, the operation is retried on the new owner of the primary replica. +[[best-effort-consistency]] === Best-Effort Consistency The replication algorithm for AP data structures enables Hazelcast clusters to offer high throughput. diff --git a/docs/modules/integrate/pages/get-started-with-vertx.adoc b/docs/modules/integrate/pages/get-started-with-vertx.adoc new file mode 100644 index 000000000..dea60ddee --- /dev/null +++ b/docs/modules/integrate/pages/get-started-with-vertx.adoc @@ -0,0 +1,461 @@ += Get started with Vert.x + +This tutorial helps you integrate Vert.x with Hazelcast and use Hazelcast for distributed session management and other distributed data structures. + +In this tutorial, you will + +- start with a simple Vert.x Hello World application +- add vertx-hazelcast module and enable distributed session management +- use `io.vertx.core.shareddata.Counter` data structure to implement a unique id generator + +== Prerequisites + +- Java 17 or newer +- Maven 3.9+ +- link:https://httpie.io[httpie] client + +== Create a new project + +1. Go to link:https://start.vertx.io[start.vertx.io], change the artifact id to `messages`, the version to 5.0.0, and generate a new project. + +2. Extract the project and build it using: + +[source,bash] +---- +$ mvn clean package +---- + +and start the application using: + +[source,bash] +---- +java -jar target/messages-1.0.0-SNAPSHOT-fat.jar +---- + +You should see output similar to the following: + +[source] +---- +HTTP server started on port 8888 +Aug 29, 2024 2:22:38 PM io.vertx.launcher.application.VertxApplication +INFO: Succeeded in deploying verticle +---- + +== Storing Data in Session + +Go to the `MainVerticle.java` file and replace the contents of the start method with the following: + +NOTE: This tutorial uses 2-space indentation, which is customary for Vertx projects due to the high number of nested callbacks. + +[source,java] +---- + public void start() { + // Create a Router + Router router = router(vertx); + + // Create local SessionStore + SessionStore store = LocalSessionStore.create(vertx); + + // Use the SessionStore to handle all requests + router.route() + .handler(SessionHandler.create(store)); + + router.route(HttpMethod.PUT, "/").handler(context -> { + context.request().bodyHandler(body -> { + List messages = getMessagesFromSession(context); + + JsonObject json = body.toJsonObject(); + String message = json.getString("message"); + messages.add(message); + + putMessagesToSession(context, messages); + + context.json( + new JsonObject() + .put("messages", messages) + ); + }); + }); + + // Create the HTTP server + vertx.createHttpServer() + // Handle every request using the router + .requestHandler(router) + // Start listening + .listen(8888) + // Print the port + .onSuccess(server -> + System.out.println( + "HTTP server started on port " + server.actualPort() + ) + ); + } + + private static List getMessagesFromSession(RoutingContext context) { + String messages = context.session().get("messages"); + if (messages == null) { + return new ArrayList<>(); + } else { + return new ArrayList<>(Arrays.asList(messages.split(","))); + } + } + + private void putMessagesToSession(RoutingContext context, List messages) { + context.session().put("messages", String.join(",", messages)); + } +---- + +[source,bash] +---- +$ http put localhost:8888 message=Hello\ World! +HTTP/1.1 200 OK +content-length: 29 +content-type: application/json +set-cookie: vertx-web.session=ed22f77473a7f613c9305431a62832a6; Path=/ + +{ + "messages": [ + "Hello World!" + ] +} + +---- + +Execute another request with the cookie: + +[source,bash] +---- +$ http put localhost:8888 'Cookie:vertx-web.session=ed22f77473a7f613c9305431a62832a6' message=Hello\ World\ 2! +HTTP/1.1 200 OK +content-length: 46 +content-type: application/json + +{ + "messages": [ + "Hello World!", + "Hello World 2!" + ] +} + +---- + +== Distributed Sessions + +Let's modify the code, so we can start multiple instances easily - the application will start on the defined port, and when the port is not available it will search for another port: + +Add the following method to the `MainVerticle.java` class: + +[source,java] +---- + private int findFreePort(int from) { + for (int port = from; port < from + 100; port++) { + try { + new ServerSocket(port).close(); + return port; + } catch (IOException e) { + // port not available, try next + } + } + throw new RuntimeException("Could not find an available port"); + } +---- + +and use it in the `start` method: + +[source,java] +---- + ... + int port = findFreePort(8888); + + // Create the HTTP server + vertx.createHttpServer() + // Handle every request using the router + .requestHandler(router) + // Start listening + .listen(port) + ... +---- + +Now, we can start two instances: + +[source,bash] +---- +$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar +HTTP server started on port 8888 +Aug 30, 2024 9:09:44 AM io.vertx.launcher.application.VertxApplication +INFO: Succeeded in deploying verticle + +... + +$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar +HTTP server started on port 8889 +Aug 30, 2024 9:09:47 AM io.vertx.launcher.application.VertxApplication +INFO: Succeeded in deploying verticle +---- + +and we can see the session is not shared between the instances. Here is the request to the first instance: + +[source, bash] +---- +$ http PUT localhost:8888 message="Hello world" +HTTP/1.1 200 OK +content-length: 28 +content-type: application/json +set-cookie: vertx-web.session=00f219c166ca50727d23eaaf9fe54229; Path=/ + +{ + "messages": [ + "Hello world" + ] +} +---- + +and here is the request to the 2nd instance. Notice the different port and that we use the cookie we received, but the data does not contain the previous message. + +[source, bash] +---- +$ http PUT localhost:8889 message="Hello world 2" 'Cookie: vertx-web.session=00f219c166ca50727d23eaaf9fe54229' +HTTP/1.1 200 OK +content-length: 30 +content-type: application/json +set-cookie: vertx-web.session=a1486c5ed6416972fdc356e4d91d2397; Path=/ + +{ + "messages": [ + "Hello world 2" + ] +} +---- + +We will fix that by using a Hazelcast Cluster Manager. There are two modules that provide Hazelcast Cluster Manager: + +- `io.vertx:vertx-hazelcast` - this module is maintained by the Vert.x team, with contributions from Hazelcast, and is built on top of open-source Hazelcast +- `com.hazelcast:vertx-hazelcast-enterprise` - this module is maintained by the Hazelcast team and is built on top of the `vertx-hazelcast` but uses Hazelcast Enterprise instead. You need an enterprise license to use Hazelcast Enterprise. + +You can use either module for most of this tutorial. At the end of this tutorial you will need the `vertx-hazelcast-enterprise` module. + +NOTE: You can get your trial key at https://hazelcast.com/get-started/?utm_source=docs-website or you can use `vertx-hazelcast` and a community edition of Hazelcast. + +Add the following dependency to the `pom.xml`: + +[source,xml] +---- + + com.hazelcast + vertx-hazelcast-enterprise + {vertx.version} + +---- + +Change the following part of the `start` method: + +[source,java] +---- +// Create local SessionStore +SessionStore store = LocalSessionStore.create(vertx); +---- + +to the following: + +[source,java] +---- +// Create clustered SessionStore +SessionStore store = ClusteredSessionStore.create(vertx); +---- + +and from now on we will start the application with `-server` parameter, which tells Vert.x to look for a cluster manager implementation. + +We also need to provide a Hazelcast configuration file, and create a file cluster.xml in the `src/main/resources` directory: + +[source,xml] +---- + + + + + replace/with/your/key + + + + + + + + + 1 + SET + + + + 1 + + + + 1 + + + + 0 + + + __vertx.* + false + 1 + + + + +---- + +Now rebuild the project and start the application. You will see more verbose output as Hazelcast prints its own startup logs: + +[source,bash] +---- +$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar -cluster +... +HTTP server started on port 8888 +... +Members {size:2, ver:2} [ + Member [192.168.0.10]:5701 - e29f0362-f9a9-4708-b6e5-1a6067b5aa39 this + Member [192.168.0.10]:5702 - 74014573-a18a-44f2-9ca7-fd90b70dcb43 +] +... +---- + +and + +[source,bash] +---- +$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar -cluster +... +HTTP server started on port 8889 +... +Members {size:2, ver:2} [ + Member [192.168.0.10]:5701 - e29f0362-f9a9-4708-b6e5-1a6067b5aa39 + Member [192.168.0.10]:5702 - 74014573-a18a-44f2-9ca7-fd90b70dcb43 this +] +... +---- + +Putting two messages into different instances while using the same cookie, we see that the session is shared between the instances. + +[source,bash] +---- +$ http PUT localhost:8888 message="Hello world" +HTTP/1.1 200 OK +content-length: 31 +content-type: application/json +set-cookie: vertx-web.session=1ab47cb96731123135f25ec7b67efd64; Path=/ + +{ + "messages": [ + "", + "Hello world" + ] +} +---- + +[source,bash] +---- +$ http PUT localhost:8889 message="Hello world 2" 'Cookie: vertx-web.session=674806546c690674962f279670abefcf' +HTTP/1.1 200 OK +content-length: 44 +content-type: application/json + +{ + "messages": [ + "Hello world", + "Hello world 2" + ] +} +---- + +== Using Counter + +Replace this part of the code at the end of the `start()` method: + +[source,java] +---- +context.json( + new JsonObject() + .put("messages", messages) +); +---- + +with the following: + +[source,java] +---- +context.vertx() + .sharedData() + .getCounter("requestId") + .onSuccess(counter -> { + counter.incrementAndGet() + .onSuccess(requestId -> { + context.json( + new JsonObject() + .put("requestId", requestId) + .put("messages", messages) + ); + }); + }); +---- + +When you now try the application, you can see the response contains an additional field named `requestId` and its value increments for every request. + +[source,bash] +---- +$ http PUT localhost:8888 message="Hello world" +HTTP/1.1 200 OK +content-length: 42 +content-type: application/json +set-cookie: vertx-web.session=d9fb4cada5c0fc625089a38f3de13e3c; Path=/ + +{ + "messages": [ + "Hello world" + ], + "requestId": 1 +} +---- + +== CP Subsystem backed Lock and Counter + +The module `vertx-hazelcast-enterprise` provides a different implementation of the `io.vertx.core.shareddata.Counter` and `io.vertx.core.shareddata.Lock` data structures. The implementation in `vertx-hazelcast` is based on the IMap data structure and provides guarantees defined in the xref:architecture:data-partitioning.adoc#best-effort-consistency[Best-effort consistency] section. This means that under certain network partition conditions the counter doesn't provide strong consistency guarantees and can generate duplicate values. + +The module `vertx-hazelcast-enterprise` uses the CP Subsystem from {enterprise-product-name} to implement the Lock and Counter. + +NOTE: For the rest of this tutorial you need to have an {enterprise-product-name} license. + +Make sure you have the following dependency: + +[source,xml] +---- + + com.hazelcast + vertx-hazelcast-enterprise + {vertx.version} + +---- + +and your XML config contains a valid license key: + +[source,xml] +---- +... + replace/with/your/key +... +---- + +Enable the CP subsystem, and in cluster.xml change the value of the `` property to `3`: + +[source,xml] +---- + 3 +---- + +You need to start at least 3 instances for the cluster to form successfully. For complete documentation, see the xref:cp-subsystem:cp-subsystem.adoc[CP Subsystem] section. + diff --git a/docs/modules/integrate/pages/integrate-with-vertx.adoc b/docs/modules/integrate/pages/integrate-with-vertx.adoc new file mode 100644 index 000000000..3355f8b9e --- /dev/null +++ b/docs/modules/integrate/pages/integrate-with-vertx.adoc @@ -0,0 +1,132 @@ += Integrate with Vert.x + +Vert.x is a reactive application toolkit for creating resource-efficient, concurrent, asynchronous and flexible applications on the JVM. + +Hazelcast integrates with Vert.x in a form of a cluster manager - the link:https://vertx.io/docs/vertx-hazelcast/java/[Hazelcast Cluster Manager]. + + +In Vert.x a cluster manager is used for various functions including: + +- Discovery and group membership of Vert.x nodes in a cluster +- Maintaining cluster-wide topic subscriber lists (so we know which nodes are interested in which event bus addresses) +- Distributed Map support +- Distributed Locks +- Distributed Counters + +There are 2 modules to choose from: +- `io.vertx:vertx-hazelcast` - this module is part of Vert.x and is maintained by the Vert.x team with contributions from Hazelcast developers. This module is licensed under the Apache 2 license. + +- `com.hazelcast:vertx-hazelcast-enterprise` - this module is built on top of `vertx-hazelcast` and leverages functionality of {enterprise-product-name} to implement some of the cluster manager functionality (e.g. it uses the CP Subsystem to implement strongly consistent `io.vertx.core.shareddata.Lock` and `io.vertx.core.shareddata.Counter`). + +== Using Vert.x Hazelcast Enterprise Cluster Manager +[.enterprise]*Enterprise* + +To use the Vert.x Hazelcast Enterprise Cluster Manager, add the following dependency to your Vert.x application: + +[source,xml] +---- + + com.hazelcast + vertx-hazelcast-enterprise + 5.0.0-SNAPSHOT + +---- + +The dependency is located in the Hazelcast private repository, and you need to add that as well: + +[source,xml] +---- + + + hazelcast-private-repository + Hazelcast Private Repository + https://repository.hazelcast.com/release/ + + +---- + +Alternatively, if you need to use a snapshot version: + +[source,xml] +---- + + + hazelcast-private-snapshot-repository + Hazelcast Private Snapshot Repository + https://repository.hazelcast.com/snapshot/ + + +---- + +To enable clustering, start your Vert.x application with the `-cluster` parameter. + +=== Configuration + +Provide a file named `cluster.xml` on your classpath to configure a Hazelcast instance used by Vert.x. + +To take advantage of the Lock and Counter data structures backed by the CP subsystem, you need to enable the xref:cp-subsystem:cp-subsystem.adoc[CP Subsytem]. + +For other configuration methods see the link:https://vertx.io/docs/vertx-hazelcast/java/#configcluster[Vert.x documentation]. + +=== Versioning and Hazelcast compatibility + +The Vert.x Hazelcast Enterprise module follows the versioning of Vertx. If you use an `x.y.z` version of Vert.x, you should use an `x.y.z` version of `vertx-hazelcast-enteprise`. + +The `vertx-hazelcast-enteprise` module is compatible with all Hazelcast versions supported at the time of the release of the `vertx-hazelcast-enteprise` module unless stated otherwise. + +While older versions may work, such configurations are not supported. + +== Using Vert.x Hazelcast Cluster Manager + +See the Vert.x Hazelcast Cluster Manager site for reference documentation for the `vertx-hazelcast` module. + +You can also follow our xref:get-started-with-vertx.adoc[Get started with Vert.x guide]. + +== Using a different Hazelcast version + +Due to Java compatibility reasons the Vert.x Hazelcast module doesn't depend on the latest version of Hazelcast. +You can change the Hazelcast dependency to any version of Hazelcast you need, e.g. you can change it to Hazelcast 5.2.x for Java 8 compatibilty, or to the latest. + +NOTE: The old versions may not be supported by Hazelcast anymore and don't receive any patches. + +There are multiple ways to replace the transitive dependency. The most reliable way is to exclude the `com.hazelcast:hazelcast` transitive dependency of the `vert-hazelcast` module and add a direct dependency on `com.hazelcast:hazelcast` to the pom.xml of your project. + +[source,xml] +---- + + io.vertx + vertx-hazelcast + + + com.hazelcast + hazelcast + + + + + com.hazelcast + hazelcast + 5.5.0 + +---- + +Similarly, for `vertx-hazelcast-enterprise`: + +[source,xml] +---- + + com.hazelcast + vertx-hazelcast-enterprise + + + com.hazelcast + hazelcast + + + + + com.hazelcast + hazelcast + 5.5.0 + +----