什么是分布式锁?
为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
为什么需要锁
- 多任务环境中才需要;
- 任务都需要对同一共享资源进行写操作;
- 对资源的访问是互斥的;
Tips:任务通过竞争获取锁才能对该资源进行操作(①竞争锁);当有一个任务在对资源进行更新时(②占有锁),其他任务都不可以对这个资源进行操作(③任务阻塞),直到该任务完成更新(④释放锁);
JVM锁解决不了分布式环境多任务对共享资源竞争的协同操作问题,因此就需要分布式锁。
分布式锁方案比较
分布式锁比较
设计实现锁
锁E-R图
1、定义锁的接口Lock
2、在AbstractLock模板锁里面实现getLock方法,实现通用的逻辑。
3、不能确实的步骤,作为虚拟方法,甩锅给子类实现。
4、子类只需要聚焦自己的小步骤逻辑,实现tryLock,waitLock,unLock方法。
ZK基于同名节点的分布式锁
此种实现方式会出现羊群效应。
ZK基于临时序列节点实现分布式锁
这种实现方式能防止羊群效应
代码
代码地址:https://github.com/SunSmileAZY/zk-lock.git
1、创建lock接口类
/**
*
* @author pine
* @date 20200221
*/
public interface Lock {
/**
* 获取到锁的资源
*/
void getLock();
/**
* 释放锁
*/
void unLock();
}
2、创建抽象类
package cn.pine.zk;
/**
*
* @author pine
* @date 20200221
*/
public abstract class AbstractLock implements Lock{
public void getLock() {
//任务通过竞争获取锁才能对该资源进行操作(①竞争锁);
// 当有一个任务在对资源进行更新时(②占有锁),
// 其他任务都不可以对这个资源进行操作(③任务阻塞),
// 直到该任务完成更新(④释放锁)
//尝试获得锁资源
//①竞争锁
if (tryLock()) {
System.out.println("##获取lock锁的资源####");
} else {
//③任务阻塞
waitLock();
// 重新获取锁资源
getLock();
}
}
// ②占有锁
public abstract boolean tryLock();
// 等待
public abstract void waitLock();
}
3、继承ZookeeperAbstractLock 实现锁
package cn.pine.zk;
import org.I0Itec.zkclient.ZkClient;
/**
* 提取公共代码
* @author pine
* @date 20200221
*/
public abstract class ZookeeperAbstractLock extends AbstractLock {
// zk连接地址
private static final String CONNECTSTRING = "127.0.0.1:2181";
// 创建zk连接
protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
protected static final String PATH = "/lock";
protected static final String PATH2 = "/lock2";
}
package cn.pine.zk;
import org.I0Itec.zkclient.IZkDataListener;
import org.springframework.stereotype.Service;
import java.util.concurrent.CountDownLatch;
/**
*
* @author pine
* @date 20200221
*/
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
private CountDownLatch countDownLatch = null;
@Override
//尝试获得锁
public boolean tryLock() {
try {
zkClient.createEphemeral(PATH);
return true;
} catch (Exception e) {
//如果创建失败报出异常
// e.printStackTrace();
return false;
}
}
@Override
public void waitLock() {
IZkDataListener izkDataListener = new IZkDataListener() {
public void handleDataDeleted(String path) throws Exception {
// 唤醒被等待的线程
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
public void handleDataChange(String path, Object data) throws Exception {
}
};
// 注册事件
zkClient.subscribeDataChanges(PATH, izkDataListener);
//如果节点存
if (zkClient.exists(PATH)) {
countDownLatch = new CountDownLatch(1);
try {
//等待,一直等到接受到事件通知
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
// 删除监听
zkClient.unsubscribeDataChanges(PATH, izkDataListener);
}
public void unLock() {
//释放锁
if (zkClient != null) {
zkClient.delete(PATH);
zkClient.close();
System.out.println("释放锁资源...");
}
}
}
package cn.pine.zk;
import cn.pine.simple.OrderNumGenerator;
import java.util.concurrent.CountDownLatch;
/**
*
* @author pine
* @date 20200221
*/
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
//发令枪,模拟50个并发
private static CountDownLatch countDownLatch = new CountDownLatch(50);
// private Lock lock = new ZookeeperDistrbuteLock();
private Lock lock = new ZookeeperDistrbuteLock2();
public void run() {
getNumber();
}
public void getNumber() {
try {
countDownLatch.await();
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){
System.out.println("####生成唯一订单号###");
for (int i = 0; i < 50; i++) {
new Thread(new OrderService()).start();
countDownLatch.countDown();
}
}
}
package cn.pine.zk;
import cn.pine.simple.OrderNumGenerator;
import java.util.concurrent.CountDownLatch;
/**
* 测试类
* @author pine
* @date 20200221
*/
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
//发令枪,模拟50个并发
private static CountDownLatch countDownLatch = new CountDownLatch(50);
// private Lock lock = new ZookeeperkLock();
private Lock lock = new ZookeeperDistrbuteLock2();
public void run() {
getNumber();
}
public void getNumber() {
try {
countDownLatch.await();
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){
System.out.println("####生成唯一订单号###");
for (int i = 0; i < 50; i++) {
new Thread(new OrderService()).start();
countDownLatch.countDown();
}
}
}
总结
分布式锁建议用zk的方式做,注意羊群效应。要用第二种方式做。