ZooKeeper实现读写锁

  • 时间:
  • 浏览:1
  • 来源:幸运快3_快3代理_幸运快3代理

在上一篇文章,或多或少人假若实现了分布式锁。今天更进一步,在分布式锁的基础之上,实现读写锁。

完全代码在 https://github.com/SeemSilly/codestory/tree/master/research-zoo-keeper

1 读写锁的概念

参考维基百科的条目: https://zh.wikipedia.org/wiki/读写锁

读写锁是计算机多线程 的并发控制的并与非 同步机制,用于出理 读写间题,读操作可并发重入,写操作是互斥的。 读写锁有多种读写权限的优先级策略,还不需要 设计为读优先、写优先或不指定优先级。

  • 读优先:允许最大并发的读操作,但假若会饿死写操作;假若写操作都要在这样任何读操作的之前 才不需要 执行。
  • 写优先:假若排队队列含有写操作,读操作就都要守候;
  • 不指定优先级:对读操作和写操作不做任何优先级的假设

不指定优先级的策略,最适合使用ZooKeeper的子节点模式来实现,今天就来尝试相似 策略。

2 锁设计

同前面介绍的普通分布式锁,也使用子节点模式实现。先用容器模式(CreateMode.CONTAINER)创建唯一的锁节点,每个锁客户端在锁节点下使用临时循序模式(CreateMode. SEQUENTIAL)创建子节点。哪几个子节点会自动在名称中间追加10位数字。

2.1 如保标识读锁还是写锁?

有并与非 简单的方案:在子节点名中标识、在节点的值中标识。假若采用在值中标识,每次子节点列表后,还都要再分别读一下子节点的值,不需要 判断是读锁还是写锁,会比较耗时。假若在子节点名称中标识,会面临一另1个间题:在同一另1个节点中创建的子节点,假若给定的名称不同,追加的10位数字是与非 仍然是递归的?

写个测试用例验证一下。

