Zookeeper分布式锁学习小结

1.Zookeeper的四种节点

节点概念

1.永久节点

通过create  /path content    来创建,

2.临时节点

通过 create -e /path  content  创建,客户端链接断开后消失,

3.永久序列化节点

create -s  /path  content ,可以在同一个节点中创建多个内容,序列号会递增。

4.临时序列化节点

create -s -e /path content

2.Zookeeper中节点的事件监听:使用都是一次性的

1.节点创建 nodeCreated

stat  -w  /xx 

2.节点删除 nodeDeleted

stat  -w  /xx

3.节点数据变化 nodeDataChanged

get  -w  /xx

4.子节点删除   nodeChildrenChanged

ls  -w /xx

3.实现基本的锁功能:独占排他

我们使用zkClient来连接zookeeper,为了使能一运行项目就连接上zookeeper,我们可以使用spring提供的@Component注解将zkClient注入到容器中,在项目启动后便会调用zkClient的无参构造方法,我们再创建init函数连接zookeeper、使用@PostConstruct注解,让他在无参构造方法执行结束后自动执行。同时记得创建destroy函数释放zookeeper连接,使用@PreDestroy注解使他在spring容器销毁之前执行。


    @PostConstruct
    public void init(){
            //获取链接 项目启动时
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try {
            zooKeeper = new ZooKeeper("192.168.146.130:2181", 30000, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    Event.KeeperState state = watchedEvent.getState();
                    if (Event.KeeperState.SyncConnected.equals(state) &&Event.EventType.None.equals(watchedEvent.getType() )) {
                        System.out.println("获取连接");
                        countDownLatch.countDown();
                    } else if(Event.KeeperState.Closed.equals(state)){
                        System.out.println("关闭连接");
                    }
                }
            });
            countDownLatch.await();
        }catch (Exception e){
        e.printStackTrace();}
    }


    @PreDestroy
    public void destroy(){
        //释放zk链接
        try {
            if (zooKeeper!=null){
                zooKeeper.close();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

类似于之前文章中Redis创建distributeLock实现java中的lock接口,我们也创建一个zkDistributeLock类实现Lock接口

public class ZkDistributedLock implements Lock {

    private ZooKeeper zooKeeper;
    private String lockName;
    private String currentNodePath;
    private static final ThreadLocal<Integer> THREAD_LOCAL=new ThreadLocal<>();
    /**
    根节点要定义死 防止走错路
     */
    private static final String ROOT_PATH = "/locks";
    public ZkDistributedLock(ZooKeeper zooKeeper, String lockName) {
        this.zooKeeper = zooKeeper;
        this.lockName = lockName;
        try {
            if(zooKeeper.exists(ROOT_PATH,false)==null){
                zooKeeper.create(ROOT_PATH,null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
            //判断threadLocal中是否有锁,有锁直接重入(+1)
            Integer flag=THREAD_LOCAL.get();
            if (flag!=null&&flag>0){
                THREAD_LOCAL.set(flag++);
                return true;
            }

            //创建znode节点过程:为了防止zk客户端程序获取锁之后,服务器宕机带来死锁问题,创建临时节点
            //所有请求要求获取锁时,给每一个请求创建临时序列化节点
             currentNodePath = this.zooKeeper.create(ROOT_PATH + "/" + lockName+ "-",
                     null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            //获取前置节点 如果前置节点 则获取锁成功 否则监听前置节点
             String preNode = this.getPreNode();
             if (preNode!=null){
                 //利用闭锁思想实现阻塞功能
                 //获取前置节点是否为空不具有原子性 所以需要再次判断zk中前置节点是否存在
                 CountDownLatch countDownLatch=new CountDownLatch(1);
                 if (this.zooKeeper.exists(ROOT_PATH + "/" + preNode, new Watcher() {
                     @Override
                     public void process(WatchedEvent watchedEvent) {
                        countDownLatch.countDown();
                     }
                 })==null){
                    THREAD_LOCAL.set(1);
                     return true;
                 } 
                 countDownLatch.await();
             }
             THREAD_LOCAL.set(1);
            return true;
        } catch (Exception e) {
            e.printStackTrace();

        }
        return false;
    }

    private String getPreNode() {
        try {
            List<String> children = this.zooKeeper.getChildren(ROOT_PATH, false);
            //如果没有子节点则抛出异常
            if (CollectionUtils.isEmpty(children)){
                throw new IllegalMonitorStateException("非法操作!");
            }
            //获取和当前节点同一资源的锁
            List<String> nodes = children.stream().filter(node -> StringUtils.startsWith(node, lockName + "-")).
                    collect(Collectors.toList());
            if (CollectionUtils.isEmpty(nodes)){
                throw new IllegalMonitorStateException("非法操作!");
            }
            //排好队
            Collections.sort(nodes);
            //获取当前节点下标
            String currentNode = StringUtils.substringAfterLast(currentNodePath, "/");
            int index = Collections.binarySearch(nodes, currentNode);
            if (index < 0){
                throw new IllegalMonitorStateException("非法操作!");
            }
            else if (index > 0){
                //返回前置
                return nodes.get(index-1);
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalMonitorStateException("非法操作!");
        }

    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        try {
            THREAD_LOCAL.set(THREAD_LOCAL.get()-1);
            if (THREAD_LOCAL.get()==0){
                //删除节点
                this.zooKeeper.delete(currentNodePath,-1);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

我们使用java.lang包下的threadLocal来记录可重入锁

 使用zookeeper我们创建的是临时锁,所以我们就不用考虑过期时间,也不用自动续期了。

场景方法实践:

public void deduct(){
        ZkDistributedLock lock = this.zkClient.getLock("lock");
        lock.lock();
        //查询库存
        String stock = redisTemplate.opsForValue().get("stock").toString();

        try {
            //判断库存充足
            if (stock != null && stock.length() != 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    // 扣减库存
                    redisTemplate.opsForValue().set("stock", String.valueOf(--st));

                }
            }
        } finally {
        lock.unlock();
        }

    }

二、Curator

配置:

@Configuration
public class CuratorConfig {

    @Bean
    public CuratorFramework curatorFramework() {
        CuratorFramework curatorFramework = CuratorFrameworkFactory
                .builder()
                .connectString("localhost:2181")
                .sessionTimeoutMs(15000)
                .connectionTimeoutMs(20000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 10))
                .build();
        curatorFramework.start();
        return curatorFramework;
    }
}

模拟场景:

使用InterProcessMutex来申请锁

acquire加锁  release解锁

@Scope(scopeName = "prototype")
@RestController
@RequestMapping("/goods-stock")
public class GoodsStockController {

    @Autowired
    private IGoodsStockService goodsStockService;

    @Autowired
    private CuratorFramework curatorFramework;

    @GetMapping("/{goodsNo}")
    public String purchase(@PathVariable("goodsNo") Integer goodsNo) throws Exception {
        QueryWrapper<GoodsStock> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("goods_no", goodsNo);
        //基于临时有序节点来实现的分布式锁.
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/Locks");
        try {
            lock.acquire(); //抢占分布式锁资源(阻塞的)
            GoodsStock goodsStock = goodsStockService.getOne(queryWrapper);
            Thread.sleep(new Random().nextInt(1000));
            if (goodsStock == null) {
                return "指定商品不存在";
            }
            if (goodsStock.getStock().intValue() < 1) {
                return "库存不够";
            }
            goodsStock.setStock(goodsStock.getStock() - 1);
            boolean res = goodsStockService.updateById(goodsStock);
            if (res) {
                return "抢购书籍:" + goodsNo + "成功";
            }
        } finally {
            lock.release(); //释放锁
        }
        return "抢购失败";
    }

}

但是实践起来没有自己写的zookeeper锁性能高。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值