@boothsun
2018-04-02T02:13:59.000000Z
字数 2340
阅读 1644
ZK
公平锁:创建有序节点,判断本节点是不是序号最小的节点(第一个节点),若是,则获取锁;若不是,则监听比该节点小的那个节点的删除事件。
非公平锁:直接尝试在指定path下创建节点,创建成功,则说明该节点抢到锁了。如果创建失败,则监听抢到锁的删除事件,或者sleep一段时间或再次重试。
可重入:使用ThreadLocal记录加锁客户端的唯一标识。重复时先从ThreadLocal获取,获取到,这认为加锁成功,直接返回。
使用瞬时节点的好处是当Session失效时,该节点将被清理,从而避免使用持久节点加锁成功后,抛异常或宕机或服务器重启等原因造成的锁无法释放问题。
// 假设需要加锁的订单Idprivate 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*/@Testpublic void curator() throws Exception {for (int i = 0 ; i < 100 ; i++) {Thread currentThread = new Thread(() -> {// 创建ConnectionCuratorFramework 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断开,该客户端创建的临时节点被清理,导致另外一节正在监听的客户端加锁成功,同时进行操作。
上文提到的临时节点不稳定,父节点为永久节点无法释放问题。我的拙见是: