ZK分布式锁(未完 待续)

实现思路

公平锁:创建有序节点,判断本节点是不是序号最小的节点(第一个节点),若是,则获取锁;若不是,则监听比该节点小的那个节点的删除事件。

非公平锁:直接尝试在指定path下创建节点,创建成功,则说明该节点抢到锁了。如果创建失败,则监听锁节点的删除事件,或者sleep一段时间后再重试。

可重入:使用ThreadLocal记录加锁客户端的唯一标识。重复时先从ThreadLocal获取,获取到,这认为加锁成功,直接返回。

使用瞬时节点创建可重入公平锁

使用瞬时节点的好处是当Session失效时,该节点将被清理,从而避免使用持久节点加锁成功后,抛异常或宕机或服务器重启等原因造成的锁无法释放问题。

使用curator实现

// 假设需要加锁的订单Id
private static String orderId = "157146671409578219";
// 工程名
private static String appName = "trade_";
// 此次加锁业务处理逻辑描述
private static String operatorDesc = "updateTrade_";

// 部门,每个部门可以有自己的zk空间目录
private static String department = "zfpt" ;

// 锁前缀 应该根据业务 具有唯一性
private static String lockPrefixKey = "/" + appName + operatorDesc ;

/**
 * 每个线程 创建自己的Connection ,创建自己的Session
 */
@Test
public void curator() throws Exception {

    for (int i = 0 ; i < 100 ; i++) {

        Thread currentThread = new Thread(() -> {

            // 创建Connection
            CuratorFramework client = CuratorFrameworkFactory.builder()
                    .connectString("master:2181,slave1:2181,slave2:2181")
                    .retryPolicy(new RetryOneTime(1000)) //重试策略
                    .namespace(department) // 可以设置自己部门缩写
                    .build();
            client.start();

            // 模拟对同一个订单加锁
            InterProcessMutex lock = new InterProcessMutex(client, lockPrefixKey + orderId);

            try {
                // 一直尝试加锁 直到锁可用。 有点像synchronized
                // lock.acquire();
                if(lock.acquire(1, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + " 抢到锁");
                } else {
                    System.out.println(Thread.currentThread().getName() + "超时没有抢到锁");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    // 如果当前线程获得到锁,则释放锁
                    if(lock.isAcquiredInThisProcess()) {
                        System.out.println(Thread.currentThread().getName() + " 释放锁");
                        lock.release();
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 没有抢到锁,故没有释放锁");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        currentThread.setName("Lock【" + i + "】");
        currentThread.start();
    }

    Thread.sleep(Integer.MAX_VALUE);
}

实现原理:
因为ZK不允许在临时节点下创建子节点,所以InterProcessMutex工具类会根据加锁传入path的(也就是案例中的lockPrefixKey + orderId)创建一个持久节点;然后在这个持久节点下创建瞬时节点,创建成功后,对该持久节点下的全部子节点进行降序排序,判断当前节点是否是第一个节点,如果是则获取锁,否则对前一个节点加上监听事件。然后Object.wait(),当监听事件被触发,则会调用notifyAll方法,对等待线程进行唤醒。再次尝试获取锁。

缺点:

  1. 传入的加锁节点会被创建为永久节点(就是lockPrefixKey + orderId),这样zk节点数量将会急速递增。
  2. 临时节点不稳定:一个客户端加锁成功后,可能会因为网络抖动等原因导致Session断开,该客户端创建的临时节点被清理,导致另外一节正在监听的客户端加锁成功,同时进行操作。

使用持久节点创建可重入公平锁

上文提到的临时节点不稳定,父节点为永久节点无法释放问题。我的拙见是:

  1. 使用持久节点代替临时节点:释放锁的时候删除自己创建的加锁节点。
  2. 父节点为永久节点无法释放:可以在每个客户端释放锁的时候进行判断,如果自己是最后一个节点,则删除父节点。
  3. 但是需要考虑的问题:客户端加锁成功后,宕机或重启或者其他极端异常情况,无法删除加锁节点。最后一个加锁节点同样异常,也无法删除父节点。这时,可以给每个锁加过期时间,过期失效。由于zk没有提供过期自动清理,所以可以在第二次访问该节点的时候 先进行判断,判断失效先删除再创建。如果没有第二次访问的节点 可以依靠定时任务进行节点清理。
posted @ 2018-05-01 17:22  做个有梦想的咸鱼  阅读(1618)  评论(1编辑  收藏  举报