网站首页 > 文章精选 正文
你有没有遇到过这样的场景?在一个高并发的互联网大厂项目里,多个服务同时争抢数据库连接池资源,导致系统出现数据不一致、服务响应缓慢等问题,甚至偶尔还会出现系统崩溃的情况。身为后端开发人员的你,是否在为如何解决这类分布式场景下的资源同步问题而发愁?今天,就和大家聊聊在 Spring Boot3 中,Zookeeper 如何帮我们解决这些棘手难题!
随着互联网行业的迅猛发展,分布式系统架构在大厂中早已成为标配。在分布式系统里,各个服务节点相互协作,共同完成复杂的业务逻辑,但也因此产生了诸如数据一致性、服务协调、配置管理等一系列问题。Spring Boot3 作为当下流行的 Java 开发框架,能够快速构建微服务应用。而 Zookeeper,凭借其出色的分布式协调能力,成为了 Spring Boot3 开发过程中的得力助手。它就像是分布式系统中的 “指挥官”,帮助各个服务节点有序协作,保障系统稳定运行。
分布式锁场景
在分布式系统中,多个进程同时访问共享资源是常见的情况,比如在电商系统的大促活动中,成百上千的订单请求同时涌入,都要对库存这个共享资源进行修改。如果没有有效的控制手段,就会出现超卖的严重问题。这时候,Zookeeper 实现的分布式锁就能派上大用场。
Zookeeper 实现分布式锁主要基于其临时顺序节点的特性。当一个服务想要获取锁时,会在 Zookeeper 的特定路径下创建一个临时顺序节点。所有创建的节点按照创建时间顺序排列,序号最小的节点代表获取到了锁。当持有锁的服务完成业务操作后,对应的临时节点会自动删除,排在后面的节点检测到前一个节点删除,就会尝试获取锁。
在 Spring Boot3 项目中使用 Zookeeper 分布式锁也很方便。首先,引入相关依赖,比如 Curator 框架相关的依赖。在pom.xml文件中添加如下内容:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.3.0</version>
</dependency>
配置好 Zookeeper 连接地址,在application.properties文件中配置:
zookeeper.connectString=127.0.0.1:2181
然后,编写获取锁和释放锁的工具类。例如,通过 Curator 框架来操作 Zookeeper,利用它提供的InterProcessMutex类,就能轻松实现分布式锁的获取与释放。下面是一个简单的工具类示例:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class ZookeeperLockUtil {
private static final String LOCK_PATH = "/mylock";
private static CuratorFramework client;
private static InterProcessMutex mutex;
static {
client = CuratorFrameworkFactory.newClient(
"127.0.0.1:2181",
new ExponentialBackoffRetry(1000, 3)
);
client.start();
mutex = new InterProcessMutex(client, LOCK_PATH);
}
public static boolean tryLock(long timeout, TimeUnit unit) {
try {
return mutex.acquire(timeout, unit);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static void unlock() {
try {
mutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在实际应用中,比如在秒杀活动的下单逻辑中,通过分布式锁保证同一时间只有一个请求能进入库存扣减的操作,确保库存数据的准确性。示例代码如下:
@Service
public class OrderService {
@Autowired
private StockService stockService;
public void placeOrder(String orderInfo) {
if (ZookeeperLockUtil.tryLock(10, TimeUnit.SECONDS)) {
try {
boolean hasStock = stockService.checkStock();
if (hasStock) {
stockService.deductStock();
// 下单逻辑
System.out.println("订单下单成功,订单信息:" + orderInfo);
} else {
System.out.println("库存不足,下单失败");
}
} finally {
ZookeeperLockUtil.unlock();
}
} else {
System.out.println("获取锁失败,下单请求稍后重试");
}
}
}
配置中心场景
在大厂的大型项目中,往往会有多个环境,开发、测试、预发、生产等,每个环境的配置可能存在差异,而且随着业务的发展,配置也需要不断更新。如果采用传统的本地配置文件方式,不仅修改配置时需要逐个环境手动更新,还容易出现配置不一致的问题。
Zookeeper 作为配置中心,能很好地解决这些问题。在 Spring Boot3 中,我们可以通过配置文件设置从 Zookeeper 导入配置信息,并且启用 Watch 机制来监听 Zookeeper 上数据的变化。当 Zookeeper 中的配置发生修改时,Spring Boot3 应用程序能够自动感知到变化,并将新的配置加载到系统中。这里需要注意的是,对于使用到配置的 Bean,要加上@RefreshScope注解,这样才能保证配置更新后,相关 Bean 能够及时使用新的配置。
首先,引入 Spring Cloud Zookeeper Config 相关依赖,在pom.xml中添加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>
在bootstrap.properties文件中配置 Zookeeper 连接地址及相关信息:
spring.application.name=myapp
spring.cloud.zookeeper.connect-string=127.0.0.1:2181
spring.cloud.zookeeper.config.enabled=true
spring.cloud.zookeeper.config.root=/config
spring.cloud.zookeeper.config.source=application.properties
假设在 Zookeeper 中,
/config/application.properties节点下存储了如下配置信息:
server.port=8080
database.url=jdbc:mysql://localhost:3306/mydb
database.username=root
database.password=123456
在 Spring Boot 应用中,定义一个配置类来读取这些配置:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Value("${server.port}")
private String serverPort;
@Value("${database.url}")
private String databaseUrl;
@Value("${database.username}")
private String databaseUsername;
@Value("${database.password}")
private String databasePassword;
// 可以根据需要添加getter和setter方法
}
例如,在一个大型的广告投放系统中,广告投放策略、投放时间等配置信息可以存储在 Zookeeper 上。运营人员在后台修改投放策略后,相关的服务能够实时获取到新的配置,无需重启服务,大大提高了系统的灵活性和运维效率。假设广告投放策略配置如下:
ad.strategy=targeted
ad.target.ageGroup=18-35
ad.target.gender=female
ad.startTime=2025-01-01
ad.endTime=2025-12-31
当运营人员修改了ad.strategy为broadcast时,Zookeeper 触发 Watch 事件,Spring Boot 应用程序自动更新配置,相关的广告投放服务能够立即按照新的策略执行。
服务注册与发现场景
在分布式系统中,服务数量众多,服务之间的调用关系也错综复杂。服务注册与发现机制能够帮助服务消费者快速找到服务提供者,实现服务的动态管理。虽然现在有 Eureka、Nacos 等专门的服务注册与发现组件,但 Zookeeper 同样可以实现这一功能。
在 Zookeeper 中,服务提供者启动时,会在 Zookeeper 的特定路径下创建一个临时节点,节点中存储着服务的相关信息,如 IP 地址、端口号等。服务消费者启动时,会去监听服务提供者所在的路径,一旦有新的服务节点创建或者已有节点消失,服务消费者就能及时感知到变化,并更新本地的服务列表。
在 Spring Boot3 项目中集成 Zookeeper 实现服务注册与发现,需要编写相应的服务注册和发现逻辑。比如,在服务启动时,将服务信息注册到 Zookeeper;在进行服务调用时,从 Zookeeper 中获取可用的服务列表,并通过负载均衡算法选择一个合适的服务节点进行调用。
引入 Spring Cloud Zookeeper Discovery 相关依赖,在pom.xml中添加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
在application.properties中配置 Zookeeper 连接地址等信息:
spring.application.name=service-provider
spring.cloud.zookeeper.connect-string=127.0.0.1:2181
spring.cloud.zookeeper.discovery.register=true
编写一个服务注册的配置类:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.zookeeper.discovery.ZookeeperServiceDiscovery;
import org.springframework.integration.zookeeper.discovery.ZookeeperServiceDiscoveryProperties;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@RefreshScope
public class ZookeeperServiceRegistrationConfig {
@Value("${spring.application.name}")
private String serviceName;
@Value("${server.port}")
private int port;
@Bean
public ZookeeperServiceDiscoveryProperties zookeeperServiceDiscoveryProperties() {
ZookeeperServiceDiscoveryProperties properties = new ZookeeperServiceDiscoveryProperties();
properties.setRoot("/services");
return properties;
}
@Bean
public ZookeeperServiceDiscovery zookeeperServiceDiscovery(ZookeeperServiceDiscoveryProperties properties) {
return new ZookeeperServiceDiscovery(properties);
}
@Bean
public MessageChannel serviceDiscoveryChannel() {
return new DirectChannel();
}
@Bean
@ServiceActivator(inputChannel = "serviceDiscoveryChannel")
public MessageHandler serviceDiscoveryMessageHandler(ZookeeperServiceDiscovery discovery) {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws Exception {
String serviceAddress = "http://localhost:" + port;
discovery.register(serviceName, serviceAddress);
}
};
}
@Bean
public MessagingTemplate messagingTemplate(MessageChannel serviceDiscoveryChannel) {
return new MessagingTemplate(serviceDiscoveryChannel);
}
@Bean
public ChannelInterceptor channelInterceptor() {
Executor executor = Executors.newSingleThreadExecutor();
return new ExecutorChannelInterceptor(executor) {
@Override
public ListenableFuture<Void> send(Message<?> message, MessageChannel channel, long timeout) {
ListenableFuture<Void> future = super.send(message, channel, timeout);
future.addCallback(new ListenableFutureCallback<Void>() {
@Override
public void onSuccess(Void result) {
MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class);
accessor.setExpirationDate(null);
}
@Override
public void onFailure(Throwable ex) {
MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class);
accessor.setExpirationDate(null);
}
});
return future;
}
};
}
}
在服务消费者端,通过如下方式获取服务列表并调用服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Random;
@Service
public class ServiceConsumer {
@Autowired
private DiscoveryClient discoveryClient;
public String consumeService() {
List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");
if (instances.isEmpty()) {
throw new RuntimeException("没有找到可用的服务实例");
}
Random random = new Random();
int index = random.nextInt(instances.size());
ServiceInstance instance = instances.get(index);
String serviceUrl = instance.getUri().toString();
// 这里可以使用RestTemplate等工具调用服务
return "调用服务成功,服务地址:" + serviceUrl;
}
}
不过,需要注意的是,相比专门的服务注册与发现组件,Zookeeper 在服务健康检查等方面功能相对较弱,在实际使用中要根据项目需求谨慎选择。例如,Zookeeper 主要通过临时节点的存活状态来间接判断服务是否健康,不像 Eureka 有专门的心跳检测机制,并且在大规模服务集群下,Zookeeper 的 Watcher 机制可能会产生较多的网络开销。
通过了解 Zookeeper 在 Spring Boot3 中的这些使用场景,相信大家对它在后端开发中的重要性有了更深刻的认识。作为后端开发人员,掌握 Zookeeper 的使用,能够让我们在面对复杂的分布式系统问题时更加游刃有余。
如果你在实际项目中也使用过 Zookeeper,欢迎在评论区分享你的经验和遇到的问题,大家一起交流学习!也别忘了点赞、收藏这篇文章,方便后续回顾,同时转发给身边的后端开发小伙伴,让更多人受益!
猜你喜欢
- 2025-06-13 SpringBoot权限炸场!动态鉴权提速10倍吊打RBAC(附工具源码)
- 2025-06-13 Spring Boot 3.4 新特性实战解析(springboot最新)
- 2025-06-13 SpringBoot 2.7.10、3.0.5 发布,修复 DoS漏洞
- 2025-06-13 springboot(二十八)stomp在spring5.3以上报跨域问题的处理
- 2025-06-13 还在为 Spring Boot3 动态配置发愁?一文教你轻松搞定!
- 2025-06-13 SpringBoot几种动态修改配置的方法
- 2025-06-13 快来看看SpringBoot2.2发行版,你能用到哪些新特性?
- 2025-06-13 Spring Boot3 应用打包成 Docker 镜像全攻略
- 2025-06-13 Spring Boot3 动态配置实现方案全解析,你掌握了吗?
- 2025-06-13 Spring Framework 6.2 和 Spring Boot 3.4 为 2025 年新一代做好准备
- 最近发表
-
- 面试中常被问到的Hash表,你了解吗
- JAVA面试考点:一文搞懂一致性Hash的原理和实现
- 一次性搞清楚equals和hashCode(hashcode() 与equals()区别,简单说明)
- HashMap.Key的故事:Key为什么出现Hash碰撞及冲突呢?
- hash冲突的几种解决方案对比(hash冲突的解决方式)
- 游戏王LN 无头骑士(无头骑士cv)
- Linux ln、unlink命令用法(linux link命令详解)
- n和l分不清矫正发音方法,这三步就够了
- golang引用私有gitlab项目代码(golang引入当前包下的文件)
- Instamic:录音领域中的 GoPro,让你想录就录,随心所欲
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)
- mysql数据库面试题 (57)