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.
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 portConfiguration 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
