Build a Simple Service Registry and Discovery with Spring Boot & Zookeeper

This guide walks through creating a lightweight service registration and discovery mechanism using Spring Boot 2.2.10, Zookeeper 3.6.2, and Java, covering environment setup, Maven dependencies, configuration files, core Java classes, and a test controller to verify dynamic service lists.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Build a Simple Service Registry and Discovery with Spring Boot & Zookeeper

Implementation steps:

When a service starts, it creates a temporary Zookeeper node containing basic information such as IP, port, and service name; the node is automatically removed when the service stops.

A watch is registered on the parent node to monitor child node changes: creation indicates a new service registration, deletion indicates a service going offline.

Next we implement a simple service registration and discovery using Spring Boot 2.2.10.RELEASE and Zookeeper 3.6.2. Versions of Zookeeper below 3.6.0 require re‑adding watches after each event, while 3.6.0 and above handle this automatically.

pom.xml dependencies:

<properties>
  <java.version>1.8</java.version>
</properties>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.2</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.12</version>
  </dependency>
</dependencies>

application.yml configuration:

server:
  port: 9881
---
spring:
  application:
    name: SN
---
zk:
  connectString: localhost:2181,localhost:2182,localhost:2183
  timeout: 3000
  config:
    server: /config/servers # ZNode to watch
  client:
    ip: 10.100.109.102 # service IP
    port: ${server.port} # service port

Configuration class that creates a ZooKeeper client:

@Configuration
public class ZKConfig {
    private static Logger logger = LoggerFactory.getLogger(ZKConfig.class);

    @Value("${zk.connectString}")
    private String connectString;
    @Value("${zk.timeout}")
    private Integer timeout;

    @Bean
    public ZooKeeper zookeeper() {
        ZooKeeper zookeeper = null;
        try {
            CountDownLatch cdl = new CountDownLatch(1);
            logger.info("zk准备连接: {}", connectString);
            zookeeper = new ZooKeeper(connectString, timeout, event -> {
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    logger.info("zk服务器连接成功");
                    cdl.countDown();
                }
            });
            cdl.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return zookeeper;
    }
}

Data class representing a service node:

@Data
public class ServerNode implements Serializable {
    private static final long serialVersionUID = 1L;
    private String ip;          // address
    private Integer port;       // port
    private String serviceName; // service name
    private LocalDateTime createTime; // creation time

    public ServerNode() {}

    public ServerNode(String ip, Integer port, String serviceName, LocalDateTime createTime) {
        this.ip = ip;
        this.port = port;
        this.serviceName = serviceName;
        this.createTime = createTime;
    }
}

Service registry interface:

public interface ServiceRegistry {
    Collection<ServerNode> getServers();
}

Implementation that registers the service, watches for changes, and maintains an in‑memory map of active nodes:

@Configuration
public class ZkServerRegister implements ServiceRegistry, InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(ZkServerRegister.class);

    @Resource
    private ZooKeeper zk;
    @Value("${zk.config.server}")
    private String path;
    @Value("${zk.client.ip}")
    private String ip;
    @Value("${zk.client.port}")
    private Integer port;
    @Value("${spring.application.name}")
    private String serviceName;

    private static Map<String, ServerNode> services = new HashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        if (zk.exists(path, null) == null) {
            zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        // recursive watch for child node changes
        zk.addWatch(path, new ServerNodeWatcher(zk), AddWatchMode.PERSISTENT_RECURSIVE);
        registerServer();
        getServer();
    }

    private void getServer() {
        try {
            List<String> childs = zk.getChildren(path, false);
            for (String node : childs) {
                try {
                    byte[] data = zk.getData(path + "/" + node, false, null);
                    services.put(path + "/" + node,
                        JSONObject.parseObject(new String(data, "UTF-8"), ServerNode.class));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            logger.info("当前服务列表:{}", services);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void registerServer() {
        String data = JSONObject.toJSONString(new ServerNode(ip, port, serviceName, LocalDateTime.now()));
        try {
            zk.create(path + "/node_" + System.nanoTime(), data.getBytes("UTF-8"),
                Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class ServerNodeWatcher implements Watcher {
        private ZooKeeper zk;
        public ServerNodeWatcher(ZooKeeper zk) { this.zk = zk; }
        @Override
        public void process(WatchedEvent event) {
            try {
                String p = event.getPath();
                switch (event.getType()) {
                    case NodeCreated:
                        logger.info("服务连接:{}", p);
                        byte[] datas = zk.getData(p, false, null);
                        services.put(p, JSONObject.parseObject(new String(datas, "UTF-8"), ServerNode.class));
                        logger.info("当前服务列表:{}", services);
                        break;
                    case NodeDeleted:
                        logger.info("服务断开:{}", p);
                        services.remove(p);
                        logger.info("当前服务列表:{}", services);
                        break;
                    default:
                        break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Collection<ServerNode> getServers() {
        return services.values();
    }
}

Test controller to expose the current service list:

@RestController
@RequestMapping("/test")
public class TestController {
    @Resource
    private ServiceRegistry sr;

    @GetMapping("/get")
    public Object get() {
        return sr.getServers();
    }
}

Run two instances of the application on ports 9881 and 9882. Access /test/get to see both services listed. After stopping the instance on 9882 and refreshing, the list updates to show only the remaining service, demonstrating dynamic registration and discovery.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMicroservicesBackend Developmentservice discoveryZooKeeperSpring Boot
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.