zoukankan      html  css  js  c++  java
  • 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没有提供过期自动清理,所以可以在第二次访问该节点的时候 先进行判断,判断失效先删除再创建。如果没有第二次访问的节点 可以依靠定时任务进行节点清理。
  • 相关阅读:
    [leetcode]Length of Last Word
    本周第一天
    本月第一周的第一天
    获取本周的第一天
    PHP实现今天是星期几
    mysql获取相隔时间段的数据
    在mysql中给查询的结果添加序号列
    《岛上书店》
    正则表达式:在大写字母前面加_
    Git忽略文件的三个办法
  • 原文地址:https://www.cnblogs.com/boothsun/p/8976829.html
Copyright © 2011-2022 走看看