Day3 zk的watch机制及分布式锁的实现
watch机制
watch类似于观察者模式,watch的实现采用了nio,客户端可以通过(get -w [name])的方式监听一个节点,当该节点触发了create,set,delete事件时,监听该节点的客户端将会收到异步通知
一个连接监听了/test [zk: localhost:2181(CONNECTED) 4] get -w /test0000000002 yyy 另一个连接更改了/test [zk: localhost:2181(CONNECTED) 0] set /test0000000002 222 此时监听了/test的连接会收到通知,并做出反应 [zk: localhost:2181(CONNECTED) 6] WATCHER:: WatchedEvent state:SyncConnected type:NodeDataChanged path:/test0000000002
watch的调用流程 客户端在调用get -w时,服务端返回指定zNode数据,并在对应的哈希表中插入被watch的Znode路径,和监听它的Watcher列表 当该znode触发事件时,服务端会查找哈希表,找到该zNode对应的watcher,并异步通知监听客户端
zookeeper实现分布式锁
读锁:任何人都可以读, 上读锁的前提:之前的锁没有写锁
写锁:只有拿到写锁的人可以写, 上写锁的前提:之前的锁没有读写锁
-
zk实现读锁 用临时顺序节点,节点数据data为"read"表示一个读锁 1.获取zk中序号比该节点小的所有节点 2.判断最小节点是否是读锁 是读锁,上锁成功 不是读锁,上锁失败,为最小节点设置监听,阻塞等待,zk的watch机制会在最小节点发生变化时通知该节点,此时重复第1步 原理:设置读锁的前提是需要之前没有写锁,而在设置写锁前一定是没有读锁的,假如read003为写锁,那么read001和read002就不可能是读锁(会读到过期数据),因此只需要判断read001的锁类型 这种实现读锁的方式有些缺陷,当节点的网络差异导致其在zk的命令执行很慢,就会一直进行这种CAS重试,会导致该节点的资源被白白浪费掉 zk实现写锁 用临时顺序节点,节点数据data为"write"表示一个写锁 1.获取zk中所有的子节点 2.判断自己是否是最小的节点 如果是,则上写锁成功. 如果不是,上锁失败,监听最小节点的变化,同时进入阻塞,直到最小节点的状态变化时,重复第1步 缺陷:假设此时有100个并发,每个请求都去监听最小的节点,当最小节点发生变化时,zk要通知每个节点,而且只有一个节点可以成功上锁,剩下的99个节点还要重新去监听上锁成功的节点,这样会对zk服务端造成很大的压力(羊群效应) 可以调整成链式监听(类似队列)来解决问题 每个节点只需监听自己的上一个节点,大大减少了watch的使用次数
