Zookeeper开源客户端curator 分布式锁

目录

一、概述

二、InterProcessMutex分布式可重入排它锁

三、InterProcessReadWriteLock读写锁

四、总结


一、概述

前面我们已经介绍过zookeeper是分布式锁的实现方案之一,它是利用临时有序节点的特性来保证唯一性的。

Zookeeper分布式锁原理:

  • 获取锁

在zookeeper中,一个分布式锁对应到zookeeper中的一个文件夹,每个客户端线程请求获取分布式锁的时候,就需要在这个文件夹创建一个临时有序节点,有两种情况:

  • 1)、创建的临时顺序节点是文件夹下的第一个节点,则认为是获取分布式锁成功;
  • 2)、创建的临时顺序节点不是文件夹下的第一个节点,则认为当前锁已经被另一个客户端线程获取,此时当前线程进入阻塞等待,这个时候它会监听上一个节点【序号小于当前节点的节点】,等待前一个节点释放锁的时候唤醒当前线程;

阻塞-唤醒逻辑:把文件夹下的节点顺序排一下序,找到当前节点的前一个节点,使用watcher机制监听前面一个节点的变化,当前一个节点被删除时会触发Watch事件,进而唤醒当前阻塞的线程。

如果前一个节点对应的客户端崩溃了,则节点对应的Watch事件也会触发,也会唤醒后一个节点对应的客户端线程,此时仍需要判断当前节点是第一个节点之后才能获取锁,否则继续进入阻塞并监听前面一个节点。

  • 可重入性

当某个客户端线程第一次获取锁成功之后,在JVM内存中的一个ConcurrentMap中存储当前线程对应的锁路径及重入次数,后面同一个线程再次获取锁时,先检查该Map中当前锁是否已被当前线程占用即可,如果已占用,则只需要递增重入次数即可。

注意:只考虑同一个客户端、同一个线程获取同一个分布式锁的可重入性。

  • 释放锁

释放锁时,首先将锁的重入次数减一,然后判断重入次数是否已经为0:

  • 1)如果重入次数为0,则删除当前客户端线程对应的临时顺序节点,删除操作会触发此节点的Watch事件,如果有别的客户端线程正在阻塞等待,则会通过Watch机制唤醒;
  • 2)如果重入次数非0,则说明还未完全释放锁,直接返回即可;

二、InterProcessMutex分布式可重入排它锁

package com.wsh.zookeeper.zookeeperapidemo.curator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CuratorInterProcessMutexDemo {

    private static final Logger logger = LoggerFactory.getLogger(CuratorInterProcessMutexDemo.class);
    private static final String ZOOKEEPER_SERVER_ADDRESS = "192.168.179.128:2181,192.168.179.129:2181,192.168.179.133:2181";

    /**
     * Curator对象
     */
    private static CuratorFramework curatorFramework;

    static {
        //初始化curator对象
        curatorFramework = CuratorFrameworkFactory
                .builder()
                //会话超时时间
                .sessionTimeoutMs(5000)
                //服务器集群地址
                .connectString(ZOOKEEPER_SERVER_ADDRESS)
                //重试策略
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        //开启客户端
        curatorFramework.start();
    }


    private static void getLock(InterProcessLock interProcessLock) {
        try {
            interProcessLock.acquire();
            logger.info("线程:" + Thread.currentThread().getName() + "获取锁成功......");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                interProcessLock.release();
                logger.info("线程:" + Thread.currentThread().getName() + "释放锁成功......");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //分布式可重入排它锁
        InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, "/distributeLock");
        new Thread(() -> getLock(interProcessMutex), "T1").start();
        new Thread(() -> getLock(interProcessMutex), "T2").start();
        new Thread(() -> getLock(interProcessMutex), "T3").start();
        new Thread(() -> getLock(interProcessMutex), "T4").start();
    }

}

运行程序,观察后端日志:

14:41:37.532 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T2获取锁成功......
14:41:37.538 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T2释放锁成功......

14:41:37.543 [T4] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T4获取锁成功......
14:41:37.554 [T4] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T4释放锁成功......

14:41:37.555 [T3] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T3获取锁成功......
14:41:37.559 [T3] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T3释放锁成功......

14:41:37.560 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T1获取锁成功......
14:41:37.564 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessMutexDemo - 线程:T1释放锁成功......

如上可见,多个线程争夺一个共享锁的时候,相互之间是互斥的。流程分析如下图所示:

三、InterProcessReadWriteLock读写锁

curator实现了跨JVM的可重入读写互斥锁。读写锁包含了读锁、写锁两个,它们的互斥关系如下:

  • 1)读写互斥;
  • 2)写写互斥;
  • 3)读读不互斥;

案例:读写互斥

