Skip to content

How to Set Up ElasticJob with ZooKeeper for Distributed Job Scheduling

Purpose

When I needed distributed job scheduling for my Java application, I chose ElasticJob. But setting it up with ZooKeeper wasn’t straightforward. This post shows the minimal configuration needed to get ElasticJob running with ZooKeeper.

Environment

  • Java 17+
  • ElasticJob 3.0.5
  • ZooKeeper 3.8.x
  • Maven

The Problem

I wanted to run scheduled jobs across multiple servers. When one server goes down, another should pick up the work. Simple enough, right?

I started by reading ElasticJob documentation and got confused:

Questions I Had
What exactly does ZooKeeper do?
Do I really need it?
How do I configure it?

The documentation explains WHAT ElasticJob does but not clearly HOW to set up the coordination layer.

What ZooKeeper Actually Does

Before diving into code, I needed to understand WHY ZooKeeper is required:

ZooKeeper Architecture
+------------------+ +------------------+
| Server A | | Server B |
| +------------+ | | +------------+ |
| | ElasticJob | | | | ElasticJob | |
| +-----+------+ | | +-----+------+ |
| | | | | |
+--------+---------+ +--------+---------+
| |
| +------------------+
| |
v v
+--------------+
| ZooKeeper | <-- Coordination
| - Leader election
| - Shard assignment
| - Failover detection
| - Job configuration
+--------------+

ZooKeeper handles:

  1. Leader election: Which node triggers the job
  2. Shard assignment: Which node handles which data partition
  3. Failover: Detecting node failures and reassigning work
  4. Configuration storage: Job definitions and runtime state

Step 1: Start ZooKeeper

First, I needed a running ZooKeeper instance. For local development, Docker is the easiest:

terminal
docker run --rm -d -p 127.0.0.1:2181:2181 --name elasticjob-zookeeper zookeeper

Verify it’s running:

terminal
docker ps | grep zookeeper

Output should show the container running.

Step 2: Add ElasticJob Dependency

I added the ElasticJob dependency to my Maven project:

pom.xml
<dependency>
<groupId>org.apache.shardingsphere.elasticjob</groupId>
<artifactId>elasticjob-bootstrap</artifactId>
<version>3.0.5</version>
</dependency>

Step 3: Initialize Registry Center

Now comes the critical part - configuring the ZookeeperRegistryCenter:

RegistryCenterConfig.java
import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
import org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperConfiguration;
import org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
public class RegistryCenterConfig {
public static CoordinatorRegistryCenter createRegistryCenter() {
ZookeeperConfiguration config =
new ZookeeperConfiguration("localhost:2181", "my-elasticjob-app");
// Optional: Set session timeout (default is 60000ms)
config.setSessionTimeoutMilliseconds(60000);
// Optional: Set connection timeout (default is 15000ms)
config.setConnectionTimeoutMilliseconds(15000);
CoordinatorRegistryCenter registryCenter =
new ZookeeperRegistryCenter(config);
registryCenter.init(); // <-- Don't forget this!
return registryCenter;
}
}

The key parameters are:

  • Server list: localhost:2181 - where ZooKeeper is running
  • Namespace: my-elasticjob-app - isolates this app’s data in ZooKeeper

Why Namespace Matters

I made a mistake when I first set this up. I used the same namespace for development and production:

Wrong - Same Namespace
// Dev environment
new ZookeeperConfiguration("dev-zk:2181", "my-app")
// Production environment
new ZookeeperConfiguration("prod-zk:2181", "my-app")

This seemed fine until I realized both environments were sharing the same ZooKeeper cluster. The jobs from dev were interfering with production!

The fix is simple:

Correct - Different Namespaces
// Dev environment
new ZookeeperConfiguration("zk-cluster:2181", "my-app-dev")
// Production environment
new ZookeeperConfiguration("zk-cluster:2181", "my-app-prod")

Step 4: Create a Simple Job

Now I need to define an actual job:

