zookeeper跟redis一样,也是基于内存的。
官网:
zookeeper是分布式系统的协调服务,提供配置管理、分布式协同、命名的中心化服务以及服务注册发现等。
zookeeper分布式锁的实现原理:
zookeeper实现分布式锁采用其提供的有序临时节点+监听来实现。临时节点只要客户端断开连接就会被删除,正好可以利用这一特性实现锁。
zookeeper是由java语言开发的。
zookeeper中的节点类型:
节点就是用于存储数据的,在zookeeper中,节点是类似于文件系统的目录的结构。
①永久节点。会进行持久化,重启不会丢失。
②临时节点。存储在内存中,不会持久化,重启服务或断开连接数据会丢失。
节点中存储的数据可以进行排序。
当节点中的数据有更新或是节点被删除时,会触发zookeeper的通知机制告知客户端,因为zookeeper一直在监听所有的节点。
领红包具体案例:
【具体业务逻辑是:先查询下数据库,判断用户的红包数据是否为空,是则插入红包数据,否则不让插入】
①引入zkclient依赖:
<!--zookeeper客户端zkclient--> <!-- https://mvnrepository.com/artifact/com.101tec/zkclient --> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.11</version> </dependency>
②代码实现:
接口:
package com.zhangxueliang.demo.springbootdemo.zookeeperLock; public interface ZookeeperLock { //加锁 void lock(); //解锁 void unlock(); }
抽象类:
package com.zhangxueliang.demo.springbootdemo.zookeeperLock; import org.I0Itec.zkclient.ZkClient; import java.util.concurrent.CountDownLatch; /** * @ProjectName springbootdemo_src * @ClassName AbstractZookeeperLock * @Desicription tryLock和waitLock方法使用到了模板方法设计模式 * @Author Zhang Xueliang * @Date 2020/1/13 14:19 * @Version 1.0 **/ public abstract class AbstractZookeeperLock implements ZookeeperLock { protected String lock; protected CountDownLatch countDownLatch;//门闩(倒计数器) protected String zk_address = "127.0.0.1:2181"; protected ZkClient zkClient = new ZkClient(zk_address); /** * 加锁 */ @Override public final void lock() { //尝试获取锁 if (tryLock()){ //已经拿到锁 System.out.println("获取锁成功。。。"); }else { //尝试获取锁未成功,等待获取锁,阻塞,如果此处已经不阻塞了,那么可以继续往下执行代码 waitLock(); //上面的阻塞结束了,那么我可以继续获取锁,递归一下 } } /** * 释放锁 */ @Override public final void unlock() { //关闭连接就解锁了 if(zkClient!=null){ zkClient.close();//此处也可以使用zkClient.delete(临时节点)来实现解锁 System.out.println("解锁成功。。。"); } } //尝试获取锁 protected abstract boolean tryLock(); //等待获取锁 protected abstract void waitLock(); }
锁实现类:
package com.zhangxueliang.demo.springbootdemo.zookeeperLock; import org.I0Itec.zkclient.IZkDataListener; import java.util.concurrent.CountDownLatch; /** * @ProjectName springbootdemo_src * @ClassName ZookeeperDistributedLock * @Desicription TODO * @Author Zhang Xueliang * @Date 2020/1/13 14:32 * @Version 1.0 **/ public class ZookeeperDistributedLock extends AbstractZookeeperLock { public ZookeeperDistributedLock(String lockName){ lock=lockName; } /** * 尝试获取锁 * @return */ @Override protected boolean tryLock() { try { //创建一个临时节点 zkClient.createEphemeral(lock); return true; }catch (Exception e){ return false; } } /** * 等待获取锁 */ @Override protected void waitLock() { //如果已经有线程创建了临时节点,那么其他线程就只能等待,不能再创建该临时节点 //此时我就监听(就是订阅数据的改变)你这个临时节点,如果该节点被删除了我就等待结束,就又可以创建临时节点 //1.订阅数据改变,就是指定监听参数中所指定的那个节点 IZkDataListener listener = new IZkDataListener() { //监听改变 @Override public void handleDataChange(String s, Object o) throws Exception { } //监听删除 @Override public void handleDataDeleted(String s) throws Exception { if (countDownLatch!=null){ countDownLatch.countDown(); } } }; //2.判断锁的节点是否存在 if (zkClient.exists(lock)){ countDownLatch = new CountDownLatch(1); try { //当计数器从1变成0之后,等待就会结束了 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //3.取消订阅 zkClient.unsubscribeDataChanges(lock,listener); } }
zookeeper分布式锁代码测试:
//测试 public static void main(String[] args) { ZookeeperDistributedLock zookeeperDistributedLock = new ZookeeperDistributedLock("/addRedPacket");//一定要带/ 不然节点添加不成功 try{ zookeeperDistributedLock.lock(); //TODO 具体要锁住的业务代码 //查询用户是否已经领过红包 //添加红包给用户 }finally { zookeeperDistributedLock.unlock(); } }
测试时需要将zoo_sample.cfg配置文件中的zookeeper最大连接数调大些:
maxClientCnxns=1000 #默认60
启动两个端口:
配置nginx.conf实现负载均衡
使用jmeter测试时访问的是nginx的80端口:
使用jmeter模拟高并发场景:
相关参数配置--