package com.wsh.zookeeper.zookeeperapidemo.curator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CuratorInterProcessReadWriteLockDemo {

    private static final Logger logger = LoggerFactory.getLogger(CuratorInterProcessReadWriteLockDemo.class);
    private static final String ZOOKEEPER_SERVER_ADDRESS = "192.168.179.128:2181,192.168.179.129:2181,192.168.179.133:2181";

    /**
     * Curator对象
     */
    private static CuratorFramework curatorFramework;
    /**
     * curator读锁对象
     */
    private static InterProcessMutex readLock;
    /**
     * curator写锁对象
     */
    private static InterProcessMutex writeLock;

    static {
        //初始化curator对象
        curatorFramework = CuratorFrameworkFactory
                .builder()
                //会话超时时间
                .sessionTimeoutMs(5000)
                //服务器集群地址
                .connectString(ZOOKEEPER_SERVER_ADDRESS)
                //重试策略
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        //开启客户端
        curatorFramework.start();
        InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(curatorFramework, "/distribute-lock");
        readLock = interProcessReadWriteLock.readLock();
        writeLock = interProcessReadWriteLock.writeLock();
    }

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                readLock.acquire();
                logger.info("线程:" + Thread.currentThread().getName() + "获取读锁...");
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    readLock.release();
                    logger.info("线程:" + Thread.currentThread().getName() + "释放读锁...");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "T1").start();

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                writeLock.acquire();
                logger.info("线程:" + Thread.currentThread().getName() + "获取写锁...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    writeLock.release();
                    logger.info("线程:" + Thread.currentThread().getName() + "释放写锁...");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "T2").start();
    }

}
15:22:11.460 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T1获取读锁...
15:22:14.468 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T1释放读锁...

15:22:14.470 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T2获取写锁...
15:22:14.473 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T2释放写锁...

由上日志可见,读锁先获取到锁资源后,写锁必须等待锁释放之后,才能获取锁。

案例:读读不互斥

package com.wsh.zookeeper.zookeeperapidemo.curator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CuratorInterProcessReadWriteLockDemo {

    private static final Logger logger = LoggerFactory.getLogger(CuratorInterProcessReadWriteLockDemo.class);
    private static final String ZOOKEEPER_SERVER_ADDRESS = "192.168.179.128:2181,192.168.179.129:2181,192.168.179.133:2181";

    /**
     * Curator对象
     */
    private static CuratorFramework curatorFramework;
    /**
     * curator读锁对象
     */
    private static InterProcessMutex readLock;
    /**
     * curator写锁对象
     */
    private static InterProcessMutex writeLock;

    static {
        //初始化curator对象
        curatorFramework = CuratorFrameworkFactory
                .builder()
                //会话超时时间
                .sessionTimeoutMs(5000)
                //服务器集群地址
                .connectString(ZOOKEEPER_SERVER_ADDRESS)
                //重试策略
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        //开启客户端
        curatorFramework.start();
        InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(curatorFramework, "/distribute-lock");
        readLock = interProcessReadWriteLock.readLock();
        writeLock = interProcessReadWriteLock.writeLock();
    }

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                readLock.acquire();
                logger.info("线程:" + Thread.currentThread().getName() + "获取读锁...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    readLock.release();
                    logger.info("线程:" + Thread.currentThread().getName() + "释放读锁...");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "T1").start();

        new Thread(() -> {
            try {
                readLock.acquire();
                logger.info("线程:" + Thread.currentThread().getName() + "获取读锁...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    readLock.release();
                    logger.info("线程:" + Thread.currentThread().getName() + "释放读锁...");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "T2").start();
    }

}
15:32:30.347 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T2获取读锁...
15:32:30.347 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T1获取读锁...

15:32:30.354 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T1释放读锁...
15:32:30.354 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T2释放读锁...

 由上面的日志可见,两个读线程之间获取锁资源的话,是可以同时获取到锁的,并不互斥。

案例:写写互斥

package com.wsh.zookeeper.zookeeperapidemo.curator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CuratorInterProcessReadWriteLockDemo {

    private static final Logger logger = LoggerFactory.getLogger(CuratorInterProcessReadWriteLockDemo.class);
    private static final String ZOOKEEPER_SERVER_ADDRESS = "192.168.179.128:2181,192.168.179.129:2181,192.168.179.133:2181";

    /**
     * Curator对象
     */
    private static CuratorFramework curatorFramework;
    /**
     * curator读锁对象
     */
    private static InterProcessMutex readLock;
    /**
     * curator写锁对象
     */
    private static InterProcessMutex writeLock;

    static {
        //初始化curator对象
        curatorFramework = CuratorFrameworkFactory
                .builder()
                //会话超时时间
                .sessionTimeoutMs(5000)
                //服务器集群地址
                .connectString(ZOOKEEPER_SERVER_ADDRESS)
                //重试策略
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        //开启客户端
        curatorFramework.start();
        InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(curatorFramework, "/distribute-lock");
        readLock = interProcessReadWriteLock.readLock();
        writeLock = interProcessReadWriteLock.writeLock();
    }

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                writeLock.acquire();
                logger.info("线程:" + Thread.currentThread().getName() + "获取写锁...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    writeLock.release();
                    logger.info("线程:" + Thread.currentThread().getName() + "释放写锁...");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "T1").start();

        new Thread(() -> {
            try {
                writeLock.acquire();
                logger.info("线程:" + Thread.currentThread().getName() + "获取写锁...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    writeLock.release();
                    logger.info("线程:" + Thread.currentThread().getName() + "释放写锁...");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "T2").start();
    }

}
15:33:28.701 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T1获取写锁...
15:33:28.706 [T1] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T1释放写锁...

15:33:28.711 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T2获取写锁...
15:33:28.717 [T2] INFO com.wsh.zookeeper.zookeeperapidemo.curator.CuratorInterProcessReadWriteLockDemo - 线程:T2释放写锁...

如上日志可见,两个线程同时尝试获取锁资源的时候,某个时刻只会有一个线程能获取成功,其他线程必须等待释放锁后才能尝试去获取锁。

四、总结

本文主要总结了curator分布式锁的实现原理,以及通过案例详解介绍了curator的可重入排它锁以及读写锁的使用,在分布式系统开发中,实现分布式锁的方式有很多种,如数据库方式、redis、zookeeper等,具体根据业务场景选择合适的即可。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值