MySimpleJob.java
import org.apache.shardingsphere.elasticjob.api.ShardingContext;
import org.apache.shardingsphere.elasticjob.simple.job.SimpleJob;
public class MySimpleJob implements SimpleJob {
@Override
public void execute(ShardingContext context) {
// context.getShardingItem() tells you which shard this is
// context.getShardingTotalCount() tells you total shards
System.out.println("Job executing on shard: " + context.getShardingItem());
// Your job logic here
doWork();
}
private void doWork() {
// Business logic
}
}

Step 5: Schedule the Job

Finally, schedule the job with ElasticJob:

JobScheduler.java
import org.apache.shardingsphere.elasticjob.api.JobConfiguration;
import org.apache.shardingsphere.elasticjob.lite.api.bootstrap.impl.ScheduleJobBootstrap;
public class JobScheduler {
public static void main(String[] args) {
// 1. Create registry center
CoordinatorRegistryCenter registryCenter =
RegistryCenterConfig.createRegistryCenter();
// 2. Define job configuration
JobConfiguration jobConfig = JobConfiguration.newBuilder("MySimpleJob", 3) // 3 shards
.cron("0/10 * * * * ?") // Every 10 seconds
.shardingItemParameters("0=Beijing,1=Shanghai,2=Guangzhou")
.overwrite(true) // Overwrite existing config on each startup
.build();
// 3. Schedule the job
ScheduleJobBootstrap scheduleJobBootstrap =
new ScheduleJobBootstrap(registryCenter, new MySimpleJob(), jobConfig);
scheduleJobBootstrap.schedule();
System.out.println("Job scheduled successfully!");
}
}

Common Mistakes I Made

Mistake 1: Forgot to Start ZooKeeper

I got this error when ZooKeeper wasn’t running:

Error Output
org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss

Always verify ZooKeeper is running before starting your application:

terminal
# Check if ZooKeeper port is listening
nc -zv localhost 2181

Mistake 2: Forgot to Call init()

I created the ZookeeperRegistryCenter but forgot to call init():

Wrong - Missing init()
CoordinatorRegistryCenter registryCenter =
new ZookeeperRegistryCenter(
new ZookeeperConfiguration("localhost:2181", "my-app")
);
// registryCenter.init() is missing!

The error was cryptic:

Error Output
IllegalStateException: Registry center has not been initialized

Mistake 3: Wrong ZooKeeper Address

When deploying to a server, I hardcoded localhost:

Wrong - Hardcoded localhost
new ZookeeperConfiguration("localhost:2181", "my-app")

This fails when the app runs on a different server than ZooKeeper. Use environment variables:

Correct - Environment variable
String zkServers = System.getenv().getOrDefault("ZOOKEEPER_SERVERS", "localhost:2181");
new ZookeeperConfiguration(zkServers, "my-app")

How to Verify It’s Working

After starting the application, I check ZooKeeper to verify the job was registered:

terminal
# Connect to ZooKeeper CLI
docker exec -it elasticjob-zookeeper zkCli.sh
# List ElasticJob data
ls /my-elasticjob-app

You should see something like:

Output
[MySimpleJob]

The Complete Setup Diagram

Here’s the complete flow:

Complete Setup Flow
+-------------------+
| Your Application |
| |
| +-------------+ |
| | MySimpleJob | |
| +------+------+ |
| | |
| +------v------+ |
| | JobBootstrap| |
| +------+------+ |
| | |
+---------+---------+
|
| init() + schedule()
|
+---------v---------+
| ZooKeeper |
| /my-elasticjob-app/
| /MySimpleJob |
| /sharding |
| /instances |
| /servers |
+-------------------+

Summary

Setting up ElasticJob with ZooKeeper requires:

  1. Running ZooKeeper: Use Docker for local development
  2. Registry Center Configuration: Create ZookeeperRegistryCenter with server list and namespace
  3. Job Implementation: Implement SimpleJob interface
  4. Job Scheduling: Use ScheduleJobBootstrap to register and schedule

The key insight is that ZooKeeper provides battle-tested distributed coordination primitives that would be extremely complex to implement correctly from scratch.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments