概述
在單體的應(yīng)用開(kāi)發(fā)場(chǎng)景中涉及并發(fā)同步的時(shí)候馒索,大家往往采用Synchronized(同步)或者其他同一個(gè)JVM內(nèi)Lock機(jī)制來(lái)解決多線程間的同步問(wèn)題。在分布式集群工作的開(kāi)發(fā)場(chǎng)景中名船,就需要一種更加高級(jí)的鎖機(jī)制來(lái)處理跨機(jī)器的進(jìn)程之間的數(shù)據(jù)同步問(wèn)題绰上。這種跨機(jī)器的鎖就是分布式鎖。
用zookeeper來(lái)實(shí)現(xiàn)分布式鎖在分布式系統(tǒng)中是非常常見(jiàn)的場(chǎng)景渠驼。
Curator
Curator是Netflix公司開(kāi)源的一套ZooKeeper客戶(hù)端框架蜈块,提供了一套易用性和可讀性更強(qiáng)的Fluent風(fēng)格的客戶(hù)端API框架
為ZooKeeper客戶(hù)端框架提供了一些比較普遍的、開(kāi)箱即用的迷扇、分布式開(kāi)發(fā)用的解決方案粪狼,例如Recipe闪金、共享鎖服務(wù)、Master選舉機(jī)制和分布式計(jì)算器等者填,幫助開(kāi)發(fā)者避免了“重復(fù)造輪子”的無(wú)效開(kāi)發(fā)工作
Guava is to Java that Curator to ZooKeeper
更多Curator介紹參考:
《Zookeeper開(kāi)源客戶(hù)端框架Curator簡(jiǎn)介》https://www.iteye.com/blog/macrochen-1366136
SpringBoot整合zookeeper澜术、curator
- 創(chuàng)建springboot項(xiàng)目,在pom.xml文件里加入zookeeper庭再、curator依賴(lài)
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</exclusion>
</exclusions>
</dependency>
完整的pom.xml文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.zhxin</groupId>
<artifactId>zkboot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 在resource目錄下創(chuàng)建config目錄膳汪,并創(chuàng)建zookeeper.properties配置文件,內(nèi)容如下
zookeeper.server= 127.0.0.1:2181
zookeeper.lockPath = /springboot_zk_lock/
- 創(chuàng)建org.zhangsan.beans.lock包叙量,并在包里創(chuàng)建AbstractZookeeperLock.java、TestLock.java兩個(gè)類(lèi)
AbstractZookeeperLock.java
package org.zhangsan.beans.lock;
import java.util.concurrent.TimeUnit;
/**
* @ClassName AbstractZookeeperLock
* @Description //AbstractZookeeperLock 鎖
* @Author singleZhang
* @Email 405780096@qq.com
* @Date 2020/12/8 0008 下午 3:17
**/
public abstract class AbstractZookeeperLock<T> {
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 測(cè)試鎖類(lèi)
* @Author singleZhang
* @Email 405780096@qq.com
* @Date 2020/12/8 0008 下午 3:26
**/
public abstract class TestLock<String> extends AbstractZookeeperLock<String> {
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包下隘庄,創(chuàng)建分布式鎖客戶(hù)端類(lèi)文件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 // 分布式鎖客戶(hù)端
* @Author singleZhang
* @Email 405780096@qq.com
* @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> T lock(AbstractZookeeperLock<T> mutex) {
String path = this.getZookeeperLockPath() + mutex.getLockPath();
InterProcessMutex lock = new InterProcessMutex(this.getClient(), path); //創(chuàng)建鎖對(duì)象
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());
}
}
}
- 創(chuàng)建org.zhangsan.config包,并在包中創(chuàng)建ZookeeperConfig.java配置類(lèi)
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 配置類(lèi)
* @Author singleZhang
* @Email 405780096@qq.com
* @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.啟動(dòng)文件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);
}
}
- 接下來(lái)測(cè)試一下鎖的功能是否能正常運(yùn)行,在test目錄下創(chuàng)建org.zhangsan.zookeeper包踢步,并創(chuàng)建ZkLockTest.java測(cè)試類(lèi)
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鎖測(cè)試
* @Author singleZhang
* @Email 405780096@qq.com
* @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<String>(lockId) {
@Override
public String execute() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.getLockId();
}
});
if (result == null) {
System.out.println("執(zhí)行失敗");
} else {
System.out.println("執(zhí)行成功");
}
}
}
-
啟動(dòng)之前配置好的zookeeper集群,運(yùn)行項(xiàng)目癣亚,測(cè)試
運(yùn)行成功丑掺,并同時(shí)間更新日志文件
分布式鎖測(cè)試結(jié)果
日志為最新時(shí)間
日志
- 在zookeeper日志目錄里查看日志
因?yàn)閦ookeeper的日志文件為二進(jìn)制文件,這里需要用到兩個(gè)jar包:/slf4j-api-1.7.25.jar述雾、zookeeper-3.4.14.jar
LogFormatter 為用到的格式化類(lèi)街州。
查看命令如下:
>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
總結(jié)
ZooKeeper分布式鎖:
- 優(yōu)點(diǎn)
ZooKeeper分布式鎖(如InterProcessMutex)兼丰,能有效地解決分布式問(wèn)題,不可重入問(wèn)題唆缴,使用起來(lái)也較為簡(jiǎn)單 - 缺點(diǎn)
ZooKeeper實(shí)現(xiàn)的分布式鎖鳍征,性能并不太高。
因?yàn)槊看卧趧?chuàng)建鎖和釋放鎖的過(guò)程中面徽,都要?jiǎng)討B(tài)創(chuàng)建艳丛、銷(xiāo)毀暫時(shí)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)鎖功能,
Zk中創(chuàng)建和刪除節(jié)點(diǎn)只能通過(guò)Leader(主)服務(wù)器來(lái)執(zhí)行趟紊,然后Leader服務(wù)器還需要將數(shù)據(jù)同步到所有的Follower(從)服務(wù)器上氮双,這樣頻繁的網(wǎng)絡(luò)通信,系統(tǒng)性能會(huì)下降霎匈。
總之戴差,在高性能、高并發(fā)的應(yīng)用場(chǎng)景下铛嘱,不建議使用ZooKeeper的分布式鎖暖释,而由于ZooKeeper的高可用性,因此在并發(fā)量不是太高的應(yīng)用場(chǎng)景中墨吓,還是推薦使用ZooKeeper的分布式鎖球匕。
目前分布式鎖,比較成熟肛真、主流的方案有兩種:
- 基于Redis的分布式鎖谐丢。適用于并發(fā)量很大、性能要求很高而可靠性問(wèn)題可以通過(guò)其他方案去彌補(bǔ)的場(chǎng)景蚓让。
- 基于ZooKeeper的分布式鎖乾忱。適用于高可靠,而并發(fā)量不是太高的場(chǎng)景
在選型時(shí)历极,選擇適合于自己業(yè)務(wù)場(chǎng)景的方案即可窄瘟。
項(xiàng)目代碼地址:
https://gitee.com/kaixinshow/springboot-note