public class SequentialTest extends TestBase {
  @Test
  public void testSequential() throws Exception {
    String rootNodeName = "/container-" + System.currentTimeMillis();
    ZooKeeperBase zooKeeper = new ZooKeeperBase(address);
    zooKeeper.createRootNode(rootNodeName, CreateMode.CONTAINER);

    Random random = new SecureRandom();
    long lastNumber = -1L;
    String[] prefixs = new String[] {"/a", "/b", "/c", "/d", "/e", "/f", "/g"};
    for (int i = 0; i < 10; i++) {
      int index = random.nextInt(prefixs.length);
      String childNodeName = rootNodeName + prefixs[index];
      String fullNodeName = zooKeeper.getZooKeeper().create(childNodeName, new byte[0],
          ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
      long number = Long.parseLong(fullNodeName.substring(childNodeName.length()));
      assert number == lastNumber + 1;
      lastNumber = number;
    }
  }
}

测试用例通过,说明在同一另1个Container中创建的子节点,不论提供的节点名是哪几个,后续追加的10位数字一定会 顺序递增的。只是,就还不需要 使用节点名来区分读锁和写锁。

2.2   类设计

介绍分布式锁的之前 ,假若创建了阻塞锁 ChildrenBlockingLock,读写锁正好还不需要 基于相似 类做重载。

 

2.3   获取锁的逻辑

写锁是一另1个独占锁,逻辑跟普通分布式锁相同,假若它之前 有锁就都要守候。或多或少或多或少,完全沿用阻塞锁的逻辑即可。

读锁允许并发,它之前 还不需要 有任意读锁,但只能有写锁。或多或少或多或少只都要判断有这样写锁即可。

3      关键代码

3.1   ChildrenNodeLock.java

相似 类,主只是增加了一另1个获取排序后子节点列表的法律法律依据 ,只是方便实现读写锁的代码。当然,相似 操作会增加或多或少耗时,假若子节点数量太满,假若不适用。

首先定义一另1个函数,用来返回子节点的前缀

/** 子节点的前缀,缺省是element,子类还不需要



重载 */
protected String getChildPrefix() {
  return "element";
}

假若定义一另1个内部人员类,子节点排序一定会用到

/** 子节点名称比较 */
private class StringCompare implements Comparator<String> {
  @Override
  public int compare(String string1, String string2) {
    return string1.substring(string1.length() - 10)
        .compareTo(string2.substring(string2.length() - 10));
  }
}

最后实现子节点排序法律法律依据 ,用于代替 getChildren 函数

/** 获取排好序的子节点列表 */
final public List<String> getOrderedChildren(String path, boolean watch)
    throws KeeperException, InterruptedException {
  List<String> children = getZooKeeper().getChildren(path, watch);
  Collections.sort(children, new StringCompare());
  return children;
}

3.2   ChildrenBlockingLock.java

在多客户端随机测试时,突然出显多线程 卡死的情形,无法正常退出。经过上加日志跟踪,发现WatchedEvent假若会丢失,也假若会发送给并一定会 注册事件的ZooKeeper客户端。在网上搜索,发现或多或少或多或少人也碰到相似间题。

简单修改了一下ChildrenBlockingLock#isLockSuccess守候信号的代码,从无参数的死等变成设置一定超时时间守候。关键代码如下

protected boolean isLockSuccess() {
  boolean lockSuccess;
  try {
    while (true) {
      String prevElementName = getPrevElementName();
      if (prevElementName == null) {
        log.trace("{} 这样更靠前的子节点,加锁成功", elementNodeName);
        lockSuccess = true;
        break;
      } else {
        // 有更小的节点,说明当前节点没抢到锁,注册前一另1个节点的监听。
        log.trace("{} 监控 {} 的事件", elementNodeName, prevElementName);
        getZooKeeper().exists(this.guidNodeName + "/" + prevElementName, true);
        synchronized (mutex) {
          // 守候最多一秒
          mutex.wait(30000);
          log.trace("{} 监控的 {} 有子节点变化", elementNodeName, guidNodeName);
        }
      }
    }
  } catch (KeeperException e) {
    lockSuccess = false;
  } catch (InterruptedException e) {
    lockSuccess = false;
  }
  return lockSuccess;
}

3.3   写锁 ZooKeeperWriteLock.java

代码基本是沿用父类,只都要重载getChildPrefix()法律法律依据 ,

/** 返回写锁的前缀 */
protected String getChildPrefix() {
  return "w-lock-";
}

3.4   读锁 ZooKeeperReadLock.java

同写锁相比,除了重载getChildPrefix()法律法律依据 ,还重载了getPrevElementName()用来查找最近一另1个写锁。

/** 返回读锁的前缀 */
protected String getChildPrefix() {
  return "r-lock-";
}

/** 是写锁 */
private boolean isWriteLock(String elementName) {
  return elementName.startsWith(ZooKeeperWriteLock.FLAG);
}

/** 读取前一另1个写锁 */
protected String getPrevElementName() throws KeeperException, InterruptedException {
  List<String> elementNames = super.getOrderedChildren(this.guidNodeName, false);
  super.traceOrderedChildren(this.guidNodeName, elementNames);
  String prevWriteElementName = null;
  for (String oneElementName : elementNames) {
    if (this.elementNodeFullName.endsWith(oneElementName)) {
      // 假若到了当前节点
      break;
    }
    if (isWriteLock(oneElementName)) {
      prevWriteElementName = oneElementName;
    }
  }
  return prevWriteElementName;
}

4      测试用例

测试用例没想到好的判断法律法律依据 ,真难使用assert判断结果,假若做了复杂性,根据日志输出,靠人眼判断是与非 正确。

4.1   测试线程 类

分别为都锁和写锁构建了另1个内部人员类

/** 写锁线程

 */
class WriteLockClient extends Thread {
  ZooKeeperWriteLock writeLock;

  public WriteLockClient() {
    try {
      this.writeLock = new ZooKeeperWriteLock(address);
    } catch (IOException e) {
    }
  }

  public void run() {
    writeLock.lock(guidNodeName, this.getName());
    try {
      Thread.sleep(30000 + random.nextInt(20) * 3000);
    } catch (InterruptedException e) {
    }
    writeLock.release(guidNodeName, this.getName());
  }
}

/** 读锁线程

 */
class ReadLockClient extends Thread {
  ZooKeeperReadLock readLock;

