1.传统业务场景
在一个项目中,需要生成一个全局的唯一ID,现在的方案有UUID,时间戳。用多线程模拟生成。
什么是分布式锁?
分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性。
分布式系统情况会出现的问题,因为集群的缘故,有很多台服务器,每个服务器都有自己的web容器,在同一时间有可能导致生成的全局唯一id出现的现象。
应对上面的问题,现有下面几种解决方案:
1.使用redis,提前生成好,然后从redis里去取,
2.使用分布式锁,Zookeeper中的临时节点。
使用分布式锁的相对于其他实现方式的优缺点:
1.使用数据库实现分布式锁,就是往数据库中添加一条数据,利用版本号等技术。这种实现方式的性能太差,线程有异常发生时,容易出现死锁的现象。
2.使用redis实现
锁的失效时间难控制,容易产生死锁,非阻塞,不可重入
3.使用Zookeeper实现分布式锁
实现相对简单,可靠性强,使用临时节点,失效时间容易控制(zk关闭)
使用***Zookeeper***实现分布式锁:
原理:使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……
利用Zookeeper实现分布式锁的原理解析:
就跟使用可重入锁的机制一样,在生成一个ID之前,先获取锁,如果该锁正在被别人使用,则等待,然后重新获取,生成完毕之后释放锁(关闭客户端),对应zookeeper的原理就是,创建一个临时节点/lock,如果该节点存在就等待(因为Zookeeper的特性,节点名称的唯一性),等待锁时利用了Zookeeper的事件监听,这个上个博客有详细介绍。https://mp.csdn.net/mdeditor/88979697#
实现代码如下:
package com.cqr.demo.com.cqr.demo.test;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @ProjectName: car-zook
* @Package: com.cqr.demo.com.cqr.demo.test
* @ClassName: OrderNumGenerator
* @Author: XW
* @Description: ${description}
* @Date: 2019/4/9 14:39
* @Version: 1.0
*/
public class OrderNumGenerator {
public static int count = 0;
public String getNumber() {
try {
Thread.sleep(200);
} catch (Exception e) {
}
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
return simpt.format(new Date()) + "-" + ++count;
}
}
//自己创建一个锁
package com.cqr.demo.com.cqr.demo.test;
/**
* @ProjectName: car-zook
* @Package: com.cqr.demo.com.cqr.demo.test
* @ClassName: Lock
* @Author: XW
* @Description: ${description}
* @Date: 2019/4/9 13:53
* @Version: 1.0
*/
public interface Lock {
//获取锁
void getLock();
//释放锁
void unlock();
}
创建一个抽象类实现Lock接口
package com.cqr.demo.com.cqr.demo.test;
import org.I0Itec.zkclient.ZkClient;
/**
* @ProjectName: car-zook
* @Package: com.cqr.demo.com.cqr.demo.test
* @ClassName: ZookeeperAbstractLock
* @Author: XW
* @Description: ${description}
* @Date: 2019/4/9 20:54
* @Version: 1.0
*/
public abstract class ZookeeperAbstractLock implements Lock{
// zookeeper连接地址
private static final String CONNECTSTRING = "127.0.0.1:2181";
// 创建zookeeper连接
protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
protected static final String PATH = "/lock";
@Override
public void getLock() {
if(trylock()){
System.out.println("获取锁成功");
}else{
//如果没有获取到锁,则等待,直到释放锁为止
waitLock();
//等待结束,继续获取锁
getLock();
}
}
protected abstract void waitLock();
protected abstract boolean trylock();
@Override
public void unlock() {
if(zkClient!=null){
//关闭zookeeper客户端则释放锁
zkClient.close();
}
}
}
package com.cqr.demo.com.cqr.demo.test;
import org.I0Itec.zkclient.IZkDataListener;
import java.util.concurrent.CountDownLatch;
/**
* @ProjectName: car-zook
* @Package: com.cqr.demo.com.cqr.demo.test
* @ClassName: ZookeeperDistrbuteLock
* @Author: XW
* @Description: ${description}
* @Date: 2019/4/9 21:00
* @Version: 1.0
*/
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock{
//信号量,用于控制
private CountDownLatch countDownLatch=null;
@Override
protected void waitLock() {
IZkDataListener iZkDataListener=new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
//当节点被删除时唤醒等待的线程
if(countDownLatch!=null){
countDownLatch.countDown();
}
}
};
//注册事件
zkClient.subscribeDataChanges(PATH,iZkDataListener);
if(zkClient.exists(PATH)){
//如果存在
countDownLatch=new CountDownLatch(1);
try {
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
// 删除监听
zkClient.unsubscribeDataChanges(PATH, iZkDataListener);
}
@Override
protected boolean trylock() {
try {
zkClient.createEphemeral(PATH);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
}
package com.cqr.demo.com.cqr.demo.test;
/**
* @ProjectName: car-zook
* @Package: com.cqr.demo.com.cqr.demo.test
* @ClassName: OrderService
* @Author: XW
* @Description: ${description}
* @Date: 2019/4/9 14:41
* @Version: 1.0
*/
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
private Lock lock = new ZookeeperDistrbuteLock();
public void run() {
getNumber();
}
public void getNumber() {
try {
lock.getLock();
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread( new OrderService()).start();
}
}
}