背景:
背景很多,比如集群中多个节点生成某个序列ID, 多个节点同时在自身节点做某些事,但是这件事只能同时只有一个节点去做,网上看过一些案例,比如火车票,库存减少等很多案例,还有一种极端的情况,比如有两个内部的系统之间服务的交互,在A系统开启线程调用B系统之后执行下面逻辑,然而B系统中有的逻辑要等A系统执行完之后再执行,有的A系统要等B系统执行完部分逻辑在执行,以此交替,这种非常极端的情况,也是不符合分布式系统设计,感觉两个系统太耦合了,我就遇到这么吐血的分布式架构,也不知道之前拆分这两套系统的人怎么想的,完全没有好好的解耦就开始这样搞,感觉像是为了拆而拆,好了这里不吐槽了,感觉成了吐槽文章。
解决方案
这里也利用zookeeper顺序创建节点的特性来解决锁的问题,curator客户已经帮我封装好了一系列逻辑,我们这里只需要用就行了,先附上《从paxos到zookeeper分布式一致原理与实践》的代码,下面来解释里面的实现的过程
引入的maven依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.4.2</version>
</dependency>
package com.test.zookeeper.curator;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
public class Curator {
static String connectionString = "192.168.66.138:2181";
static String lock_path = "/curator_recipes_lock_path";
static CuratorFramework client = CuratorFrameworkFactory
.builder().connectString(connectionString)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.sessionTimeoutMs(5000).build();
public static void main(String[] args) throws Exception {
recipes_Lock();
System.in.read();
}
/**
* 分布式锁
* 这里是一个客户端多个线程去执行,如果多个客户端模拟也是一样的,因为都是在相同的节点下
*/
static void recipes_Lock(){
client.start();
final InterProcessMutex lock = new InterProcessMutex(client, lock_path);
final CountDownLatch down = new CountDownLatch(1);
for(int i=0; i<10; i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
down.await();
lock.acquire();
} catch (Exception e) {
}
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss|SSS");
String orderNo = format.format(new Date());
System.out.println("生成的订单号是: "+orderNo);
try {
lock.release();
} catch (Exception e) {
}
}
}).start();
}
down.countDown();
}
}
InterProcessMutex类通过CuratorFramework和path构造,当执行了acquire方法的时候会在/curator_recipes_master_path节点下生成尾号带顺序的节点,可以看下图
上面代码起了10个线程,所有生成了10个临时节点
zookeeper服务会获取/curator_recipes_master_path节点下所有的等待锁的临时节点,最小的节点获得锁,并非最小的节点会等待上一个节点释放锁,释放锁的时候会删除临时节点,所以并非最小的节点会在小1的节点注册事件,一旦监听到事件,会立马唤醒所有等待的线程,具体源码实现已经有很多文章可以观看