zk常见的使⽤场景
服务注册与订阅(共⽤节点)
分布式通知(监听znode)
服务命名(znode特性)
数据订阅、发布(watcher)
分布式锁(临时节点)
zk是个数据库,⽂件存储系统,并且有监听通知机制(观察者模式)存⽂件系统,存了节点
zk的节点类型有4⼤类(节点名称唯⼀)
持久化节点(zk断开节点还在)
持久化顺序编号⽬录节点
临时⽬录节点(客户端断开后节点就删除了)
临时⽬录编号⽬录节点
节点创建
create /test laogong // 创建永久节点
create -e /test laogong // 创建临时节点 临时节点就创建成功了,如果我断开这次链接,这个节点⾃然就消失了
create -s /test // 创建顺序节点
create -e -s /test // 创建临时顺序节点
我定义了⼀个库存inventory值为1,还⽤到了⼀个CountDownLatch发令枪,等10个线程都就绪了⼀起
去扣减库存。
是不是就像10台机器⼀起去拿到库存,然后扣减库存了?
所有机器⼀起去拿,发现都是1,那⼤家都认为是⾃⼰抢到了,都做了减⼀的操作,但是等所有⼈都执
⾏完,再去set值的时候,发现其实已经超卖了 ,
是吧,这还不是超卖⼀个两个的问题,超卖7个都有,代码⾥⾯明明判断了库存⼤于0才去减的,怎么回
事开头我说明了。
那怎么解决这个问题?
sync,lock也只能保证你当前机器线程安全,这样分布式访问还是有问题。
上⾯跟⼤家提到的zk的节点就可以解决这个问题。
zk节点有个唯⼀的特性,就是我们创建过这个节点了,你再创建zk是会报错的,那我们就利⽤⼀下他的
唯⼀性去实现⼀下
怎么实现呢?
上⾯不是10个线程嘛?
我们全部去创建,创建成功的第⼀个返回true他就可以继续下⾯的扣减库存操作,后续的节点访问就会
全部报错,扣减失败,我们把它们丢⼀个队列去排队。
那怎么释放锁呢?
删除节点咯,删了再通知其他的⼈过来加锁,依次类推。
我们实现⼀下,zk加锁的场景
是不是,只有第⼀个线程能扣减成功,其他的都失败了。
但是你发现问题没有,你加了锁了,你得释放啊,你不释放后⾯的报错了就不重试了。
那简单,删除锁就释放掉了,Lock在finally⾥⾯unLock,现在我们在finally删除节点。
加锁我们知道创建节点就够了,但是你得实现⼀个阻塞的效果呀,那咋搞?
死循环,递归不断去尝试,直到成功,⼀个伪装的阻塞效果。
怎么知道前⾯的⽼哥删除节点了嗯?
监听节点的删除事件
但是你发现你这样做的问题没?
是的,会出现死锁。
第⼀个仔加锁成功了,在执⾏代码的时候,机器宕机了,那节点是不是就不能删除了?
你要故作沉思,⾃问⾃答,时⽽看看远⽅,时⽽看看⾯试官,假装⾃⼰什么都不知道。
哦我想起来了,创建临时节点就好了,客户端连接⼀断开,别的就可以监听到节点的变化了。
嗯还不错,那你发现还有别的问题没?
好像这种监听机制也不好。
怎么个不好呢?你们可以看到,监听,是所有服务都去监听⼀个节点的,节点的释放也会通知所有的服务器,如果是
900个服务器呢?
这对服务器是很⼤的⼀个挑战,⼀个释放的消息,就好像⼀个牧⽺⽝进⼊了⽺群,⼤家都四散⽽开,随
时可能⼲掉机器,会占⽤服务资源,⽹络带宽等等。
这就是⽺群效应
那怎么解决这个问题?好的,临时顺序节点,可以顺利解决这个问题。
之前说了全部监听⼀个节点问题很⼤,那我们就监听我们的前⼀个节点,因为是顺序的,很容易找到⾃⼰的前后。
和之前监听⼀个永久节点的区别就在于,这⾥每个节点只监听了⾃⼰的前⼀个节点,释放当然也是⼀个
个释放下去,就不会出现⽺群效应了。
你说了这么多,挺不错的,你能说说ZK在分布式锁中实践的⼀些缺点么?
Zk性能上可能并没有缓存服务那么⾼。
因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。
ZK中创建和删除节点只能通过Leader服务器来执⾏,然后将数据同步到所有的Follower机器上。(这⾥
涉及zk集群的知识,我就不展开了,以后zk章节跟⽼公们细聊)
还有么?
使⽤Zookeeper也有可能带来并发问题,只是并不常⻅⽽已。
由于⽹络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这
时候其他客户端就可以获取到分布式锁了。
就可能产⽣并发问题了,这个问题不常⻅是因为zk有重试机制,⼀旦zk集群检测不到客户端的⼼跳,就
会重试,Curator客户端⽀持多种重试策略。
多次重试之后还不⾏的话才会删除临时节点。
Tip:所以,选择⼀个合适的重试策略也⽐较重要,要在锁的粒度和并发之间找⼀个平衡。
有更好的实现么?
基于Redis的分布式锁