基于Zookeeper的分布式锁实现

一、分布式锁使用场景

当我们的系统使用集群部署、在多台服务器上部署的时候,我们java提供的synchronized关键字和Lock接口这种基于单jvm的锁就无法满足我们的需求了。这个时候就需要使用到分布式锁。

二、分布式锁需要具有哪些条件

1、互斥性:和我们本地锁一样互斥的是最基本的,但分布式锁得保证在不同的节点的不同线程互斥。
2、可重入性:同一个节点的同一个线程获得锁后,可再次获得锁。
3、支持所超时,防止死锁。
4、高效、高可用:加锁和解锁要高效,高可用防止分布式锁失效。
5、支持阻塞和非阻塞,和Reentrantlock一样支持lock、trylock、以及trylock(long timeout)。
6、(可选)支持公平锁和非公平锁,公平锁就是按照请求加锁的顺序加锁,非公平锁就是无序的。

三、zookeeper加锁过程

1、zk加锁用到了zk上的一个概念“临时顺序节点”,类似于文件系统,在根节点下一层层的创建目录,如/user/study/,简单来说,就是直接在zk一个路径下,创建一个顺序节点,这个顺序节点有zk内部自行维护的一个自增节点序号,比如xxx-0000001、xxx-0000002。这个节点在会话超时或结束后会自动删除,比如加锁的客户端宕机,zk会自动删除这个节点,这样就有效的防止了客户端没有释放锁而造成死锁。
2、当客户端A创建临时节点后他会在/user/study/目录下查看自己是不是排在第一个的节点,如果是就表示自己是第一个来获取锁的,即可获取到锁。
3、当客户端A获取了锁,还没释放,这个时候客户端B也来获取锁,这个时候它也会创建一个临时顺序节点,然后去检查自己是不是排第一个的,这个时候他发现自己前面已经有一个节点了,那么就无法加锁,但他会在前一个节点上加一个监听器(zk天然支持),去监听这个节点是否被删除等变化,一旦节点被删除,zk就会通知B,你前面的节点删除了,这个时候B判断自己是第一个节点,就可以加锁成功啦。
4、执行业务代码,执行完毕后,删除临时顺序节点,释放锁。

四、使用Curator框架实现zk分布式锁

安装zookeeper
启动本地zookeeper :zkServer start

在这里插入图片描述

在图中路径下/usr/local/etc/zookeeper/ 打开zoo_Sample.cfg文件可以查看zk的配置信息
  • 在这里插入图片描述
  • tickTime:用于定义ZooKeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,即每隔tickTime毫秒就会发送一个心跳。上面设置的是2000毫秒。
  • initLimit:用来设置ZooKeeper服务器集群中连接到Leader的Follower服务器最长能能接受("设定值"tickTime)毫秒时间的心跳。超过该时间后ZooKeeper服务器集群中的Follower服务器还没有返回信息,那么表明该Follower服务器连接失败。上面设置的是(102000)毫秒。该属性是针对ZooKeeper为集群模式或伪集群模式时使用的参数
  • syncLimit:用于设置ZooKeeper服务器集群中Leader服务器与Follower服务器之间发送消息时请求和应答的最大时长,其时间长度为("设定值"tickTime)毫秒。上面设置的是(52000)毫秒。该属性是针对ZooKeeper为集群模式或伪集群模式时使用的参数
  • dataDir:ZooKeeper保存数据的目录,默认情况下,ZooKeeper将写数据的日志文件也保存在这个目录里。注意:该目录不能是/tmp
  • clientPort:客户端连接ZooKeeper服务器的端口,Zookeeper监听该端口并通过该端口接受客户端的访问请求
创建springboot工程,在maven中引入以下依赖
<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>3.4.11</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
			<version>2.12.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>2.12.0</version>
		</dependency>
在application.properties文件中配置zk信息
#重试次数
curator.retryCount=5
#重试间隔时间
curator.elapsedTimeMs = 5000
# zookeeper 地址
curator.connectString=127.0.0.1:2182
# session超时时间
curator.sessionTimeoutMs=60000
# 连接超时时间
curator.connectionTimeoutMs=5000
创建CuratorConfiguration类
package com.lw.sutdy.redisdemo.zookeeper.configuration;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 
 * @date 17:01 2019/7/23
 */
@Configuration
public class CuratorConfiguration {
    @Value("${curator.retryCount}")
    private int retryCount;

    @Value("${curator.elapsedTimeMs}")
    private int elapsedTimeMs;

    @Value("${curator.connectString}")
    private String connectString;

    @Value("${curator.sessionTimeoutMs}")
    private int sessionTimeoutMs;

    @Value("${curator.connectionTimeoutMs}")
    private int connectionTimeoutMs;

    @Bean
    public CuratorFramework curatorFramework() {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(
                connectString,
                sessionTimeoutMs,
                connectionTimeoutMs,
                new RetryNTimes(retryCount, elapsedTimeMs));
        curatorFramework.start();
        return curatorFramework;
    }
}

创建CuratorUtil工具类,使用InterProcessMutex获取分布式锁
package com.lw.sutdy.redisdemo.zookeeper;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 
 * @date 17:31 2019/7/23
 */
@Service
public class CuratorUtil {
    private final String ROOT_PATH_LOCK = "/tryLock/";
    @Autowired
    private CuratorFramework curatorFramework;
    private ConcurrentHashMap<String, InterProcessMutex> lockMap = new ConcurrentHashMap<>();

    public synchronized InterProcessMutex getZkLock(String path){
        InterProcessMutex zkLock = lockMap.get(path);
        if (zkLock == null) {
            InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, ROOT_PATH_LOCK + path);
            lockMap.put(path, interProcessMutex);
            return interProcessMutex;
        }

        return zkLock;
    }

}

写一个测试类测试一下
package com.lw.sutdy.redisdemo;

import com.lw.sutdy.redisdemo.zookeeper.CuratorUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author liuwei
 * @date 19:28 2019/7/23
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class ZKLockTest {
    @Autowired
    private CuratorUtil curatorUtil;
    //创建一个线程池来执行任务
    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 4, 2000,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    private final static CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(20);
    @Test
    public void  testLock() throws Exception {
        MyThread thread = new MyThread(curatorUtil);
        for(int i = 0; i<20;i++) {
            executor.execute(thread);
        }
        //这里使用countdownlatch等待20个子线程执行完毕后,主线程在继续执行,防止子线程还没执行完毕,主线程就执行完了,程序终止。
        COUNT_DOWN_LATCH.await();
        System.out.println("lala");
        executor.shutdown();
    }

    static  class MyThread implements Runnable{
        private int count=100;
        private CuratorUtil curatorUtil;

        public MyThread(CuratorUtil curatorUtil) {
            this.curatorUtil = curatorUtil;
        }

        @Override
        public void run() {
            String path = "test1";
            try {
            //使用InterProcessMutex锁的acquire方法获取锁。
                curatorUtil.getZkLock(path).acquire();
                count--;
                System.out.println(Thread.currentThread().getName()+",结果:"+count);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                //释放锁
                    curatorUtil.getZkLock(path).release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            COUNT_DOWN_LATCH.countDown();

        }
    }


}

执行结果入下,于预期一样。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值