源于蚂蚁课堂的学习,点击这里查看(老余很给力)
所谓分布式锁,就是多个JVM之间可以共享的锁。众所周知,我们常见的锁(sync,lock,cas)都是基于同一JVM。
在不同JVM中实现锁的共享,就需要一个全局的地方存储这把锁,zk和redis是主流解决这种问题的技术
场景:分布式任务调度平台。多个节点同时触发定时任务,这样会导致重复执行。可使用分布式锁去解决这种问题
仿真问题
仿真场景:假设用户的ID是通过策略自增得到,理论上每次获取的ID都会不同,假设现在有多个JVM,那么就可能会出现
同时获取id的情形,这时会出现ID重复的现象
/**
* @Description todo
* @Author: yanxh<br>
* @Date 2020-05-07 10:11<br>
* @Version 1.0<br>
*/
public class IdUtils {
private static int i;
public static int getId(){
return i++;
}
}
/**
* @Description todo
* @Author: yanxh<br>
* @Date 2020-05-07 10:05<br>
* @Version 1.0<br>
*/
public class UserService {
public String getUserId() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Thread.currentThread().getName() + "___" + IdUtils.getId();
}
}
/**
* @Description todo
* @Author: yanxh<br>
* @Date 2020-05-07 10:12<br>
* @Version 1.0<br>
*/
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(new UserService().getUserId())).start();
}
}
}
使用zookeeper进行分布式锁
/**
* @Description todo
* @Author: yanxh<br>
* @Date 2020-05-07 10:11<br>
* @Version 1.0<br>
*/
public class IdUtils {
private static int i;
public static final String path = "/lock";
public static String getId() {
return System.currentTimeMillis() + "___" + i++;
}
}
package live.yanxiaohui.lock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import java.util.concurrent.CountDownLatch;
/**
* @Description todo
* @Author: yanxh<br>
* @Date 2020-05-07 10:41<br>
* @Version 1.0<br>
*/
public class ZookeeperLock {
private final String URL = "127.0.0.1:2181";
private final CountDownLatch countDownLatch = new CountDownLatch(1);
// 超时时间
private final int TIME_OUT = 50000;
private ZkClient zkClient;
public ZookeeperLock() {
zkClient = new ZkClient(URL, TIME_OUT);
}
/**
* 判断锁是否存在
*
* @param path
* @return
*/
public boolean tryLock(String path) {
try {
zkClient.createEphemeral(path);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取锁
*
* @param path 文件节点的路径
* @return
* @throws Exception
*/
public void getLock(String path) {
if (tryLock(path)) {
System.out.println(Thread.currentThread().getName() + "___" + IdUtils.getId());
} else {
waitLock(path);
getLock(path);
}
}
/**
* 释放锁
*/
public void close() {
if (zkClient == null) {
return;
}
zkClient.close();
System.out.println(Thread.currentThread().getName() + "___释放了锁");
}
/**
* 等待获取锁
*
* @param path 文件节点的路径
* @throws Exception
*/
public void waitLock(String path) {
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
// 当前节点被删除,继续进行锁的获取
countDownLatch.countDown();
}
};
zkClient.subscribeDataChanges(path, listener);
try {
// 等待锁的释放
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
// 接触绑定
zkClient.unsubscribeDataChanges(path, listener);
}
}
package live.yanxiaohui.lock;
/**
* @Description todo
* @Author: yanxh<br>
* @Date 2020-05-07 10:05<br>
* @Version 1.0<br>
*/
public class UserService {
public void getUserId() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
ZookeeperLock lock = new ZookeeperLock();
String path = IdUtils.path;
try {
lock.getLock(path);
} catch (Exception e) {
lock.waitLock(path);
} finally {
lock.close();
}
}
}
/**
* @Description todo
* @Author: yanxh<br>
* @Date 2020-05-07 10:12<br>
* @Version 1.0<br>
*/
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> new UserService().getUserId()).start();
}
}
}
运行发现,不会再有重复的ID产生
分析其原理
多请求同时创建相同的节点(路径相同),只要谁能够创建成功 谁就能够获取到锁;
如果创建节点的时候,突然该节点已经被其他请求创建的话则直接等待;
只要能够创建节点成功,则开始进入到正常业务逻辑操作,其他没有获取锁进行等待;
正常业务逻辑流程执行完后,调用zk关闭连接方式释放锁,从而是其他的请求开始进入到获取锁的资源。
可根据链接的请求时间去设置业务的超时时间,在规定时间内完成业务,否则就进行数据的回滚