zookeeper实现分布式锁的原理

源于蚂蚁课堂的学习,点击这里查看(老余很给力)

所谓分布式锁,就是多个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关闭连接方式释放锁,从而是其他的请求开始进入到获取锁的资源。

可根据链接的请求时间去设置业务的超时时间,在规定时间内完成业务,否则就进行数据的回滚
原创文章 148 获赞 258 访问量 11万+

猜你喜欢

转载自blog.csdn.net/yxh13521338301/article/details/105966408
今日推荐