This post is the second in the series of publications about using the Apache ZooKeeper for building configuration management solutions for a distributed system. It focuses on implementing a tool for loading initial configuration data into a fresh ZooKeeper ensemble.
In the previous publication,
Managing configuration of a distributed system with Apache ZooKeeper,
you can find assumptions about the possible structure of the configuration data in the distributed system, assessment of
using ZooKeeper ensemble as a centralized configuration storage and basic examples of managing configuration data of
the simple Scala-based HTTP service.
Using ZooKeeper command line utility
The most obvious way to import the initial configuration into the ZooKeeper ensemble is to use the bundled
command line utility. The CLI shell scripts are located in the /bin subdirectory of a ZooKeeper distribution, where the
zkCli.sh
script is designed for UNIX systems and the zkCli.cmd
script - for Windows systems.
A complete list of available shell commands you can find in the previous publication as well as the instructions on importing initial configuration entries into the fresh ZooKeeper ensemble using the command line utility.
Although the ZooKeeper CLI shell allows to execute file system like operations and looks pretty nice for the first acquaintance with the ZooKeeper or working with the simplest solutions, it is useless for more or less advanced use cases. For creating complex distributed applications ZooKeeper offers bindings in two languages: C and Java. Detailed information on client libraries are available in the "Bindings" section of the ZooKeeper Programmer's Guide.
Importing initial configuration data from a file
Consider creating the script for importing initial configuration into a ZooKeeper ensemble from the file. This solution will have advantages over the CLI shell, eliminating the need to manually create a ZNode for each configuration item and enter values in the console every time they should be imported. Thus, all required data can be just placed into the file (or group of files), structured and then imported by executing only one command.
I prefer Groovy language for implementing the import script, as it is great for writing concise and maintainable code, and, being the JVM-based language, allows using a giant Java codebase with the help of the embedded JAR dependency manager - Grape.
In general, the process of importing configuration data from the file can be divided into two steps:
- obtaining configuration data from the file;
- uploading configuration data into the ZooKeeper ensemble.
Let's go through them one-by-one.
Parsing the configuration from a file
I'm sure that Typesafe Config library is an excellent choice for the task of reading and parsing configuration data from the file. It has rich functionality and good documentation - it is a pleasure to use this library for working with the application configuration. Currently, it supports the following formats: HOCON (*.conf), json (*.json), properties (*.properties). This means that you are free to choose any of these three formats to store your initial configuration data. Examples of how the same data looks in different formats are shown in the code snippets below:
1 2 3 4 |
|
1 2 3 4 5 6 |
|
1 2 |
|
The following Grapes annotation adds Typesafe Config library to the script's classpath:
1
|
|
The initial configuration from the previous publication in the HOCON format might look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Finally, here is a code example for loading and parsing configuration file:
1 2 3 4 |
|
Uploading the configuration to a server
At the second step - uploading initial configuration data into the ZooKeeper ensemble - Apache Curator Framework is used. It provides the clean and simple high-level API and automates connection management, which simplifies using ZooKeeper.
In the example to the first publication Curator Framework was used to obtain specific configuration entries from the ZooKeeper server. Here, it will be responsible for creating ZNode structure and filling it with the initial configuration data.
Below is the client initialization code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
The Connect string parameter value contains comma-separated list of "host:port" pairs of the ZooKeeper servers in the ensemble.
The Retry policy parameter is responsible for setting behavior for recovering from connection errors. In the current
example, a BoundedExponentialBackoffRetry policy is used. This means that a client retries connection attempts
the specified number of times with an increasing (up to a maximum bound) sleep time between retries.
The Namespace parameter should be set if all ZNode paths are expected to be prefixed by the specific namespace.
Default values are set in the script code for all of the mentioned variables. Also, a user can override the "connectString" and the "namespace" variables via script parameters. This will be described in the next paragraph.
It is easy to start a client:
1
|
|
Also, please, do not forget to close the client when it is not needed anymore to avoid leaks of the system resources. In the example script, the call is placed into the finally block of the script's common error handling wrapper:
1 2 3 4 |
|
After the content of the configuration file is loaded and parsed, and the client is initialized, the script starts to
create ZNode structure and upload data entries. As you might notice in the previous paragraph, configuration entries
are being sorted by path (ascending). This way, ZNodes are being created in a natural order, starting from the root path
and then moving deeper through the config structure.
Pay additional attention on the fact that default separator of the path tokens in the Config object is a dot symbol, while ZooKeeper requires to use a slash symbol for this, that's why additional processing of the path tokens takes place here.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Running the script
The command line interface of the script is the following:
1 2 3 4 5 6 7 8 9 10 |
|
This output can be displayed when executing the script with -help option:
1
|
|
The call of the script against the single local ZooKeeper instance in the ensemble and the "application.conf" configuration file, located in the same directory as the script, looks like this:
1
|
|
The call of the script against the ensemble of three ZooKeeper instances and a custom path to the configuration file looks like this:
1 2 |
|
Sources
Complete code of the import script is available in the code snipped below or in the GitHub repo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
|
The source code of the whole example Scala project is available on GitHub.
Hope you find this helpful.