Spring Boot 整合 ZooKeeper:服务发现、配置中心与分布式锁实战
1. ZooKeeper 是什么
ZooKeeper 是一个分布式协调服务,常用于:
- 服务注册与发现
- 分布式配置管理
- 分布式锁
- Leader 选举
- 集群节点状态监听
它的核心数据结构是 znode,可以理解成一棵类似文件系统的树。每个节点既可以存数据,也可以挂子节点。
常见节点类型:
| 类型 | 说明 | 典型用途 |
|---|---|---|
| 持久节点 | 客户端断开后仍然存在 | 配置、目录 |
| 临时节点 | 客户端会话结束后自动删除 | 服务实例注册 |
| 顺序节点 | 创建时自动追加递增序号 | 分布式锁、队列 |
| 临时顺序节点 | 会话结束自动删除,并带顺序号 | 分布式锁、Leader 选举 |
在 Spring Boot 里使用 ZooKeeper,常见有两种方式:
- 使用
Spring Cloud Zookeeper做服务发现和配置中心。 - 使用
Apache Curator操作节点、监听节点、实现分布式锁。
2. 环境准备
本文以 Spring Boot 3.5.x 和 Spring Cloud 2025.0.x 为例。
如果你使用 Spring Boot 4.0.x,应选择 Spring Cloud 2025.1.x。Spring Cloud 和 Spring Boot 必须匹配版本,不建议手动乱指定 starter 版本。
先用 Docker 启动一个本地 ZooKeeper:
# docker-compose.yml
services:
zookeeper:
image: zookeeper:3.9
container_name: zk-blog
ports:
- "2181:2181"
environment:
ZOO_4LW_COMMANDS_WHITELIST: "ruok,stat,mntr"
启动:
docker compose up -d
验证:
docker exec -it zk-blog zkCli.sh
进入 CLI 后执行:
ls /
能正常返回节点列表,说明 ZooKeeper 已经可用。
3. Maven 依赖
<properties>
<java.version>17</java.version>
<spring-cloud.version>2025.0.3</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Web 服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ZooKeeper 服务注册与发现 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<!-- ZooKeeper 配置中心,可选 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>
<!-- 客户端负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Curator 分布式锁、缓存监听等高级用法 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
</dependencies>
注意:如果使用了 Spring Cloud BOM,业务依赖一般不要再手写具体版本,交给 BOM 管理即可。
4. 服务注册
创建一个订单服务 order-service。
# application.yml
server:
port: 8081
spring:
application:
name: order-service
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
discovery:
enabled: true
register: true
root: /services
启动类:
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
写一个测试接口:
package com.example.order;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Value("${server.port}")
private int port;
@GetMapping("/api/orders/ping")
public Map<String, Object> ping() {
return Map.of(
"service", "order-service",
"port", port,
"message", "order service is ok"
);
}
}
启动后,进入 ZooKeeper CLI:
docker exec -it zk-blog zkCli.sh
查看服务注册信息:
ls /services
ls /services/order-service
能看到 order-service 以及实例节点,就说明服务注册成功。
5. 服务发现与调用
再创建一个消费者服务 user-service。
# application.yml
server:
port: 8082
spring:
application:
name: user-service
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
discovery:
enabled: true
register: true
配置一个支持负载均衡的 RestTemplate:
package com.example.user;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class HttpClientConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
调用 order-service:
package com.example.user;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController {
private final RestTemplate restTemplate;
public UserController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping("/api/users/call-order")
public String callOrder() {
return restTemplate.getForObject(
"http://order-service/api/orders/ping",
String.class
);
}
}
访问:
curl http://localhost:8082/api/users/call-order
这里的 http://order-service 不是 DNS 域名,而是服务名。Spring Cloud LoadBalancer 会通过 ZooKeeper 找到真实实例地址。
6. 使用 ZooKeeper 做配置中心
Spring Cloud Zookeeper Config 可以把 ZooKeeper 中的节点数据加载进 Spring Environment。
配置:
server:
port: 8081
spring:
application:
name: order-service
config:
import: optional:zookeeper:
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
config:
enabled: true
root: config
default-context: application
profile-separator: ","
默认配置路径类似:
| 路径 | 说明 |
|---|---|
/config/application | 所有应用共享配置 |
/config/application,dev | 所有应用在 dev 环境的共享配置 |
/config/order-service | order-service 专属配置 |
/config/order-service,dev | order-service 在 dev 环境的专属配置 |
写入配置:
docker exec -it zk-blog zkCli.sh
在 ZooKeeper CLI 中执行:
create /config ""
create /config/application ""
create /config/order-service ""
create /config/order-service/app.message "hello from zookeeper"
create /config/order-service/order.discount-enabled "true"
读取配置:
package com.example.order;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigController {
@Value("${app.message:default message}")
private String message;
@Value("${order.discount-enabled:false}")
private boolean discountEnabled;
@GetMapping("/api/config")
public String config() {
return "message=" + message + ", discountEnabled=" + discountEnabled;
}
}
访问:
curl http://localhost:8081/api/config
返回示例:
message=hello from zookeeper, discountEnabled=true
7. 使用 Curator 操作 ZooKeeper 节点
如果项目已经引入了 Spring Cloud Zookeeper,通常可以直接注入 CuratorFramework。
package com.example.order;
import java.nio.charset.StandardCharsets;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.stereotype.Service;
@Service
public class ZkNodeService {
private final CuratorFramework curatorFramework;
public ZkNodeService(CuratorFramework curatorFramework) {
this.curatorFramework = curatorFramework;
}
public void createOrUpdate(String path, String value) throws Exception {
byte[] data = value.getBytes(StandardCharsets.UTF_8);
if (curatorFramework.checkExists().forPath(path) == null) {
curatorFramework.create()
.creatingParentsIfNeeded()
.forPath(path, data);
return;
}
curatorFramework.setData().forPath(path, data);
}
public String get(String path) throws Exception {
byte[] data = curatorFramework.getData().forPath(path);
return new String(data, StandardCharsets.UTF_8);
}
public void delete(String path) throws Exception {
if (curatorFramework.checkExists().forPath(path) != null) {
curatorFramework.delete()
.deletingChildrenIfNeeded()
.forPath(path);
}
}
}
测试接口:
package com.example.order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ZkNodeController {
private final ZkNodeService zkNodeService;
public ZkNodeController(ZkNodeService zkNodeService) {
this.zkNodeService = zkNodeService;
}
@GetMapping("/api/zk/node")
public String node() throws Exception {
String path = "/demo/order/message";
zkNodeService.createOrUpdate(path, "created by spring boot");
return zkNodeService.get(path);
}
}
访问:
curl http://localhost:8081/api/zk/node
8. 使用 Curator 实现分布式锁
分布式锁是 ZooKeeper 最常见的应用之一。Curator 已经封装好了锁实现,推荐直接使用 InterProcessMutex,不要自己手写临时顺序节点逻辑。
package com.example.order;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.stereotype.Service;
@Service
public class OrderLockService {
private final CuratorFramework curatorFramework;
public OrderLockService(CuratorFramework curatorFramework) {
this.curatorFramework = curatorFramework;
}
public String createOrder(Long userId, Long productId) throws Exception {
String lockPath = "/locks/order-create/" + productId;
InterProcessMutex lock = new InterProcessMutex(curatorFramework, lockPath);
boolean acquired = lock.acquire(3, TimeUnit.SECONDS);
if (!acquired) {
return "系统繁忙,请稍后重试";
}
try {
return doCreateOrder(userId, productId);
} finally {
lock.release();
}
}
private String doCreateOrder(Long userId, Long productId) {
return "create order success, userId=" + userId + ", productId=" + productId;
}
}
接口:
package com.example.order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderLockController {
private final OrderLockService orderLockService;
public OrderLockController(OrderLockService orderLockService) {
this.orderLockService = orderLockService;
}
@GetMapping("/api/orders/create")
public String createOrder() throws Exception {
return orderLockService.createOrder(1001L, 2001L);
}
}
访问:
curl http://localhost:8081/api/orders/create
分布式锁使用建议:
- 锁路径要尽量细,例如按商品 ID、订单 ID、用户 ID 拆分,不要所有业务共用一个锁。
acquire一定要设置超时时间,避免线程无限等待。release必须放在finally中。- 锁内逻辑要尽量短,不要把长时间网络调用放进锁里。
- 分布式锁不能替代业务幂等,订单、支付、库存仍然要做唯一约束和幂等控制。
9. 监听节点变化
Curator 提供了缓存监听能力,可以监听某个路径下节点的新增、修改、删除。
package com.example.order;
import java.nio.charset.StandardCharsets;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Component
public class ZkNodeWatcher {
private final CuratorFramework curatorFramework;
private CuratorCache cache;
public ZkNodeWatcher(CuratorFramework curatorFramework) {
this.curatorFramework = curatorFramework;
}
@PostConstruct
public void start() {
String watchPath = "/demo/order";
this.cache = CuratorCache.build(curatorFramework, watchPath);
this.cache.listenable().addListener((type, oldData, data) -> {
if (data == null) {
System.out.println("node event: " + type);
return;
}
String path = data.getPath();
String value = data.getData() == null
? ""
: new String(data.getData(), StandardCharsets.UTF_8);
System.out.println("node event: " + type + ", path=" + path + ", value=" + value);
});
this.cache.start();
}
@PreDestroy
public void stop() {
if (this.cache != null) {
this.cache.close();
}
}
}
然后调用前面的 /api/zk/node 接口修改节点,就能在控制台看到监听日志。
10. 常见问题
10.1 启动时报连不上 ZooKeeper
检查:
docker ps
docker logs zk-blog
确认 application.yml 中连接地址正确:
spring:
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
如果只是本地开发,可以使用:
spring:
config:
import: optional:zookeeper:
optional: 表示 ZooKeeper 暂时不可用时不阻止应用启动。生产环境是否使用 optional 要看业务要求。
10.2 服务没有注册进去
检查:
spring:
cloud:
zookeeper:
discovery:
enabled: true
register: true
然后进入 CLI:
ls /services
如果没有服务节点,优先看应用启动日志和 ZooKeeper 连接日志。
10.3 配置读取不到
确认路径是否符合默认规则:
/config/application
/config/application,dev
/config/{spring.application.name}
/config/{spring.application.name},{profile}
例如应用名是:
spring:
application:
name: order-service
那么专属配置路径就是:
/config/order-service
10.4 ZooKeeper 可以当数据库用吗
不建议。
ZooKeeper 适合存储小体量的协调数据,例如服务实例、配置项、锁节点、Leader 状态。不要把大 JSON、大列表、业务主数据、订单数据放到 ZooKeeper 里。
11. 生产环境建议
- ZooKeeper 集群建议使用 3 个或 5 个节点,避免单点故障。
- 不要在 ZooKeeper 中存储大数据,只保存协调元信息。
- 对关键路径配置 ACL、网络访问控制和监控告警。
- 设置合理的连接超时、会话超时和重试策略。
- 分布式锁只保护临界区,业务仍然要有幂等、唯一索引和补偿机制。
- 服务发现适合内部服务治理,不要直接暴露 ZooKeeper 给公网。
- 配置中心场景要明确刷新策略,不要假设所有配置都会自动实时刷新到所有 Bean。
12. 总结
Spring Boot 使用 ZooKeeper 主要有三类场景:
| 场景 | 推荐方案 |
|---|---|
| 服务注册与发现 | spring-cloud-starter-zookeeper-discovery |
| 配置中心 | spring-cloud-starter-zookeeper-config |
| 分布式锁、节点监听 | Apache Curator |
如果只是做 Spring Cloud 微服务治理,优先使用 Spring Cloud Zookeeper。如果要实现锁、选主、节点监听等协调能力,优先使用 Curator 提供的成熟 recipe,不建议自己手写底层 ZooKeeper 算法。
参考资料
更多推荐



所有评论(0)