[关闭]
@boothsun 2018-04-02T10:13:59.000000Z 字数 2340 阅读 1498

ZK分布式锁(未完!!!)

ZK


实现思路

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

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

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

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

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

使用curator实现

  1. // 假设需要加锁的订单Id
  2. private static String orderId = "157146671409578219";
  3. // 工程名
  4. private static String appName = "trade_";
  5. // 此次加锁业务处理逻辑描述
  6. private static String operatorDesc = "updateTrade_";
  7. // 部门,每个部门可以有自己的zk空间目录
  8. private static String department = "zfpt" ;
  9. // 锁前缀 应该根据业务 具有唯一性
  10. private static String lockPrefixKey = "/" + appName + operatorDesc ;
  11. /**
  12. * 每个线程 创建自己的Connection ,创建自己的Session
  13. */
  14. @Test
  15. public void curator() throws Exception {
  16. for (int i = 0 ; i < 100 ; i++) {
  17. Thread currentThread = new Thread(() -> {
  18. // 创建Connection
  19. CuratorFramework client = CuratorFrameworkFactory.builder()
  20. .connectString("master:2181,slave1:2181,slave2:2181")
  21. .retryPolicy(new RetryOneTime(1000)) //重试策略
  22. .namespace(department) // 可以设置自己部门缩写
  23. .build();
  24. client.start();
  25. // 模拟对同一个订单加锁
  26. InterProcessMutex lock = new InterProcessMutex(client, lockPrefixKey + orderId);
  27. try {
  28. // 一直尝试加锁 直到锁可用。 有点像synchronized
  29. // lock.acquire();
  30. if(lock.acquire(1, TimeUnit.SECONDS)) {
  31. System.out.println(Thread.currentThread().getName() + " 抢到锁");
  32. } else {
  33. System.out.println(Thread.currentThread().getName() + "超时没有抢到锁");
  34. }
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. } finally {
  38. try {
  39. // 如果当前线程获得到锁,则释放锁
  40. if(lock.isAcquiredInThisProcess()) {
  41. System.out.println(Thread.currentThread().getName() + " 释放锁");
  42. lock.release();
  43. } else {
  44. System.out.println(Thread.currentThread().getName() + " 没有抢到锁,故没有释放锁");
  45. }
  46. } catch (Exception e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. });
  51. currentThread.setName("Lock【" + i + "】");
  52. currentThread.start();
  53. }
  54. Thread.sleep(Integer.MAX_VALUE);
  55. }

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

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

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

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

  1. 使用持久节点代替临时节点:释放锁的时候删除自己创建的加锁节点。
  2. 父节点为永久节点无法释放:可以在每个客户端释放锁的时候进行判断,如果自己是最后一个节点,则删除父节点。
  3. 但是需要考虑的问题:客户端加锁成功后,宕机或重启或者其他极端异常情况,无法删除加锁节点。最后一个加锁节点同样异常,也无法删除父节点。这时,可以给每个锁加过期时间,过期失效。由于zk没有提供过期自动清理,所以可以在第二次访问该节点的时候 先进行判断,判断失效先删除再创建。如果没有第二次访问的节点 可以依靠定时任务进行节点清理。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注