概述
在单体的应用开发场景中涉及并发同步的时候,大家往往采用Synchronized(同步)或者其他同一个JVM内Lock机制来解决多线程间的同步问题。在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题。这种跨机器的锁就是分布式锁。
用zookeeper来实现分布式锁在分布式系统中是非常常见的场景。
Curator
Curator是Netflix公司开源的一套ZooKeeper客户端框架,提供了一套易用性和可读性更强的Fluent风格的客户端API框架
为ZooKeeper客户端框架提供了一些比较普遍的、开箱即用的、分布式开发用的解决方案,例如Recipe、共享锁服务、Master选举机制和分布式计算器等,帮助开发者避免了“重复造轮子”的无效开发工作
Guava is to Java that Curator to ZooKeeper
更多Curator介绍参考:
《Zookeeper开源客户端框架Curator简介》https://www.iteye.com/blog/macrochen-1366136
SpringBoot整合zookeeper、curator
- 创建springboot项目,在pom.xml文件里加入zookeeper、curator依赖
org.apache.zookeeper
zookeeper
3.4.14
org.slf4j
slf4j-log4j12
log4j
log4j
org.apache.curator
curator-framework
4.2.0
org.apache.zookeeper
zookeeper
org.apache.curator
curator-recipes
4.2.0
org.apache.curator
curator-framework
完整的pom.xml文件如下
4.0.0
org.zhxin
zkboot
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.1.1.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
1.18.4
provided
org.apache.zookeeper
zookeeper
3.4.14
org.slf4j
slf4j-log4j12
log4j
log4j
org.apache.curator
curator-framework
4.2.0
org.apache.zookeeper
zookeeper
org.apache.curator
curator-recipes
4.2.0
org.apache.curator
curator-framework
org.springframework.boot
spring-boot-maven-plugin
- 在resource目录下创建config目录,并创建zookeeper.properties配置文件,内容如下
zookeeper.server= 127.0.0.1:2181
zookeeper.lockPath = /springboot_zk_lock/
- 创建org.zhangsan.beans.lock包,并在包里创建AbstractZookeeperLock.java、TestLock.java两个类
AbstractZookeeperLock.java
package org.zhangsan.beans.lock;
import java.util.concurrent.TimeUnit;
/**
* @ClassName AbstractZookeeperLock
* @Description //AbstractZookeeperLock 锁
* @Author singleZhang
* @Email [email protected]
* @Date 2020/12/8 0008 下午 3:17
**/
public abstract class AbstractZookeeperLock {
private static final int TIME_OUT = 5;
public abstract String getLockPath();
public abstract T execute();
public int getTimeout(){
return TIME_OUT;
}
public TimeUnit getTimeUnit(){
return TimeUnit.SECONDS;
}
}
TestLock.java
package org.zhangsan.beans.lock;
import lombok.Getter;
/**
* @ClassName TestLock
* @Description //lock 测试锁类
* @Author singleZhang
* @Email [email protected]
* @Date 2020/12/8 0008 下午 3:26
**/
public abstract class TestLock extends AbstractZookeeperLock {
private static final java.lang.String LOCK_PATH = "test_";
@Getter
private String lockId;
public TestLock(String lockId) {
this.lockId = lockId;
}
@Override
public java.lang.String getLockPath() {
return LOCK_PATH + this.lockId;
}
}
- 在org.zhangsan.beans包下,创建分布式锁客户端类文件ZookeeperClient.java
package org.zhangsan.beans;
import lombok.Getter;
import lombok.Setter;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zhangsan.beans.lock.AbstractZookeeperLock;
/**
* @ClassName ZookeeperClient
* @Description // 分布式锁客户端
* @Author singleZhang
* @Email [email protected]
* @Date 2020/12/8 0008 下午 3:03
**/
public class ZookeeperClient {
private static final Logger logger = LoggerFactory.getLogger(ZookeeperClient.class);
private static final int SLEEP_TIME = 1000;
private static final int MAX_RETRIES = 3;
@Getter @Setter
private String zookeeperServer;
@Getter @Setter
private String zookeeperLockPath;
@Getter
private CuratorFramework client;
public ZookeeperClient(String zookeeperServer, String zookeeperLockPath) {
this.zookeeperServer = zookeeperServer;
this.zookeeperLockPath = zookeeperLockPath;
}
public T lock(AbstractZookeeperLock mutex) {
String path = this.getZookeeperLockPath() + mutex.getLockPath();
InterProcessMutex lock = new InterProcessMutex(this.getClient(), path); //创建锁对象
boolean success = false;
try {
try {
success = lock.acquire(mutex.getTimeout(), mutex.getTimeUnit()); //获取锁
} catch (Exception e) {
throw new RuntimeException("obtain lock error " + e.getMessage() + ", path " + path);
}
if (success) {
return (T) mutex.execute();
} else {
return null;
}
} finally {
try {
if (success){
lock.release(); //释放锁
}
} catch (Exception e) {
logger.error("release lock error {}, path {}", e.getMessage(), path);
}
}
}
public void init() {
this.client = CuratorFrameworkFactory
.builder()
.connectString(this.getZookeeperServer())
.retryPolicy(new ExponentialBackoffRetry(SLEEP_TIME, MAX_RETRIES))
.build();
this.client.start();
}
public void destroy() {
try {
if (getClient() != null) {
getClient().close();
}
} catch (Exception e) {
logger.error("stop zookeeper client error {}", e.getMessage());
}
}
}
- 创建org.zhangsan.config包,并在包中创建ZookeeperConfig.java配置类
package org.zhangsan.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.zhangsan.beans.ZookeeperClient;
/**
* @ClassName ZookeeperConfig
* @Description //Zookeeper 配置类
* @Author singleZhang
* @Email [email protected]
* @Date 2020/12/8 0008 下午 2:52
**/
@Configuration
@PropertySource("classpath:config/zookeeper.properties")
public class ZookeeperConfig {
@Autowired
private Environment environment;
@Bean(initMethod = "init", destroyMethod = "destroy")
public ZookeeperClient zookeeperClient(){
String zookeeperServer = environment.getRequiredProperty("zookeeper.server");
String zookeeperLockPath = environment.getRequiredProperty("zookeeper.lockPath");
return new ZookeeperClient(zookeeperServer, zookeeperLockPath);
}
}
6.启动文件ZkBootApplication.java
package org.zhangsan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class ZkBootApplication {
public static void main(String[] args){
SpringApplication.run(ZkBootApplication.class,args);
}
}
- 接下来测试一下锁的功能是否能正常运行,在test目录下创建org.zhangsan.zookeeper包,并创建ZkLockTest.java测试类
package org.zhangsan.zookeeper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.zhangsan.beans.ZookeeperClient;
import org.zhangsan.beans.lock.TestLock;
/**
* @ClassName ZkLockTest
* @Description //Zookeeper锁测试
* @Author singleZhang
* @Email [email protected]
* @Date 2020/12/8 0008 下午 3:30
**/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ZkLockTest {
@Autowired
private ZookeeperClient zookeeperClient;
@Test
public void zookeeperLockTest(){
String lockId = "123123";
String result = zookeeperClient.lock(new TestLock(lockId) {
@Override
public String execute() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.getLockId();
}
});
if (result == null) {
System.out.println("执行失败");
} else {
System.out.println("执行成功");
}
}
}
-
启动之前配置好的zookeeper集群,运行项目,测试
运行成功,并同时间更新日志文件
日志为最新时间
- 在zookeeper日志目录里查看日志
因为zookeeper的日志文件为二进制文件,这里需要用到两个jar包:/slf4j-api-1.7.25.jar、zookeeper-3.4.14.jar
LogFormatter 为用到的格式化类。
查看命令如下:
>java -classpath lib/slf4j-api-1.7.25.jar:zookeeper-3.4.14.jar org.apache.zookeeper.server.LogFormatter E:/zookeeper/zookeeper-3.4.14/log/zoo-1/version-2/log.200000001
总结
ZooKeeper分布式锁:
- 优点
ZooKeeper分布式锁(如InterProcessMutex),能有效地解决分布式问题,不可重入问题,使用起来也较为简单 - 缺点
ZooKeeper实现的分布式锁,性能并不太高。
因为每次在创建锁和释放锁的过程中,都要动态创建、销毁暂时节点来实现锁功能,
Zk中创建和删除节点只能通过Leader(主)服务器来执行,然后Leader服务器还需要将数据同步到所有的Follower(从)服务器上,这样频繁的网络通信,系统性能会下降。
总之,在高性能、高并发的应用场景下,不建议使用ZooKeeper的分布式锁,而由于ZooKeeper的高可用性,因此在并发量不是太高的应用场景中,还是推荐使用ZooKeeper的分布式锁。
目前分布式锁,比较成熟、主流的方案有两种:
- 基于Redis的分布式锁。适用于并发量很大、性能要求很高而可靠性问题可以通过其他方案去弥补的场景。
- 基于ZooKeeper的分布式锁。适用于高可靠,而并发量不是太高的场景
在选型时,选择适合于自己业务场景的方案即可。
项目代码地址:
https://gitee.com/kaixinshow/springboot-note
返回【Spring Boot学习】目录