├── .editorconfig
├── .github
├── maven-cd-settings.xml
├── maven-ci-settings.xml
└── workflows
│ ├── ci-4.x.yml
│ ├── ci-5.x-stable.yml
│ ├── ci-5.x.yml
│ ├── ci-matrix-5.x.yml
│ ├── ci.yml
│ └── deploy.yml
├── .gitignore
├── .travis.deploy.artifacts.sh
├── .travis.yml
├── LICENSE.txt
├── README.adoc
├── pom.xml
└── src
├── main
├── asciidoc
│ └── index.adoc
├── java
│ ├── example
│ │ ├── Examples.java
│ │ └── package-info.java
│ └── io
│ │ └── vertx
│ │ └── spi
│ │ └── cluster
│ │ └── zookeeper
│ │ ├── ZookeeperClusterManager.java
│ │ ├── impl
│ │ ├── ConfigUtil.java
│ │ ├── RetryPolicyHelper.java
│ │ ├── SubsMapHelper.java
│ │ ├── ZKAsyncMap.java
│ │ ├── ZKCounter.java
│ │ ├── ZKLock.java
│ │ ├── ZKMap.java
│ │ └── ZKSyncMap.java
│ │ └── package-info.java
└── resources
│ ├── META-INF
│ └── services
│ │ └── io.vertx.core.spi.VertxServiceProvider
│ └── default-zookeeper.json
└── test
├── java
└── io
│ └── vertx
│ ├── core
│ ├── ProgrammaticZKClusterManagerTest.java
│ ├── ZKClusteredComplexHATest.java
│ ├── ZKClusteredHATest.java
│ ├── eventbus
│ │ ├── ZKClusteredEventbusTest.java
│ │ ├── ZKFaultToleranceTest.java
│ │ └── ZKNodeInfoTest.java
│ └── shareddata
│ │ ├── ZKClusteredAsyncMapTest.java
│ │ ├── ZKClusteredAsynchronousLockTest.java
│ │ └── ZKClusteredSharedCounterTest.java
│ ├── ext
│ └── web
│ │ └── sstore
│ │ └── ZKClusteredSessionHandlerTest.java
│ ├── servicediscovery
│ └── impl
│ │ └── ZKDiscoveryImplClusteredTest.java
│ └── spi
│ └── cluster
│ └── zookeeper
│ ├── ConsumerRoundRobinTest.java
│ ├── MockZKCluster.java
│ ├── RetryPolicyTest.java
│ └── ZKSyncMapTest.java
└── resources
├── log4j.properties
└── zookeeper.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | trim_trailing_whitespace = true
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.github/maven-cd-settings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | false
20 |
21 |
22 |
23 | vertx-snapshots-repository
24 | ${env.VERTX_NEXUS_USERNAME}
25 | ${env.VERTX_NEXUS_PASSWORD}
26 |
27 |
28 |
29 |
30 |
31 | google-mirror
32 |
33 | true
34 |
35 |
36 |
37 | google-maven-central
38 | GCS Maven Central mirror EU
39 | https://maven-central.storage-download.googleapis.com/maven2/
40 |
41 | true
42 |
43 |
44 | false
45 |
46 |
47 |
48 |
49 |
50 | google-maven-central
51 | GCS Maven Central mirror
52 | https://maven-central.storage-download.googleapis.com/maven2/
53 |
54 | true
55 |
56 |
57 | false
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/.github/maven-ci-settings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | false
20 |
21 |
22 |
23 | google-mirror
24 |
25 | true
26 |
27 |
28 |
29 | google-maven-central
30 | GCS Maven Central mirror EU
31 | https://maven-central.storage-download.googleapis.com/maven2/
32 |
33 | true
34 |
35 |
36 | false
37 |
38 |
39 |
40 |
41 |
42 | google-maven-central
43 | GCS Maven Central mirror
44 | https://maven-central.storage-download.googleapis.com/maven2/
45 |
46 | true
47 |
48 |
49 | false
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/.github/workflows/ci-4.x.yml:
--------------------------------------------------------------------------------
1 | name: vertx-zookeeper (4.x)
2 | on:
3 | schedule:
4 | - cron: '0 4 * * *'
5 | jobs:
6 | CI:
7 | strategy:
8 | matrix:
9 | include:
10 | - os: ubuntu-latest
11 | jdk: 8
12 | uses: ./.github/workflows/ci.yml
13 | with:
14 | branch: 4.x
15 | jdk: ${{ matrix.jdk }}
16 | os: ${{ matrix.os }}
17 | secrets: inherit
18 | Deploy:
19 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }}
20 | needs: CI
21 | uses: ./.github/workflows/deploy.yml
22 | with:
23 | branch: 4.x
24 | jdk: 8
25 | secrets: inherit
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci-5.x-stable.yml:
--------------------------------------------------------------------------------
1 | name: vertx-zookeeper (5.x-stable)
2 | on:
3 | push:
4 | branches:
5 | - '5.[0-9]+'
6 | pull_request:
7 | branches:
8 | - '5.[0-9]+'
9 | schedule:
10 | - cron: '0 6 * * *'
11 | jobs:
12 | CI-CD:
13 | uses: ./.github/workflows/ci-matrix-5.x.yml
14 | secrets: inherit
15 | with:
16 | branch: ${{ github.event_name == 'schedule' && vars.VERTX_5_STABLE_BRANCH || github.event.pull_request.head.sha || github.ref_name }}
17 |
--------------------------------------------------------------------------------
/.github/workflows/ci-5.x.yml:
--------------------------------------------------------------------------------
1 | name: vertx-zookeeper (5.x)
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 | branches:
8 | - master
9 | schedule:
10 | - cron: '0 5 * * *'
11 | jobs:
12 | CI-CD:
13 | uses: ./.github/workflows/ci-matrix-5.x.yml
14 | secrets: inherit
15 | with:
16 | branch: ${{ github.event.pull_request.head.sha || github.ref_name }}
17 |
--------------------------------------------------------------------------------
/.github/workflows/ci-matrix-5.x.yml:
--------------------------------------------------------------------------------
1 | name: CI matrix (5.x)
2 | on:
3 | workflow_call:
4 | inputs:
5 | branch:
6 | required: true
7 | type: string
8 | jobs:
9 | CI:
10 | strategy:
11 | matrix:
12 | include:
13 | - os: ubuntu-latest
14 | jdk: 11
15 | uses: ./.github/workflows/ci.yml
16 | with:
17 | branch: ${{ inputs.branch }}
18 | jdk: ${{ matrix.jdk }}
19 | os: ${{ matrix.os }}
20 | secrets: inherit
21 | Deploy:
22 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }}
23 | needs: CI
24 | uses: ./.github/workflows/deploy.yml
25 | with:
26 | branch: ${{ inputs.branch }}
27 | jdk: 11
28 | secrets: inherit
29 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | workflow_call:
4 | inputs:
5 | branch:
6 | required: true
7 | type: string
8 | jdk:
9 | default: 8
10 | type: string
11 | os:
12 | default: ubuntu-latest
13 | type: string
14 | jobs:
15 | Test:
16 | name: Run tests
17 | runs-on: ${{ inputs.os }}
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v2
21 | with:
22 | ref: ${{ inputs.branch }}
23 | - name: Install JDK
24 | uses: actions/setup-java@v2
25 | with:
26 | java-version: ${{ inputs.jdk }}
27 | distribution: temurin
28 | - name: Run tests
29 | run: mvn -s .github/maven-ci-settings.xml -q clean verify -B
30 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 | on:
3 | workflow_call:
4 | inputs:
5 | branch:
6 | required: true
7 | type: string
8 | jdk:
9 | default: 8
10 | type: string
11 | jobs:
12 | Deploy:
13 | name: Deploy to OSSRH
14 | runs-on: ubuntu-latest
15 | env:
16 | VERTX_NEXUS_USERNAME: ${{ secrets.VERTX_NEXUS_USERNAME }}
17 | VERTX_NEXUS_PASSWORD: ${{ secrets.VERTX_NEXUS_PASSWORD }}
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v2
21 | with:
22 | ref: ${{ inputs.branch }}
23 | - name: Install JDK
24 | uses: actions/setup-java@v2
25 | with:
26 | java-version: ${{ inputs.jdk }}
27 | distribution: temurin
28 | - name: Get project version
29 | run: echo "PROJECT_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout | grep -v '\[')" >> $GITHUB_ENV
30 | - name: Maven deploy
31 | if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }}
32 | run: mvn deploy -s .github/maven-cd-settings.xml -DskipTests -B
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .gradle
3 | .idea
4 | .vertx
5 | .classpath
6 | .project
7 | .settings
8 | .yardoc
9 | .yardopts
10 | build
11 | target
12 | out
13 | *.iml
14 | *.ipr
15 | *.iws
16 | test-output
17 | Scratch.java
18 | ScratchTest.java
19 | test-results
20 | test-tmp
21 | *.class
22 |
--------------------------------------------------------------------------------
/.travis.deploy.artifacts.sh:
--------------------------------------------------------------------------------
1 | PROJECT_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -B | grep -v '\[')
2 | if [[ "$PROJECT_VERSION" =~ .*SNAPSHOT ]] && [[ "${TRAVIS_BRANCH}" =~ ^master$|^[0-9]+\.[0-9]+$ ]] && [[ "${TRAVIS_PULL_REQUEST}" = "false" ]];
3 | then
4 | mvn deploy -s .travis.maven.settings.xml -DskipTests -B;
5 | fi
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | install: true
3 | branches:
4 | only:
5 | - master
6 | - /^\d+\.\d+$/
7 | cache:
8 | directories:
9 | - $HOME/.m2
10 | before_cache:
11 | - rm -rf $HOME/.m2/repository/io/vertx/
12 | jobs:
13 | include:
14 | - stage: test
15 | name: "Unit tests - OracleJDK 8"
16 | script: mvn -B -DtestLogLevel=OFF test
17 | jdk: oraclejdk8
18 | - name: "Unit tests - OpenJDK 11"
19 | if: type != pull_request
20 | script: mvn -B -DtestLogLevel=OFF test
21 | jdk: openjdk11
22 | - stage: deploy
23 | name: "Deploy to Sonatype's snapshots repository"
24 | jdk: openjdk8
25 | if: type != pull_request
26 | script: bash .travis.deploy.artifacts.sh
27 | notifications:
28 | email:
29 | recipients:
30 | - stream1984@me.com
31 | on_success: always
32 | on_failure: always
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2013 The original author or authors
3 | * ------------------------------------------------------
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | = Zookeeper Cluster Manager
2 |
3 | image:https://github.com/vert-x3/vertx-zookeeper/workflows/CI/badge.svg?branch=master["Build Status", link="https://github.com/vert-x3/vertx-zookeeper/actions?query=workflow%3ACI"]
4 |
5 | This is a cluster manager implementation for Vert.x that uses http://zookeeper.apache.org/[Zookeeper].
6 |
7 | Please see the main documentation on the web-site for a full description:
8 |
9 | * https://vertx.io/docs/vertx-zookeeper/java/[Web-site documentation]
10 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | io.vertx
7 | vertx5-parent
8 | 12
9 |
10 |
11 | vertx-zookeeper
12 | 5.1.0-SNAPSHOT
13 | Vert.x Zookeeper Cluster Manager
14 |
15 |
16 | 5.4.0
17 | 3.7.2
18 | 4.13.1
19 | ${project.basedir}/src/main/asciidoc
20 |
21 |
22 |
23 |
24 |
25 | io.vertx
26 | vertx-dependencies
27 | ${project.version}
28 | pom
29 | import
30 |
31 |
32 |
33 |
34 |
35 | scm:git:git@github.com:vert-x3/vertx-zookeeper.git
36 | scm:git:git@github.com:vert-x3/vertx-zookeeper.git
37 | git@github.com:vert-x3/vertx-zookeeper.git
38 |
39 |
40 |
41 |
42 | io.vertx
43 | vertx-codegen-api
44 | true
45 |
46 |
47 | io.vertx
48 | vertx-codegen-json
49 | true
50 |
51 |
52 | io.vertx
53 | vertx-docgen-api
54 | true
55 |
56 |
57 | io.vertx
58 | vertx-core
59 | provided
60 |
61 |
62 | io.vertx
63 | vertx-web
64 | true
65 |
66 |
67 |
68 | org.apache.curator
69 | curator-framework
70 | ${curator.version}
71 |
72 |
73 |
74 |
75 |
76 |
77 | org.slf4j
78 | slf4j-api
79 |
80 |
81 | jline
82 | jline
83 |
84 |
85 | log4j
86 | log4j
87 |
88 |
89 |
90 | com.google.guava
91 | guava
92 |
93 |
94 |
95 |
96 | org.apache.zookeeper
97 | zookeeper
98 | ${zookeeper.version}
99 |
100 |
101 | io.netty
102 | netty-handler
103 |
104 |
105 | io.netty
106 | netty-tranasport-native-epoll
107 |
108 |
109 | org.slf4j
110 | slf4j-api
111 |
112 |
113 | org.slf4j
114 | slf4j-log4j12
115 |
116 |
117 | log4j
118 | log4j
119 |
120 |
121 | com.github.spotbugs
122 | spotbugs-annotations
123 |
124 |
125 |
126 |
127 | org.apache.curator
128 | curator-recipes
129 | ${curator.version}
130 |
131 |
132 |
133 |
134 | com.google.guava
135 | guava
136 |
137 |
138 |
139 |
140 | org.slf4j
141 | slf4j-api
142 | true
143 |
144 |
145 | org.apache.logging.log4j
146 | log4j-api
147 | true
148 |
149 |
150 | org.apache.logging.log4j
151 | log4j-core
152 | true
153 |
154 |
155 |
156 | junit
157 | junit
158 | ${junit.version}
159 | test
160 |
161 |
162 | org.apache.curator
163 | curator-test
164 | ${curator.version}
165 | test
166 |
167 |
168 | com.google.guava
169 | guava
170 |
171 |
172 | org.junit.jupiter
173 | junit-jupiter-api
174 |
175 |
176 |
177 |
178 | io.vertx
179 | vertx-core
180 | test-jar
181 | test
182 |
183 |
184 | io.vertx
185 | vertx-web
186 | test-jar
187 | test
188 |
189 |
190 | io.vertx
191 | vertx-service-discovery
192 | test
193 |
194 |
195 | io.vertx
196 | vertx-service-proxy
197 | test
198 |
199 |
200 | io.vertx
201 | vertx-service-discovery
202 | test-jar
203 | test
204 |
205 |
206 | org.assertj
207 | assertj-core
208 | 3.3.0
209 | test
210 |
211 |
212 | com.jayway.awaitility
213 | awaitility
214 | 1.7.0
215 | test
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | maven-compiler-plugin
224 |
225 |
226 | default-compile
227 |
228 |
229 |
230 | io.vertx
231 | vertx-codegen
232 | processor
233 |
234 |
235 | io.vertx
236 | vertx-docgen-processor
237 | processor
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 | org.apache.maven.plugins
246 | maven-surefire-plugin
247 |
248 | false
249 |
250 | PARANOID
251 | ${project.build.directory}
252 | ${project.version}
253 | true
254 |
255 |
256 | -Xmx1200M
257 | 1
258 | true
259 |
260 |
261 |
262 |
263 |
264 |
265 | maven-assembly-plugin
266 |
267 |
268 | package-docs
269 |
270 | single
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 | coverage
282 |
283 |
284 |
285 |
286 | org.apache.maven.plugins
287 | maven-surefire-plugin
288 |
289 | false
290 |
291 | PARANOID
292 | ${project.build.directory}
293 | ${project.version}
294 | true
295 |
296 |
297 |
298 | -Xmx1200M
299 | 1
300 | true
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
--------------------------------------------------------------------------------
/src/main/asciidoc/index.adoc:
--------------------------------------------------------------------------------
1 | = Zookeeper Cluster Manager
2 |
3 | This is a cluster manager implementation for Vert.x that uses http://zookeeper.apache.org/[Zookeeper].
4 |
5 | It implements interfaces of vert.x cluster totally. So you can using it to instead of vertx-hazelcast if you want.
6 | This implementation is packaged inside:
7 |
8 | [source,xml,subs="+attributes"]
9 | ----
10 |
11 | io.vertx
12 | vertx-zookeeper
13 | ${maven.version}
14 |
15 | ----
16 |
17 | In Vert.x a cluster manager is used for various functions including:
18 |
19 | * Discovery and group membership of Vert.x nodes in a cluster
20 | * Maintaining cluster wide topic subscriber lists (so we know which nodes are interested in which event busaddresses)
21 | * Distributed Map support
22 | * Distributed Locks
23 | * Distributed Counters
24 |
25 | Cluster managersdo not* handle the event bus inter-node transport, this is done directly by Vert.x with TCP connections.
26 |
27 | == How to work
28 | We are using http://curator.apache.org/[Apache Curator] framework rather than zookeeper client directly, so
29 | we have a dependency for libraries used in Curator such as `guava`, `slf4j` and of course `zookeeper`.
30 |
31 | Since ZK using tree dictionary to store data, we can take root path as namespace default root path is `io.vertx` which in default-zookeeper.json.
32 | and there are another 5 sub path to record other information for functions in vert.x cluster manager, all you can change the path is `root path`.
33 |
34 | you can find all the vert.x node information in path of `/io.vertx/cluster/nodes/`,
35 | `/io.vertx/asyncMap/$name/` record all the `AsyncMap` you created with `io.vertx.core.shareddata.AsyncMap` interface.
36 | `/io.vertx/asyncMultiMap/$name/` record all the `AsyncMultiMap` you created with `io.vertx.core.spi.cluster.AsyncMultiMap` interface.
37 | `/io.vertx/locks/` record distributed Locks information.
38 | `/io.vertx/counters/` record distributed Count information.
39 |
40 | == Using this cluster manager
41 |
42 | If you are using Vert.x from the command line, the jar corresponding to this cluster manager (it will be named `vertx-zookeeper-${maven.version}`.jar`
43 | should be in the `lib` directory of the Vert.x installation.
44 |
45 | If you want clustering with this cluster manager in your Vert.x Maven or Gradle project then just add a dependency to
46 | the artifact: `io.vertx:vertx-zookeeper:${version}` in your project.
47 |
48 | If the jar is on your classpath as above then Vert.x will automatically detect this and use it as the cluster manager.
49 | Please make sure you don't have any other cluster managers on your classpath or Vert.x might
50 | choose the wrong one.
51 |
52 | You can also specify the cluster manager programmatically if you are embedding Vert.x by specifying it on the options
53 | when you are creating your Vert.x instance, for example:
54 |
55 | [source, $lang]
56 | ----
57 | {@link example.Examples#example1()}
58 | ----
59 |
60 | == Configuring this cluster manager
61 |
62 | Usually the cluster manager is configured by a file
63 | https://github.com/vert-x3/vertx-zookeeper/blob/master/src/main/resources/default-zookeeper.json[`default-zookeeper.json`]
64 | which is packaged inside the jar.
65 |
66 | If you want to override this configuration you can provide a file called `zookeeper.json` on your classpath and this
67 | will be used instead. If you want to embed the `zookeeper.json` file in a fat jar, it must be located at the root of the
68 | fat jar. If it's an external file, the*directory** containing the file must be added to the classpath. For
69 | example, if you are using the _launcher_ class from Vert.x, the classpath enhancement can be done as follows:
70 |
71 | [source,shell]
72 | ----
73 | # If the zookeeper.json is in the current directory:
74 | java -jar ... -cp . -cluster
75 | vertx run MyVerticle -cp . -cluster
76 |
77 | # If the zookeeper.json is in the conf directory
78 | java -jar ... -cp conf -cluster
79 | ----
80 |
81 | Another way to override the configuration is by providing the system property `vertx.zookeeper.conf` with a
82 | location:
83 |
84 | [source,shell]
85 | ----
86 | # Use a cluster configuration located in an external file
87 | java -Dvertx.zookeeper.config=./config/my-zookeeper-conf.json -jar ... -cluster
88 |
89 | # Or use a custom configuration from the classpath
90 | java -Dvertx.zookeeper.config=classpath:my/package/config/my-cluster-config.json -jar ... -cluster
91 | ----
92 |
93 | The `vertx.zookeeper.config` system property, when present, overrides any `zookeeper.json` from the classpath, but if
94 | loading
95 | from this system property fails, then loading falls back to either `zookeeper.json` or the Zookeeper default configuration.
96 |
97 | The configuration file is described in detail in `default-zookeeper.json`'s comment.
98 |
99 | You can also specify configuration programmatically if embedding:
100 |
101 | [source,java]
102 | ----
103 | {@link example.Examples#example2()}
104 | ----
105 |
106 | The retry policy can be specified in the json configuration as following:
107 |
108 | [source,json]
109 | ----
110 | {
111 | "retry":{
112 | "policy": "exponential_backoff"
113 | }
114 | }
115 | ----
116 |
117 | The possible value for the policy are:
118 |
119 | * `exponential_backoff` (default)
120 | * `bounded_exponential_backoff`
121 | * `one_time`
122 | * `n_times`
123 | * `forever`
124 | * `until_elapsed`
125 |
126 |
127 | IMPORTANT: You can also configure the zookeeper hosts using the `vertx.zookeeper.hosts` system property.
128 |
129 | === Enabling logging
130 |
131 | When trouble-shooting clustering issues with Zookeeper it's often useful to get some logging output from Zookeeper
132 | to see if it's forming a cluster properly. You can do this (when using the default JUL logging) by adding a file
133 | called `vertx-default-jul-logging.properties` on your classpath. This is a standard java.util.logging (JUL)
134 | configuration file. Inside it set:
135 |
136 | [source,properties]
137 | ----
138 | org.apache.zookeeper.level=INFO
139 | ----
140 |
141 | and also
142 |
143 | [source,properties]
144 | ----
145 | java.util.logging.ConsoleHandler.level=INFO
146 | java.util.logging.FileHandler.level=INFO
147 | ----
148 |
--------------------------------------------------------------------------------
/src/main/java/example/Examples.java:
--------------------------------------------------------------------------------
1 | package example;
2 |
3 | import io.vertx.core.Vertx;
4 | import io.vertx.core.VertxOptions;
5 | import io.vertx.core.json.JsonObject;
6 | import io.vertx.core.spi.cluster.ClusterManager;
7 | import io.vertx.spi.cluster.zookeeper.ZookeeperClusterManager;
8 | import org.apache.curator.framework.CuratorFramework;
9 |
10 | /**
11 | * Created by stream.
12 | */
13 | public class Examples {
14 |
15 | public void example1() {
16 | ClusterManager mgr = new ZookeeperClusterManager();
17 | Vertx.builder()
18 | .withClusterManager(mgr)
19 | .buildClustered().onComplete(res -> {
20 | if (res.succeeded()) {
21 | Vertx vertx = res.result();
22 | } else {
23 | // failed!
24 | }
25 | });
26 | }
27 |
28 | public void example2() {
29 | JsonObject zkConfig = new JsonObject();
30 | zkConfig.put("zookeeperHosts", "127.0.0.1");
31 | zkConfig.put("rootPath", "io.vertx");
32 | zkConfig.put("retry", new JsonObject()
33 | .put("initialSleepTime", 3000)
34 | .put("maxTimes", 3));
35 |
36 |
37 | ClusterManager mgr = new ZookeeperClusterManager(zkConfig);
38 |
39 | Vertx.builder()
40 | .withClusterManager(mgr)
41 | .buildClustered().onComplete(res -> {
42 | if (res.succeeded()) {
43 | Vertx vertx = res.result();
44 | } else {
45 | // failed!
46 | }
47 | });
48 | }
49 |
50 | public void example3(CuratorFramework curator) {
51 | ClusterManager mgr = new ZookeeperClusterManager(curator);
52 | Vertx.builder().withClusterManager(mgr).buildClustered().onComplete(res -> {
53 | if (res.succeeded()) {
54 | Vertx vertx = res.result();
55 | } else {
56 | // failed!
57 | }
58 | });
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/example/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2013 The original author or authors
3 | * ------------------------------------------------------
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | @Source
17 | package example;
18 |
19 | import io.vertx.docgen.Source;
--------------------------------------------------------------------------------
/src/main/java/io/vertx/spi/cluster/zookeeper/ZookeeperClusterManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2020 The original author or authors
3 | * ------------------------------------------------------
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 |
17 | package io.vertx.spi.cluster.zookeeper;
18 |
19 | import io.vertx.core.*;
20 | import io.vertx.core.buffer.Buffer;
21 | import io.vertx.core.internal.VertxInternal;
22 | import io.vertx.core.internal.logging.Logger;
23 | import io.vertx.core.internal.logging.LoggerFactory;
24 | import io.vertx.core.json.JsonObject;
25 | import io.vertx.core.shareddata.AsyncMap;
26 | import io.vertx.core.shareddata.Counter;
27 | import io.vertx.core.shareddata.Lock;
28 | import io.vertx.core.spi.cluster.*;
29 | import io.vertx.spi.cluster.zookeeper.impl.*;
30 | import org.apache.curator.RetryPolicy;
31 | import org.apache.curator.framework.CuratorFramework;
32 | import org.apache.curator.framework.CuratorFrameworkFactory;
33 | import org.apache.curator.framework.api.CuratorEventType;
34 | import org.apache.curator.framework.imps.CuratorFrameworkState;
35 | import org.apache.curator.framework.recipes.cache.PathChildrenCache;
36 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
37 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
38 | import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
39 | import org.apache.zookeeper.CreateMode;
40 | import org.apache.zookeeper.KeeperException;
41 |
42 | import java.util.ArrayList;
43 | import java.util.List;
44 | import java.util.Map;
45 | import java.util.Objects;
46 | import java.util.Optional;
47 | import java.util.UUID;
48 | import java.util.concurrent.ConcurrentHashMap;
49 | import java.util.concurrent.ExecutorService;
50 | import java.util.concurrent.Executors;
51 | import java.util.concurrent.TimeUnit;
52 | import java.util.function.Function;
53 | import java.util.stream.Collectors;
54 |
55 | /**
56 | * A cluster manager that uses Zookeeper
57 | *
58 | * @author Stream.Liu
59 | */
60 | public class ZookeeperClusterManager implements ClusterManager, PathChildrenCacheListener {
61 |
62 | private static final Logger log = LoggerFactory.getLogger(ZookeeperClusterManager.class);
63 |
64 | private VertxInternal vertx;
65 |
66 | private NodeListener nodeListener;
67 | private RegistrationListener registrationListener;
68 | private PathChildrenCache clusterNodes;
69 | private volatile boolean active;
70 | private volatile boolean joined;
71 |
72 | private String nodeId;
73 | private NodeInfo nodeInfo;
74 | private CuratorFramework curator;
75 | private boolean customCuratorCluster;
76 | private RetryPolicy retryPolicy;
77 | private SubsMapHelper subsMapHelper;
78 | private final Map localNodeInfo = new ConcurrentHashMap<>();
79 | private final Map locks = new ConcurrentHashMap<>();
80 | private final Map> asyncMapCache = new ConcurrentHashMap<>();
81 |
82 | private JsonObject conf = new JsonObject();
83 |
84 | private static final String ZK_PATH_LOCKS = "/locks/";
85 | private static final String ZK_PATH_CLUSTER_NODE = "/cluster/nodes/";
86 | private static final String ZK_PATH_CLUSTER_NODE_WITHOUT_SLASH = "/cluster/nodes";
87 |
88 | private ExecutorService lockReleaseExec;
89 |
90 | private Function resolveNodeId = path -> {
91 | String[] pathArr = path.split("\\/");
92 | return pathArr[pathArr.length - 1];
93 | };
94 |
95 | public ZookeeperClusterManager() {
96 | conf = ConfigUtil.loadConfig(null);
97 | }
98 |
99 | public ZookeeperClusterManager(CuratorFramework curator) {
100 | this(curator, UUID.randomUUID().toString());
101 | }
102 |
103 | public ZookeeperClusterManager(String resourceLocation) {
104 | conf = ConfigUtil.loadConfig(resourceLocation);
105 | }
106 |
107 | public ZookeeperClusterManager(CuratorFramework curator, String nodeId) {
108 | Objects.requireNonNull(curator, "The Curator instance cannot be null.");
109 | Objects.requireNonNull(nodeId, "The nodeId cannot be null.");
110 | this.curator = curator;
111 | this.nodeId = nodeId;
112 | this.customCuratorCluster = true;
113 | }
114 |
115 | public ZookeeperClusterManager(JsonObject config) {
116 | this.conf = config;
117 | }
118 |
119 | public void setConfig(JsonObject conf) {
120 | this.conf = conf;
121 | }
122 |
123 | public JsonObject getConfig() {
124 | return conf;
125 | }
126 |
127 | public CuratorFramework getCuratorFramework() {
128 | return this.curator;
129 | }
130 |
131 | @Override
132 | public void init(Vertx vertx) {
133 | this.vertx = (VertxInternal) vertx;
134 | }
135 |
136 | @Override
137 | public void getAsyncMap(String name, Completable> promise) {
138 | vertx.>executeBlocking(() -> {
139 | @SuppressWarnings("unchecked")
140 | AsyncMap zkAsyncMap = (AsyncMap) asyncMapCache.computeIfAbsent(name, key -> new ZKAsyncMap<>(vertx, curator, name));
141 | return zkAsyncMap;
142 | }).onComplete(promise);
143 | }
144 |
145 | @Override
146 | public Map getSyncMap(String name) {
147 | return new ZKSyncMap<>(curator, name);
148 | }
149 |
150 | @Override
151 | public void getLockWithTimeout(String name, long timeout, Completable promise) {
152 | vertx.executeBlocking(() -> {
153 | ZKLock lock = locks.get(name);
154 | if (lock == null) {
155 | InterProcessSemaphoreMutex mutexLock = new InterProcessSemaphoreMutex(curator, ZK_PATH_LOCKS + name);
156 | lock = new ZKLock(mutexLock, lockReleaseExec);
157 | }
158 | try {
159 | if (lock.getLock().acquire(timeout, TimeUnit.MILLISECONDS)) {
160 | locks.putIfAbsent(name, lock);
161 | return lock;
162 | } else {
163 | throw new VertxException("Timed out waiting to get lock " + name);
164 | }
165 | } catch (Exception e) {
166 | throw new VertxException("get lock exception", e);
167 | }
168 | }, false).onComplete(promise);
169 | }
170 |
171 | @Override
172 | public void getCounter(String name, Completable promise) {
173 | vertx.executeBlocking(() -> {
174 | try {
175 | Objects.requireNonNull(name);
176 | return new ZKCounter(vertx, curator, name, retryPolicy);
177 | } catch (Exception e) {
178 | throw new VertxException(e, true);
179 | }
180 | }).onComplete(promise);
181 | }
182 |
183 | @Override
184 | public String getNodeId() {
185 | return nodeId;
186 | }
187 |
188 | @Override
189 | public List getNodes() {
190 | return clusterNodes.getCurrentData().stream()
191 | .map(childData -> resolveNodeId.apply(childData.getPath()))
192 | .collect(Collectors.toList());
193 | }
194 |
195 | @Override
196 | public void registrationListener(RegistrationListener registrationListener) {
197 | this.registrationListener = registrationListener;
198 | }
199 |
200 | @Override
201 | public void nodeListener(NodeListener listener) {
202 | this.nodeListener = listener;
203 | }
204 |
205 | @Override
206 | public void setNodeInfo(NodeInfo nodeInfo, Completable promise) {
207 | synchronized (this) {
208 | this.nodeInfo = nodeInfo;
209 | }
210 | try {
211 | Buffer buffer = Buffer.buffer();
212 | nodeInfo.writeToBuffer(buffer);
213 | curator.create().orSetData().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground((c, e) -> {
214 | if (e.getType() == CuratorEventType.SET_DATA || e.getType() == CuratorEventType.CREATE) {
215 | vertx.runOnContext(Avoid -> {
216 | localNodeInfo.put(nodeId, nodeInfo);
217 | promise.succeed();
218 | });
219 | }
220 | }).withUnhandledErrorListener(log::error).forPath(ZK_PATH_CLUSTER_NODE + nodeId, buffer.getBytes());
221 | } catch (Exception e) {
222 | log.error("create node failed.", e);
223 | }
224 | }
225 |
226 | @Override
227 | public synchronized NodeInfo getNodeInfo() {
228 | return nodeInfo;
229 | }
230 |
231 | @Override
232 | public void getNodeInfo(String nodeId, Completable promise) {
233 | vertx.executeBlocking(() -> {
234 | return Optional.ofNullable(clusterNodes.getCurrentData(ZK_PATH_CLUSTER_NODE + nodeId))
235 | .map(childData -> {
236 | Buffer buffer = Buffer.buffer(childData.getData());
237 | NodeInfo nodeInfo = new NodeInfo();
238 | nodeInfo.readFromBuffer(0, buffer);
239 | return nodeInfo;
240 | }).orElseThrow(() -> new VertxException("Not a member of the cluster", true));
241 | }, false).onComplete(promise);
242 | }
243 |
244 | private void addLocalNodeId() throws VertxException {
245 | clusterNodes = new PathChildrenCache(curator, ZK_PATH_CLUSTER_NODE_WITHOUT_SLASH, true);
246 | clusterNodes.getListenable().addListener(this);
247 | try {
248 | clusterNodes.start(PathChildrenCache.StartMode.NORMAL);
249 | //Join to the cluster
250 | createThisNode();
251 | joined = true;
252 | subsMapHelper = new SubsMapHelper(curator, vertx, registrationListener, nodeId);
253 | } catch (Exception e) {
254 | throw new VertxException(e);
255 | }
256 | }
257 |
258 | private void createThisNode() throws Exception {
259 | try {
260 | curator.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(ZK_PATH_CLUSTER_NODE + nodeId, nodeId.getBytes());
261 | } catch (KeeperException.NodeExistsException e) {
262 | //idempotent
263 | log.info("node:" + nodeId + " have created successful.");
264 | }
265 | }
266 |
267 | @Override
268 | public void join(Completable promise) {
269 | vertx.executeBlocking(() -> {
270 | if (!active) {
271 | active = true;
272 | lockReleaseExec = Executors.newCachedThreadPool(r -> new Thread(r, "vertx-zookeeper-service-release-lock-thread"));
273 |
274 | //The curator instance has been passed using the constructor.
275 | if (customCuratorCluster) {
276 | addLocalNodeId();
277 | return null;
278 | }
279 |
280 | if (curator == null) {
281 | retryPolicy = RetryPolicyHelper.createRetryPolicy(conf.getJsonObject("retry", new JsonObject()));
282 |
283 | // Read the zookeeper hosts from a system variable
284 | String hosts = System.getProperty("vertx.zookeeper.hosts");
285 | if (hosts == null) {
286 | hosts = conf.getString("zookeeperHosts", "127.0.0.1");
287 | }
288 | log.info("Zookeeper hosts set to " + hosts);
289 |
290 | curator = CuratorFrameworkFactory.builder()
291 | .connectString(hosts)
292 | .namespace(conf.getString("rootPath", "io.vertx"))
293 | .sessionTimeoutMs(conf.getInteger("sessionTimeout", 20000))
294 | .connectionTimeoutMs(conf.getInteger("connectTimeout", 3000))
295 | .retryPolicy(retryPolicy).build();
296 | }
297 | curator.start();
298 | while (curator.getState() != CuratorFrameworkState.STARTED) {
299 | try {
300 | Thread.sleep(100);
301 | } catch (InterruptedException e) {
302 | if (curator.getState() != CuratorFrameworkState.STARTED) {
303 | throw new VertxException("zookeeper client being interrupted while starting.", true);
304 | }
305 | }
306 | }
307 | nodeId = UUID.randomUUID().toString();
308 | addLocalNodeId();
309 | return null;
310 | } else {
311 | return null;
312 | }
313 | }).onComplete(promise);
314 | }
315 |
316 | @Override
317 | public void leave(Completable promise) {
318 | vertx.executeBlocking(() -> {
319 | synchronized (ZookeeperClusterManager.this) {
320 | if (active) {
321 | active = false;
322 | joined = false;
323 | lockReleaseExec.shutdown();
324 | try {
325 | clusterNodes.close();
326 | subsMapHelper.close();
327 | curator.close();
328 | } catch (Exception e) {
329 | log.warn("zookeeper close exception.", e);
330 | }
331 | }
332 | return null;
333 | }
334 | }).onComplete(promise);
335 | }
336 |
337 | @Override
338 | public boolean isActive() {
339 | return active;
340 | }
341 |
342 | @Override
343 | public void addRegistration(String address, RegistrationInfo registrationInfo, Completable promise) {
344 | System.out.println("ADD REGISTRATION " + address + " " + registrationInfo);
345 | subsMapHelper.put(address, registrationInfo, (res, err) -> {
346 | System.out.println("ADD REG COMPLETE " + err);
347 | promise.complete(null, err);
348 | });
349 | }
350 |
351 | @Override
352 | public void removeRegistration(String address, RegistrationInfo registrationInfo, Completable promise) {
353 | subsMapHelper.remove(address, registrationInfo, promise);
354 | }
355 |
356 | @Override
357 | public void getRegistrations(String address, Completable> promise) {
358 | vertx.executeBlocking(() -> subsMapHelper.get(address), false).onComplete(ar -> {
359 | System.out.println("GET REG " + ar.result());
360 | promise.complete(ar.result(), ar.cause());
361 | });
362 | }
363 |
364 | @Override
365 | public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
366 | if (!active) return;
367 | switch (event.getType()) {
368 | case CHILD_ADDED:
369 | try {
370 | if (nodeListener != null && client.getState() != CuratorFrameworkState.STOPPED) {
371 | nodeListener.nodeAdded(resolveNodeId.apply(event.getData().getPath()));
372 | }
373 | } catch (Throwable t) {
374 | log.error("Failed to handle memberAdded", t);
375 | }
376 | break;
377 | case CHILD_REMOVED:
378 | try {
379 | if (nodeListener != null && client.getState() != CuratorFrameworkState.STOPPED) {
380 | nodeListener.nodeLeft(resolveNodeId.apply(event.getData().getPath()));
381 | }
382 | } catch (Throwable t) {
383 | log.warn("Failed to handle memberRemoved", t);
384 | }
385 | break;
386 | case CHILD_UPDATED:
387 | //log.warn("Weird event that update cluster node. path:" + event.getData().getPath());
388 | break;
389 | case CONNECTION_RECONNECTED:
390 | if (joined) {
391 | createThisNode();
392 | List> futures = new ArrayList<>();
393 | for(Map.Entry entry : localNodeInfo.entrySet()) {
394 | Promise promise = Promise.promise();
395 | setNodeInfo(entry.getValue(), promise);
396 | futures.add(promise.future());
397 | }
398 | Future.all(futures).onComplete(ar -> {
399 | if (ar.failed()) {
400 | log.error("recover node info failed.", ar.cause());
401 | }
402 | });
403 | }
404 | break;
405 | case CONNECTION_SUSPENDED:
406 | //just release locks on this node.
407 | locks.values().forEach(ZKLock::release);
408 | break;
409 | case CONNECTION_LOST:
410 | //release locks and clean locks
411 | joined = false;
412 | locks.values().forEach(ZKLock::release);
413 | locks.clear();
414 | break;
415 | }
416 | }
417 | }
418 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/spi/cluster/zookeeper/impl/ConfigUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2023 The original author or authors
3 | * ------------------------------------------------------
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 |
17 | package io.vertx.spi.cluster.zookeeper.impl;
18 |
19 | import io.vertx.core.internal.logging.Logger;
20 | import io.vertx.core.internal.logging.LoggerFactory;
21 | import io.vertx.core.json.JsonObject;
22 |
23 | import java.io.BufferedInputStream;
24 | import java.io.BufferedReader;
25 | import java.io.File;
26 | import java.io.FileInputStream;
27 | import java.io.FileNotFoundException;
28 | import java.io.IOException;
29 | import java.io.InputStream;
30 | import java.io.InputStreamReader;
31 |
32 | public class ConfigUtil {
33 |
34 | private static final Logger log = LoggerFactory.getLogger(ConfigUtil.class);
35 |
36 | private static final String DEFAULT_CONFIG_FILE = "default-zookeeper.json";
37 | private static final String CONFIG_FILE = "zookeeper.json";
38 | private static final String ZK_SYS_CONFIG_KEY = "vertx.zookeeper.config";
39 |
40 | public static JsonObject loadConfig(String resourceLocation) {
41 | JsonObject conf = null;
42 | try (
43 | InputStream is = getConfigStream(resourceLocation != null ? resourceLocation : System.getProperty(ZK_SYS_CONFIG_KEY));
44 | BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is)))
45 | ) {
46 | String line;
47 | StringBuilder sb = new StringBuilder();
48 | while ((line = reader.readLine()) != null) {
49 | sb.append(line);
50 | }
51 | conf = new JsonObject(sb.toString());
52 | } catch (IOException ex) {
53 | log.error("Failed to read config", ex);
54 | }
55 | return conf;
56 | }
57 |
58 | private static InputStream getConfigStream(String resourceLocation) {
59 | InputStream is = getConfigStreamFor(resourceLocation);
60 | if (is == null) {
61 | is = getConfigStreamFromClasspath(CONFIG_FILE, DEFAULT_CONFIG_FILE);
62 | }
63 | return is;
64 | }
65 |
66 | private static InputStream getConfigStreamFor(String resourceLocation) {
67 | InputStream is = null;
68 | if (resourceLocation != null) {
69 | if (resourceLocation.startsWith("classpath:")) {
70 | return getConfigStreamFromClasspath(resourceLocation.substring("classpath:".length()), CONFIG_FILE);
71 | }
72 | File cfgFile = new File(resourceLocation);
73 | if (cfgFile.exists()) {
74 | try {
75 | is = new FileInputStream(cfgFile);
76 | } catch (FileNotFoundException ex) {
77 | log.warn(String.format("Failed to open file '%s' defined in '%s'. Continuing classpath search for %s", resourceLocation, ZK_SYS_CONFIG_KEY, CONFIG_FILE));
78 | }
79 | }
80 | }
81 | return is;
82 | }
83 |
84 | private static InputStream getConfigStreamFromClasspath(String configFile, String defaultConfig) {
85 | InputStream is = null;
86 | ClassLoader ctxClsLoader = Thread.currentThread().getContextClassLoader();
87 | if (ctxClsLoader != null) {
88 | is = ctxClsLoader.getResourceAsStream(configFile);
89 | }
90 | if (is == null) {
91 | is = ConfigUtil.class.getClassLoader().getResourceAsStream(configFile);
92 | if (is == null) {
93 | is = ConfigUtil.class.getClassLoader().getResourceAsStream(defaultConfig);
94 | }
95 | }
96 | return is;
97 | }
98 |
99 | private ConfigUtil() {
100 | // Utility class
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/spi/cluster/zookeeper/impl/RetryPolicyHelper.java:
--------------------------------------------------------------------------------
1 | package io.vertx.spi.cluster.zookeeper.impl;
2 |
3 | import io.vertx.core.internal.logging.Logger;
4 | import io.vertx.core.internal.logging.LoggerFactory;
5 | import io.vertx.core.json.JsonObject;
6 | import org.apache.curator.RetryPolicy;
7 | import org.apache.curator.retry.*;
8 |
9 | public class RetryPolicyHelper {
10 | private static final String DEFAULT_RETRY_POLICY = "exponential_backoff";
11 | private static final Logger log = LoggerFactory.getLogger(RetryPolicyHelper.class);
12 |
13 |
14 | /**
15 | * creates a {@link RetryPolicy} based on a JsonObject configuration.
16 | * It falls back to {@link ExponentialBackoffRetry} if no valid policy is specified.
17 | *
18 | * @param conf the configuration object
19 | * @return RetryPolicy instance
20 | */
21 | public static RetryPolicy createRetryPolicy(JsonObject conf){
22 | String policy = conf.getString("policy", DEFAULT_RETRY_POLICY);
23 | int initialSleepTime = conf.getInteger("initialSleepTime", 1000);
24 | int maxTimes = conf.getInteger("maxTimes", 5);
25 | int intervalTimes = conf.getInteger("intervalTimes",10000);
26 |
27 | switch (policy){
28 | case "bounded_exponential_backoff": return new BoundedExponentialBackoffRetry(initialSleepTime,maxTimes,intervalTimes);
29 | case "one_time": return new RetryOneTime(intervalTimes);
30 | case "n_times": return new RetryNTimes(maxTimes, intervalTimes);
31 | case "forever": return new RetryForever(intervalTimes);
32 | case "until_elapsed": return new RetryUntilElapsed(maxTimes,intervalTimes);
33 | case DEFAULT_RETRY_POLICY: return new ExponentialBackoffRetry(initialSleepTime, maxTimes, intervalTimes);
34 | default: {
35 | log.warn(String.format("%s is not a valid policy, falling back to %s",policy, DEFAULT_RETRY_POLICY));
36 | return new ExponentialBackoffRetry(initialSleepTime, maxTimes, intervalTimes);
37 | }
38 | }
39 | }
40 |
41 | private RetryPolicyHelper() {
42 | //Utility class
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/spi/cluster/zookeeper/impl/SubsMapHelper.java:
--------------------------------------------------------------------------------
1 | package io.vertx.spi.cluster.zookeeper.impl;
2 |
3 | import io.vertx.core.Completable;
4 | import io.vertx.core.Future;
5 | import io.vertx.core.Promise;
6 | import io.vertx.core.VertxException;
7 | import io.vertx.core.buffer.Buffer;
8 | import io.vertx.core.internal.VertxInternal;
9 | import io.vertx.core.internal.logging.Logger;
10 | import io.vertx.core.internal.logging.LoggerFactory;
11 | import io.vertx.core.spi.cluster.RegistrationInfo;
12 | import io.vertx.core.spi.cluster.RegistrationListener;
13 | import io.vertx.core.spi.cluster.RegistrationUpdateEvent;
14 | import org.apache.curator.framework.CuratorFramework;
15 | import org.apache.curator.framework.api.CuratorEventType;
16 | import org.apache.curator.framework.recipes.cache.ChildData;
17 | import org.apache.curator.framework.recipes.cache.TreeCache;
18 | import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
19 | import org.apache.curator.framework.recipes.cache.TreeCacheListener;
20 | import org.apache.zookeeper.CreateMode;
21 |
22 | import java.util.*;
23 | import java.util.concurrent.ConcurrentHashMap;
24 | import java.util.concurrent.ConcurrentMap;
25 | import java.util.function.BiFunction;
26 | import java.util.function.Function;
27 |
28 | public class SubsMapHelper implements TreeCacheListener {
29 |
30 | private final CuratorFramework curator;
31 | private final TreeCache treeCache;
32 | private final VertxInternal vertx;
33 | private final RegistrationListener registrationListener;
34 | private final String nodeId;
35 | private final ConcurrentMap> ownSubs = new ConcurrentHashMap<>();
36 | private final ConcurrentMap> localSubs = new ConcurrentHashMap<>();
37 |
38 | private static final String VERTX_SUBS_NAME = "/__vertx.subs";
39 | private static final Logger log = LoggerFactory.getLogger(SubsMapHelper.class);
40 |
41 | private static final Function keyPath = address -> VERTX_SUBS_NAME + "/" + address;
42 | private static final Function valuePath = registrationInfo -> registrationInfo.nodeId() + "-" + registrationInfo.seq();
43 | private static final BiFunction fullPath = (address, registrationInfo) -> keyPath.apply(address) + "/" + valuePath.apply(registrationInfo);
44 |
45 | public SubsMapHelper(CuratorFramework curator, VertxInternal vertx, RegistrationListener registrationListener, String nodeId) {
46 | this.curator = curator;
47 | this.vertx = vertx;
48 | this.treeCache = new TreeCache(curator, VERTX_SUBS_NAME);
49 | this.treeCache.getListenable().addListener(this);
50 | try {
51 | this.treeCache.start();
52 | } catch (Exception e) {
53 | throw new VertxException(e);
54 | }
55 | this.registrationListener = registrationListener;
56 | this.nodeId = nodeId;
57 | }
58 |
59 | public void close() {
60 | treeCache.close();
61 | }
62 |
63 | public void put(String address, RegistrationInfo registrationInfo, Completable promise) {
64 | if (registrationInfo.localOnly()) {
65 | localSubs.compute(address, (add, curr) -> addToSet(registrationInfo, curr));
66 | fireRegistrationUpdateEvent(address);
67 | promise.succeed();
68 | } else {
69 | try {
70 | Buffer buffer = Buffer.buffer();
71 | registrationInfo.writeToBuffer(buffer);
72 | curator.create().orSetData().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground((c, e) -> {
73 | if (e.getType() == CuratorEventType.CREATE || e.getType() == CuratorEventType.SET_DATA) {
74 | vertx.runOnContext(Avoid -> {
75 | ownSubs.compute(address, (add, curr) -> addToSet(registrationInfo, curr));
76 | promise.succeed();
77 | });
78 | }
79 | }).withUnhandledErrorListener(log::error).forPath(fullPath.apply(address, registrationInfo), buffer.getBytes());
80 | } catch (Exception e) {
81 | log.error(String.format("create subs address %s failed.", address), e);
82 | }
83 | }
84 | }
85 |
86 | private Set addToSet(RegistrationInfo registrationInfo, Set curr) {
87 | Set res = curr != null ? curr : Collections.synchronizedSet(new LinkedHashSet<>());
88 | res.add(registrationInfo);
89 | return res;
90 | }
91 |
92 | public List get(String address) {
93 | Map map = treeCache.getCurrentChildren(keyPath.apply(address));
94 | Collection remote = (map == null) ? Collections.emptyList() : map.values();
95 |
96 | List list;
97 | int size;
98 | size = remote.size();
99 | Set local = localSubs.get(address);
100 | if (local != null) {
101 | synchronized (local) {
102 | size += local.size();
103 | if (size == 0) {
104 | return Collections.emptyList();
105 | }
106 | list = new ArrayList<>(size);
107 | list.addAll(local);
108 | }
109 | } else if (size == 0) {
110 | return Collections.emptyList();
111 | } else {
112 | list = new ArrayList<>(size);
113 | }
114 | for (ChildData childData : remote) {
115 | list.add(toRegistrationInfo(childData));
116 | }
117 | return list;
118 | }
119 |
120 | private static RegistrationInfo toRegistrationInfo(ChildData childData) {
121 | RegistrationInfo registrationInfo = new RegistrationInfo();
122 | Buffer buffer = Buffer.buffer(childData.getData());
123 | registrationInfo.readFromBuffer(0, buffer);
124 | return registrationInfo;
125 | }
126 |
127 | public void remove(String address, RegistrationInfo registrationInfo, Completable promise) {
128 | try {
129 | if (registrationInfo.localOnly()) {
130 | localSubs.computeIfPresent(address, (add, curr) -> removeFromSet(registrationInfo, curr));
131 | fireRegistrationUpdateEvent(address);
132 | promise.succeed();
133 | } else {
134 | curator.delete().guaranteed().inBackground((c, e) -> {
135 | if (e.getType() == CuratorEventType.DELETE) {
136 | vertx.runOnContext(aVoid -> {
137 | ownSubs.computeIfPresent(address, (add, curr) -> removeFromSet(registrationInfo, curr));
138 | promise.succeed();
139 | });
140 | }
141 | }).forPath(fullPath.apply(address, registrationInfo));
142 | }
143 | } catch (Exception e) {
144 | log.error(String.format("remove subs address %s failed.", address), e);
145 | promise.fail(e);
146 | }
147 | }
148 |
149 | private Set removeFromSet(RegistrationInfo registrationInfo, Set curr) {
150 | curr.remove(registrationInfo);
151 | return curr.isEmpty() ? null : curr;
152 | }
153 |
154 | @Override
155 | public void childEvent(CuratorFramework client, TreeCacheEvent event) {
156 | switch (event.getType()) {
157 | case NODE_ADDED:
158 | case NODE_UPDATED:
159 | case NODE_REMOVED:
160 | // /__vertx.subs/AddressName/NodeId -> AddressName
161 | String[] pathElements = event.getData().getPath().split("/", 4);
162 | if (pathElements.length <= 3) {
163 | // "/__vertx.subs" and "/__vertx.subs/XX" added event
164 | break;
165 | }
166 | String addr = pathElements[2];
167 | vertx.>executeBlocking(() -> get(addr), false).onComplete(ar -> {
168 | if (ar.succeeded()) {
169 | registrationListener.registrationsUpdated(new RegistrationUpdateEvent(addr, ar.result()));
170 | } else {
171 | log.trace("A failure occured while retrieving the updated registrations", ar.cause());
172 | registrationListener.registrationsUpdated(new RegistrationUpdateEvent(addr, Collections.emptyList()));
173 | }
174 | });
175 | break;
176 | case CONNECTION_SUSPENDED:
177 | log.warn(String.format("vertx node %s which connected to zookeeper have been suspended.", nodeId));
178 | break;
179 | case CONNECTION_LOST:
180 | log.warn(String.format("vertx node %s which connected to zookeeper has lost", nodeId));
181 | break;
182 | case CONNECTION_RECONNECTED:
183 | log.info(String.format("vertx node %s have reconnected to zookeeper", nodeId));
184 | vertx.runOnContext(aVoid -> {
185 | List> futures = new ArrayList<>();
186 | for (Map.Entry> entry : ownSubs.entrySet()) {
187 | for (RegistrationInfo registrationInfo : entry.getValue()) {
188 | Promise promise = Promise.promise();
189 | put(entry.getKey(), registrationInfo, promise);
190 | futures.add(promise.future());
191 | }
192 | }
193 | Future.all(futures).onComplete(ar -> {
194 | if (ar.failed()) {
195 | log.error("recover node subs information failed.", ar.cause());
196 | } else {
197 | log.info("recover node subs success.");
198 | }
199 | });
200 | });
201 | break;
202 | }
203 | }
204 |
205 | private void fireRegistrationUpdateEvent(String address) {
206 | registrationListener.registrationsUpdated(new RegistrationUpdateEvent(address, get(address)));
207 | }
208 |
209 | }
210 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/spi/cluster/zookeeper/impl/ZKAsyncMap.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2016 The original author or authors
3 | * ------------------------------------------------------
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.spi.cluster.zookeeper.impl;
17 |
18 | import io.vertx.core.Future;
19 | import io.vertx.core.Promise;
20 | import io.vertx.core.Vertx;
21 | import io.vertx.core.internal.VertxInternal;
22 | import io.vertx.core.shareddata.AsyncMap;
23 | import org.apache.curator.framework.CuratorFramework;
24 | import org.apache.curator.framework.api.CuratorEventType;
25 | import org.apache.zookeeper.data.Stat;
26 |
27 | import java.io.IOException;
28 | import java.time.Instant;
29 | import java.util.*;
30 |
31 | /**
32 | * Created by Stream.Liu
33 | */
34 | public class ZKAsyncMap extends ZKMap implements AsyncMap {
35 |
36 | public ZKAsyncMap(Vertx vertx, CuratorFramework curator, String mapName) {
37 | super(curator, vertx, ZK_PATH_ASYNC_MAP, mapName);
38 | }
39 |
40 | @Override
41 | public Future get(K k) {
42 | return assertKeyIsNotNull(k)
43 | .compose(aVoid -> checkExists(k))
44 | .compose(checkResult -> {
45 | Promise promise = Promise.promise();
46 | if (checkResult) {
47 | try {
48 | curator.getData().inBackground((c,e) -> {
49 | if (e.getType() == CuratorEventType.GET_DATA) {
50 | V value = asObject(e.getData());
51 | vertx.runOnContext(aVoid -> promise.complete(value));
52 | }
53 | }).forPath(keyPath(k));
54 | } catch (Exception e) {
55 | promise.fail(e);
56 | }
57 | } else {
58 | //ignore
59 | promise.complete();
60 | }
61 | return promise.future();
62 | });
63 | }
64 |
65 | @Override
66 | public Future put(K k, V v) {
67 | return put(k, v, Optional.empty());
68 | }
69 |
70 | @Override
71 | public Future put(K k, V v, long ttl) {
72 | return put(k, v, Optional.of(ttl));
73 | }
74 |
75 | private Future put(K k, V v, Optional timeoutOptional) {
76 | return assertKeyAndValueAreNotNull(k, v)
77 | .compose(aVoid -> checkExists(k))
78 | .compose(checkResult -> checkResult ? setData(k, v) : create(k, v, timeoutOptional))
79 | .compose(stat -> Future.succeededFuture());
80 | }
81 |
82 | @Override
83 | public Future putIfAbsent(K k, V v) {
84 | return putIfAbsent(k, v, Optional.empty());
85 | }
86 |
87 | @Override
88 | public Future putIfAbsent(K k, V v, long ttl) {
89 | return putIfAbsent(k, v, Optional.of(ttl));
90 | }
91 |
92 | private Future putIfAbsent(K k, V v, Optional timeoutOptional) {
93 | return assertKeyAndValueAreNotNull(k, v)
94 | .compose(aVoid -> get(k))
95 | .compose(value -> {
96 | if (value == null) {
97 | if (timeoutOptional.isPresent()) {
98 | return put(k, v, timeoutOptional).compose(aVoid -> Future.succeededFuture(null));
99 | } else {
100 | return put(k, v).compose(aVoid -> Future.succeededFuture(null));
101 | }
102 | } else {
103 | return Future.succeededFuture(value);
104 | }
105 | });
106 | }
107 |
108 | @Override
109 | public Future remove(K k) {
110 | return assertKeyIsNotNull(k).compose(aVoid -> {
111 | Promise promise = Promise.promise();
112 | get(k).onComplete(promise);
113 | return promise.future();
114 | }).compose(value -> {
115 | Promise promise = Promise.promise();
116 | if (value != null) {
117 | return delete(k, value);
118 | } else {
119 | promise.complete();
120 | }
121 | return promise.future();
122 | });
123 | }
124 |
125 | @Override
126 | public Future removeIfPresent(K k, V v) {
127 | return assertKeyAndValueAreNotNull(k, v)
128 | .compose(aVoid -> {
129 | Promise promise = Promise.promise();
130 | get(k).onComplete(promise);
131 | return promise.future();
132 | })
133 | .compose(value -> {
134 | Promise promise = Promise.promise();
135 | if (Objects.equals(value, v)) {
136 | delete(k, v).onComplete(deleteResult -> {
137 | if (deleteResult.succeeded()) promise.complete(true);
138 | else promise.fail(deleteResult.cause());
139 | });
140 | } else {
141 | promise.complete(false);
142 | }
143 | return promise.future();
144 | });
145 | }
146 |
147 | @Override
148 | public Future replace(K k, V v) {
149 | return assertKeyAndValueAreNotNull(k, v)
150 | .compose(aVoid -> {
151 | Promise innerPromise = Promise.promise();
152 | vertx.executeBlocking(() -> {
153 | long startTime = Instant.now().toEpochMilli();
154 | int retries = 0;
155 |
156 | for (; ; ) {
157 | Stat stat = new Stat();
158 | String path = keyPath(k);
159 | V currentValue = getData(stat, path);
160 | //do not replace value if previous value is null
161 | if (currentValue == null) {
162 | return null;
163 | }
164 | if (compareAndSet(startTime, retries++, stat, path, currentValue, v)) {
165 | return currentValue;
166 | }
167 | }
168 | }, false).onComplete(innerPromise);
169 | return innerPromise.future();
170 | });
171 | }
172 |
173 | @Override
174 | public Future replaceIfPresent(K k, V oldValue, V newValue) {
175 | return assertKeyIsNotNull(k)
176 | .compose(aVoid -> assertValueIsNotNull(oldValue))
177 | .compose(aVoid -> assertValueIsNotNull(newValue))
178 | .compose(aVoid -> {
179 | Promise innerPromise = Promise.promise();
180 | vertx.executeBlocking(() -> {
181 | long startTime = Instant.now().toEpochMilli();
182 | int retries = 0;
183 |
184 | for (; ; ) {
185 | Stat stat = new Stat();
186 | String path = keyPath(k);
187 | V currentValue = getData(stat, path);
188 | if (!currentValue.equals(oldValue)) {
189 | return false;
190 | }
191 | if (compareAndSet(startTime, retries++, stat, path, oldValue, newValue)) {
192 | return true;
193 | }
194 | }
195 | }, false).onComplete(innerPromise);
196 | return innerPromise.future();
197 | });
198 | }
199 |
200 | @Override
201 | public Future clear() {
202 | //just remove parent node
203 | return delete(mapPath, null).mapEmpty();
204 | }
205 |
206 | @Override
207 | public Future size() {
208 | Promise promise = ((VertxInternal)vertx).getOrCreateContext().promise();
209 | try {
210 | curator.getChildren().inBackground((client, event) ->
211 | promise.tryComplete(event.getChildren().size()))
212 | .forPath(mapPath);
213 | } catch (Exception e) {
214 | promise.tryFail(e);
215 | }
216 | return promise.future();
217 | }
218 |
219 | @Override
220 | public Future> keys() {
221 | Promise> promise = ((VertxInternal)vertx).getOrCreateContext().promise();
222 | try {
223 | curator.getChildren().inBackground((client, event) -> {
224 | Set keys = new HashSet<>();
225 | for (String base64Key : event.getChildren()) {
226 | byte[] binaryKey = Base64.getUrlDecoder().decode(base64Key);
227 | K key;
228 | try {
229 | key = asObject(binaryKey);
230 | } catch (Exception e) {
231 | promise.tryFail(e);
232 | return;
233 | }
234 | keys.add(key);
235 | }
236 | promise.tryComplete(keys);
237 | }).forPath(mapPath);
238 | } catch (Exception e) {
239 | promise.tryFail(e);
240 | }
241 | return promise.future();
242 | }
243 |
244 | @Override
245 | public Future> values() {
246 | Promise> keysPromise = ((VertxInternal)vertx).getOrCreateContext().promise();
247 | keys().onComplete(keysPromise);
248 | return keysPromise.future().compose(keys -> {
249 | List> futures = new ArrayList<>(keys.size());
250 | for (K k : keys) {
251 | Promise valuePromise = Promise.promise();
252 | get(k).onComplete(valuePromise);
253 | futures.add(valuePromise.future());
254 | }
255 | return Future.all(futures).map(compositeFuture -> {
256 | List values = new ArrayList<>(compositeFuture.size());
257 | for (int i = 0; i < compositeFuture.size(); i++) {
258 | values.add(compositeFuture.resultAt(i));
259 | }
260 | return values;
261 | });
262 | });
263 | }
264 |
265 | @Override
266 | public Future