zookeeper在项目中有很多应用,其中一个比较常见的的就是分布式锁,zookeeper实现分布式锁的原理是根据zookeeper创建的临时有序节点,每次zookeeper在同一个目录下创建的临时有序节点是有序的,会自动累加,如果本次操作创建的节点在目录内是最小节点,则获得锁,否则阻塞等待锁,并且总是在前一个节点上注册watcher监视前一个节点的释放,较小的节点释放后,等待的节点对应的操作获得锁,以此类推,代码简单实现如下:
public
class
ZKDisLock {
private
static
final
Logger SERVICE_LOG = LoggerFactory.getLogger(LogConstants.SERVICE_LOG);
//锁目录
private
static
final
String BASE_PATH =
"/disLock"
;
private
static
final
String SPLIT_FLAG =
"_"
;
private
Integer sessionOut;
private
String zkHost;
private
ZooKeeper zooKeeper;
//当前锁节点名称
private
String lockNode;
//等待锁节点名称
private
String waitNode;
private
CountDownLatch countDownLatch;
public
ZKDisLock(Integer sessionOut, String zkHost) {
this
.sessionOut = sessionOut;
this
.zkHost = zkHost;
}
//ZKDisLock初始化
public
void
init()
throws
DisLockException {
try
{
zooKeeper =
new
ZooKeeper(zkHost, sessionOut,
new
Watcher() {
@Override
public
void
process(WatchedEvent watchedEvent) {
}
});
//判断有无根目录,没有的话创建
Stat stat = zooKeeper.exists(BASE_PATH,
false
);
if
(stat ==
null
) {
zooKeeper.create(BASE_PATH,
null
, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
catch
(Exception e) {
SERVICE_LOG.info(
"zk初始化失败"
);
throw
new
DisLockException(
"zk初始化失败"
);
}
}
//加锁
public
void
lock(String content)
throws
DisLockException {
if
(zooKeeper ==
null
) {
throw
new
DisLockException(
"zk未初始化"
);
}
try
{
//创建节点
String itemName = zooKeeper.create(BASE_PATH +
"/"
+ content + SPLIT_FLAG,
null
, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
//判断节点是否是目录中同一个content的最小节点
if
(isLowestNode(content, itemName)) {
SERVICE_LOG.info(
"线程:"
+ Thread.currentThread().getId() +
",加锁成功,节点名称:"
+ lockNode);
return
;
}
else
{
SERVICE_LOG.info(
"线程:"
+ Thread.currentThread().getId() +
",等待锁:"
+ waitNode);
//不是最小节点,等待锁释放
waitForLock(sessionOut);
//锁释放后,再次判断下是否是最小节点
if
(isLowestNode(content, itemName)) {
SERVICE_LOG.info(
"线程:"
+ Thread.currentThread().getId() +
",获得锁,节点名称:"
+ lockNode);
return
;
}
}
}
catch
(Exception e) {
SERVICE_LOG.error(
"加锁异常"
);
throw
new
DisLockException(
"加锁异常"
);
}
}
//判断是否是最小节点
private
boolean
isLowestNode(String content, String itemName)
throws
Exception {
//获取所有子节点
List<String> nodeNames = zooKeeper.getChildren(BASE_PATH,
null
);
List<String> itemNodes =
new
ArrayList<>();
lockNode = itemName;
//过滤content节点
for
(String name : nodeNames) {
if
(name.split(SPLIT_FLAG)[
0
].toString().equals(content)) {
itemNodes.add(name);
}
}
//排序
Collections.sort(itemNodes);
//最小节点返回
if
(itemName.equals(BASE_PATH +
"/"
+ itemNodes.get(
0
))) {
return
true
;
}
//非最小节点,找到前一个节点
int
nodeIndex = Collections.binarySearch(itemNodes, itemName.substring(itemName.lastIndexOf(
"/"
) +
1
));
waitNode = itemNodes.get(nodeIndex -
1
);
return
false
;
}
//等待锁
private
void
waitForLock(
final
Integer waitTime)
throws
Exception {
//判断等待节点是否释放,同时注册watcher,通知释放事件
Stat stat = zooKeeper.exists(BASE_PATH +
"/"
+ waitNode,
new
Watcher() {
@Override
public
void
process(WatchedEvent watchedEvent) {
SERVICE_LOG.info(
"watch proceess "
+ watchedEvent.toString());
if
(countDownLatch !=
null
) {
//锁释放,通知等待节点
countDownLatch.countDown();
}
}
});
//如果等待的锁不存在返回
if
(stat ==
null
) {
return
;
}
//存在,等待
countDownLatch =
new
CountDownLatch(
1
);
countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
}
//释放锁
public
void
unLock()
throws
DisLockException {
try
{
//删除锁节点
zooKeeper.delete(lockNode, -
1
);
SERVICE_LOG.info(
"线程:"
+ Thread.currentThread().getId() +
",解锁成功,节点名称:"
+ lockNode);
lockNode =
null
;
zooKeeper.close();
}
catch
(Exception e) {
String errorMsg =
"解锁异常,节点名称:"
+ lockNode;
SERVICE_LOG.error(errorMsg);
throw
new
DisLockException(errorMsg);
}
}
}
|
以上是用zookeeper实现分布式锁的一个简单实现,可能有些地方并不太严谨,但是足以说明问题,下面来测试下这个分布式锁:
public
static
void
main(String[] args)
throws
Exception{
ExecutorService executorService= Executors.newFixedThreadPool(
40
);
String content=
"abcdefg"
;
String zkHost=
"127.0.0.1"
;
Integer sessionOut=
30000
;
//创建10个线程测试
for
(
int
i=
0
;i<
10
;++i){
ZKDisLock disLock=
new
ZKDisLock(sessionOut,zkHost);
disLock.init();
executorService.submit(
new
TestJob(disLock,content));
}
}
//测试任务
private
static
class
TestJob
implements
Runnable{
private
ZKDisLock disLock;
private
String content;
public
TestJob(ZKDisLock disLock, String content) {
this
.disLock = disLock;
this
.content = content;
}
@Override
public
void
run() {
try
{
disLock.lock(content);
Thread.sleep(
2000
);
disLock.unLock();
}
catch
(Exception e){
System.out.println(e.getMessage());
}
}
}
|
运行结果如下:
17
-
12
-
20
15
:
03
:
03
,
436
INFO service(ZKDisLock.java:
73
) ## 线程:
13
,等待锁:abcdefg_0000000327
17
-
12
-
20
15
:
03
:
03
,
436
INFO service(ZKDisLock.java:
73
) ## 线程:
19
,等待锁:abcdefg_0000000328
17
-
12
-
20
15
:
03
:
03
,
436
INFO service(ZKDisLock.java:
70
) ## 线程:
16
,加锁成功,节点名称:/disLock/abcdefg_0000000327
17
-
12
-
20
15
:
03
:
03
,
438
INFO service(ZKDisLock.java:
73
) ## 线程:
22
,等待锁:abcdefg_0000000329
17
-
12
-
20
15
:
03
:
03
,
441
INFO service(ZKDisLock.java:
73
) ## 线程:
25
,等待锁:abcdefg_0000000330
17
-
12
-
20
15
:
03
:
03
,
444
INFO service(ZKDisLock.java:
73
) ## 线程:
28
,等待锁:abcdefg_0000000331
17
-
12
-
20
15
:
03
:
03
,
456
INFO service(ZKDisLock.java:
73
) ## 线程:
31
,等待锁:abcdefg_0000000332
17
-
12
-
20
15
:
03
:
03
,
460
INFO service(ZKDisLock.java:
73
) ## 线程:
34
,等待锁:abcdefg_0000000333
17
-
12
-
20
15
:
03
:
03
,
464
INFO service(ZKDisLock.java:
73
) ## 线程:
37
,等待锁:abcdefg_0000000334
17
-
12
-
20
15
:
03
:
03
,
469
INFO service(ZKDisLock.java:
73
) ## 线程:
40
,等待锁:abcdefg_0000000335
17
-
12
-
20
15
:
03
:
05
,
442
INFO service(ZKDisLock.java:
137
) ## 线程:
16
,解锁成功,节点名称:/disLock/abcdefg_0000000327
17
-
12
-
20
15
:
03
:
05
,
443
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000327
17
-
12
-
20
15
:
03
:
05
,
444
INFO service(ZKDisLock.java:
76
) ## 线程:
13
,获得锁,节点名称:/disLock/abcdefg_0000000328
17
-
12
-
20
15
:
03
:
07
,
449
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000328
17
-
12
-
20
15
:
03
:
07
,
449
INFO service(ZKDisLock.java:
137
) ## 线程:
13
,解锁成功,节点名称:/disLock/abcdefg_0000000328
17
-
12
-
20
15
:
03
:
07
,
451
INFO service(ZKDisLock.java:
76
) ## 线程:
19
,获得锁,节点名称:/disLock/abcdefg_0000000329
17
-
12
-
20
15
:
03
:
09
,
459
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000329
17
-
12
-
20
15
:
03
:
09
,
459
INFO service(ZKDisLock.java:
137
) ## 线程:
19
,解锁成功,节点名称:/disLock/abcdefg_0000000329
17
-
12
-
20
15
:
03
:
09
,
461
INFO service(ZKDisLock.java:
76
) ## 线程:
22
,获得锁,节点名称:/disLock/abcdefg_0000000330
17
-
12
-
20
15
:
03
:
11
,
469
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000330
17
-
12
-
20
15
:
03
:
11
,
469
INFO service(ZKDisLock.java:
137
) ## 线程:
22
,解锁成功,节点名称:/disLock/abcdefg_0000000330
17
-
12
-
20
15
:
03
:
11
,
471
INFO service(ZKDisLock.java:
76
) ## 线程:
25
,获得锁,节点名称:/disLock/abcdefg_0000000331
17
-
12
-
20
15
:
03
:
13
,
480
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000331
17
-
12
-
20
15
:
03
:
13
,
480
INFO service(ZKDisLock.java:
137
) ## 线程:
25
,解锁成功,节点名称:/disLock/abcdefg_0000000331
17
-
12
-
20
15
:
03
:
13
,
482
INFO service(ZKDisLock.java:
76
) ## 线程:
28
,获得锁,节点名称:/disLock/abcdefg_0000000332
17
-
12
-
20
15
:
03
:
15
,
488
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000332
17
-
12
-
20
15
:
03
:
15
,
488
INFO service(ZKDisLock.java:
137
) ## 线程:
28
,解锁成功,节点名称:/disLock/abcdefg_0000000332
17
-
12
-
20
15
:
03
:
15
,
490
INFO service(ZKDisLock.java:
76
) ## 线程:
31
,获得锁,节点名称:/disLock/abcdefg_0000000333
17
-
12
-
20
15
:
03
:
17
,
498
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000333
17
-
12
-
20
15
:
03
:
17
,
498
INFO service(ZKDisLock.java:
137
) ## 线程:
31
,解锁成功,节点名称:/disLock/abcdefg_0000000333
17
-
12
-
20
15
:
03
:
17
,
500
INFO service(ZKDisLock.java:
76
) ## 线程:
34
,获得锁,节点名称:/disLock/abcdefg_0000000334
17
-
12
-
20
15
:
03
:
19
,
506
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000334
17
-
12
-
20
15
:
03
:
19
,
506
INFO service(ZKDisLock.java:
137
) ## 线程:
34
,解锁成功,节点名称:/disLock/abcdefg_0000000334
17
-
12
-
20
15
:
03
:
19
,
508
INFO service(ZKDisLock.java:
76
) ## 线程:
37
,获得锁,节点名称:/disLock/abcdefg_0000000335
17
-
12
-
20
15
:
03
:
21
,
516
INFO service(ZKDisLock.java:
118
) ## watch proceess WatchedEvent state:SyncConnected type:NodeDeleted path:/disLock/abcdefg_0000000335
17
-
12
-
20
15
:
03
:
21
,
516
INFO service(ZKDisLock.java:
137
) ## 线程:
37
,解锁成功,节点名称:/disLock/abcdefg_0000000335
17
-
12
-
20
15
:
03
:
21
,
518
INFO service(ZKDisLock.java:
76
) ## 线程:
40
,获得锁,节点名称:/disLock/abcdefg_0000000336
17
-
12
-
20
15
:
03
:
23
,
527
INFO service(ZKDisLock.java:
137
) ## 线程:
40
,解锁成功,节点名称:/disLock/abcdefg_0000000336
|
从以上结果可以看到,线程16最先获得锁,创建最小节点 /disLock/abcdefg_0000000327,其次是线程13等待线程16的锁释放,其对应节点是/disLock/abcdefg_0000000328,等待abcdefg_0000000327释放,线程16锁释放后,线程13获得锁,其次是线程19等在线程13之后,以此类推,最后所有线程都获得了锁并且成功解锁,另外上述运行结果也展示了线程之间的锁的交接时通过注册的watcher传递的。以上的测试结果表明,我们实现的分布式锁可以正确的运行,达到了预期效果。