  public ReadLockClient() {
    try {
      this.readLock = new ZooKeeperReadLock(address);
    } catch (IOException e) {
    }
  }

public void run() { readLock.lock(guidNodeName, this.getName()); try { Thread.sleep(30000 + random.nextInt(20) * 3000); } catch (InterruptedException e) { } readLock.release(guidNodeName, this.getName()); try { readLock.getZooKeeper().close(); } catch (InterruptedException e) { } } }

4.2   读-读锁测试

代码

@Test
public void testReadRead() throws IOException, InterruptedException {
  ReadLockClient readLock1 = new ReadLockClient();
  ReadLockClient readLock2 = new ReadLockClient();
  readLock1.start();
  readLock2.start();
  readLock1.join();
  readLock2.join();
}

测试结果还不需要 看得人,另1个读锁并发执行

22:18.861 [Thread-2 INFO] r-lock-0000000000 get read lock : true
22:18.865 [Thread-1 INFO] r-lock-0000000001 get read lock : true
22:20.065 [Thread-2 INFO] r-lock-0000000000 release read lock
22:21.366 [Thread-1 INFO] r-lock-0000000001 release read lock

4.3   读-写锁测试

代码

@Test
public void testReadWrite() throws IOException, InterruptedException {
  ReadLockClient readLock1 = new ReadLockClient();
  WriteLockClient writeLock1 = new WriteLockClient();
  readLock1.start();
  Thread.sleep(3000);
  writeLock1.start();
  readLock1.join();
  writeLock1.join();
}

测试结果还不需要 看得人,首先获取读锁,释放之前 才获取到写锁。

27:40.30000 [Thread-1 INFO] r-lock-0000000000 get read lock : true
27:43.310 [Thread-1 INFO] r-lock-0000000000 release read lock
27:43.423 [Thread-2 INFO] w-lock-0000000001 get write lock : true
27:44.423 [Thread-2 INFO] w-lock-0000000001 release write lock

4.4   写-读锁测试

代码

@Test
public void testWriteRead() throws IOException, InterruptedException {
  ReadLockClient readLock1 = new ReadLockClient();
  WriteLockClient writeLock1 = new WriteLockClient();
  writeLock1.start();
  Thread.sleep(3000);
  readLock1.start();
  writeLock1.join();
  readLock1.join();
}

测试结果还不需要 看得人,首先获取写锁,释放之前 才获取到读锁。

29:17.661 [Thread-2 INFO] w-lock-0000000000 get write lock : true
29:19.966 [Thread-2 INFO] w-lock-0000000000 release write lock
29:19.976 [Thread-1 INFO] r-lock-0000000001 get read lock : true
29:22.476 [Thread-1 INFO] r-lock-0000000001 release read lock

4.5   多客户端随机读写锁测试

测试代码

@Test
public void testRandomReadWriteLock() throws IOException, InterruptedException {
  int threadCount = 20;
  Thread[] lockThreads = new Thread[threadCount];
  for (int i = 0; i < threadCount; i++) {
    // 一定概率是写锁
    boolean writeLock = random.nextInt(5) == 0;
    if (writeLock) {
      lockThreads[i] = new WriteLockClient();
    } else {
      lockThreads[i] = new ReadLockClient();
    }
    lockThreads[i].start();
  }
 

  for (int i = 0; i < threadCount; i++) {
    lockThreads[i].join();
  }
}

测试结果还不需要 看出,假若连续多个读锁会并发执行。为了方便查看,我上加了或多或少横线分隔。

300:31.317 [Thread-1 INFO] w-lock-0000000000 get write lock : true
300:32.824 [Thread-1 INFO] w-lock-0000000000 release write lock
------------------------------------------------------------------
300:32.834 [Thread-17 INFO] r-lock-0000000004 get read lock : true
300:32.835 [Thread-19 INFO] r-lock-0000000002 get read lock : true
300:32.835 [Thread-20 INFO] r-lock-0000000001 get read lock : true
300:32.836 [Thread-18 INFO] r-lock-0000000003 get read lock : true
300:34.135 [Thread-20 INFO] r-lock-0000000001 release read lock
300:34.634 [Thread-17 INFO] r-lock-0000000004 release read lock
300:34.935 [Thread-19 INFO] r-lock-0000000002 release read lock
300:35.036 [Thread-18 INFO] r-lock-0000000003 release read lock
------------------------------------------------------------------
300:35.053 [Thread-16 INFO] w-lock-0000000005 get write lock : true
300:36.154 [Thread-16 INFO] w-lock-0000000005 release write lock
------------------------------------------------------------------
300:36.1300 [Thread-14 INFO] r-lock-0000000007 get read lock : true
300:36.1300 [Thread-15 INFO] r-lock-0000000006 get read lock : true
300:38.1300 [Thread-14 INFO] r-lock-0000000007 release read lock
300:38.661 [Thread-15 INFO] r-lock-0000000006 release read lock
------------------------------------------------------------------
300:38.669 [Thread-13 INFO] w-lock-0000000008 get write lock : true
300:39.969 [Thread-13 INFO] w-lock-0000000008 release write lock
------------------------------------------------------------------
300:39.976 [Thread-12 INFO] r-lock-0000000009 get read lock : true
300:39.977 [Thread-8 INFO] r-lock-0000000014 get read lock : true
300:39.977 [Thread-6 INFO] r-lock-0000000015 get read lock : true
300:39.984 [Thread-10 INFO] r-lock-0000000011 get read lock : true
300:39.985 [Thread-3 INFO] r-lock-0000000018 get read lock : true
300:39.984 [Thread-7 INFO] r-lock-0000000013 get read lock : true
300:39.984 [Thread-11 INFO] r-lock-0000000010 get read lock : true
300:39.983 [Thread-9 INFO] r-lock-0000000012 get read lock : true
300:39.983 [Thread-2 INFO] r-lock-0000000019 get read lock : true
300:39.982 [Thread-5 INFO] r-lock-0000000016 get read lock : true
300:39.986 [Thread-4 INFO] r-lock-0000000017 get read lock : true
300:40.986 [Thread-3 INFO] r-lock-0000000018 release read lock
300:41.086 [Thread-2 INFO] r-lock-0000000019 release read lock
300:41.285 [Thread-6 INFO] r-lock-0000000015 release read lock
300:41.576 [Thread-12 INFO] r-lock-0000000009 release read lock
300:42.185 [Thread-10 INFO] r-lock-0000000011 release read lock
300:42.186 [Thread-5 INFO] r-lock-0000000016 release read lock
300:42.187 [Thread-11 INFO] r-lock-0000000010 release read lock
300:42.286 [Thread-9 INFO] r-lock-0000000012 release read lock
300:42.586 [Thread-7 INFO] r-lock-0000000013 release read lock
300:42.677 [Thread-8 INFO] r-lock-0000000014 release read lock
300:42.887 [Thread-4 INFO] r-lock-0000000017 release read lock

猜你喜欢

TechWeb.com.cn

先是7nm产能供不应求,5nm产能也被预定了,现在就连前两代的工艺也面临缺货了,16/12nm产能的交付期也延长了。针对6家会员退出Libra(天秤币)学精,美国财政部长史蒂芬

2019-10-14

Alphabet旗下Sidewalk与多伦多建智慧城市 耗资将超10亿美元

【TechWeb报道】10月18日消息,据国外媒体报道,谷歌母公司Alphabet旗下的智慧教育城市子公司SidewalkLabs,日前回应了将与加拿大城市多伦多一块儿打造一片

2019-10-14

D. Meadowcroft数据,D. Meadowcroft新闻,D. Meadowcroft视频,D. Meadowcroft身价

首页新闻视频直播数据APP懂球号广告企业媒体合作D.MeadowcroftD.Meadowcroft俱乐部:维克诺域治国籍:英格兰身高:193CM位置:后卫年龄:34岁体重:7

2019-10-14

M. Assoumani数据,M. Assoumani新闻,M. Assoumani视频,M. Assoumani身价

首页新闻视频直播数据APP懂球号广告媒体合作M.AssoumaniM.Assoumani俱乐部:尼斯B队国籍:法国身高:CM位置:中场年龄:体重:KG号码:号生日:惯用脚:比赛

2019-10-14

王者荣耀2018年度评选活动之王者最会玩投票地址

王者荣耀2018年度评选活动现在日后开启了投票活动,玩家可不须要为当时人最喜欢的精彩瞬间进行投票了,下面我们都都我们都都来看下这次活动的投票地址吧。本次的活动一共有六个奖项评选

2019-10-14