完整代码在 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 如何标识读锁还是写锁?有两种简单的方案:在子节点名中标识、在节点的值中标识。如果采用在值中标识,每次子节点列表后,还需要再分别读一下子节点的值,才能判断是读锁还是写锁,会比较耗时。如果在子节点名称中标识,会面临一个问题:在同一个节点中创建的子节点,如果给定的名称不同,追加的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; } } }