springboot基于zookeeper实现分布式锁

参考链接:
https://blog.csdn.net/wuzhiwei549/article/details/80692278
https://blog.52itstyle.vip/archives/3202/

先说明一下zookeeper实现分布式锁的原理,这里参看csdn一篇非常好的文章,如果不想看原理直接跳过看后面的具体代码实现
单机应用架构中,秒杀案例使用ReentrantLcok或者synchronized来达到秒杀商品互斥的目的。然而在分布式系统中,会存在多台机器并行去实现同一个功能。也就是说,在多进程中,如果还使用以上JDK提供的进程锁,来并发访问数据库资源就可能会出现商品超卖的情况。因此,需要我们来实现自己的分布式锁。

实现一个分布式锁应该具备的特性:

高可用、高性能的获取锁与释放锁
在分布式系统环境下,一个方法或者变量同一时间只能被一个线程操作
具备锁失效机制,网络中断或宕机无法释放锁时,锁必须被删除,防止死锁
具备阻塞锁特性,即没有获取到锁,则继续等待获取锁
具备非阻塞锁特性,即没有获取到锁,则直接返回获取锁失败
具备可重入特性,一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁
在之前的秒杀案例中,我们曾介绍过关于分布式锁几种实现方式:

基于数据库实现分布式锁
基于 Redis 实现分布式锁
基于 Zookeeper 实现分布式锁
前两种对于分布式生产环境来说并不是特别推荐,高并发下数据库锁性能太差,Redis在锁时间限制和缓存一致性存在一定问题。这里我们重点介绍一下 Zookeeper 如何实现分布式锁。
springboot基于zookeeper实现分布式锁_第1张图片

Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Znode。

Znode分为四种类型:

1.持久节点 (PERSISTENT)

默认的节点类型。创建节点的客户端与zookeeper断开连接后,该节点依旧存在 。

2.持久节点顺序节点(PERSISTENT_SEQUENTIAL)

所谓顺序节点,就是在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号:
springboot基于zookeeper实现分布式锁_第2张图片

3.临时节点(EPHEMERAL)

和持久节点相反,当创建节点的客户端与zookeeper断开连接后,临时节点会被删除:

springboot基于zookeeper实现分布式锁_第3张图片

springboot基于zookeeper实现分布式锁_第4张图片
springboot基于zookeeper实现分布式锁_第5张图片

4.临时顺序节点(EPHEMERAL_SEQUENTIAL)

顾名思义,临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与zookeeper断开连接后,临时节点会被删除。

Zookeeper分布式锁的原理

Zookeeper分布式锁恰恰应用了临时顺序节点。具体如何实现呢?让我们来看一看详细步骤:

获取锁

首先,在Zookeeper当中创建一个持久节点ParentLock。当第一个客户端想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点 Lock1。
springboot基于zookeeper实现分布式锁_第6张图片

之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
springboot基于zookeeper实现分布式锁_第7张图片

这时候,如果再有一个客户端 Client2 前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock2。

springboot基于zookeeper实现分布式锁_第8张图片

Client2查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。

于是,Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。
springboot基于zookeeper实现分布式锁_第9张图片

这时候,如果又有一个客户端Client3前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock3。

springboot基于zookeeper实现分布式锁_第10张图片

Client3查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock3是不是顺序最靠前的一个,结果同样发现节点Lock3并不是最小的。

于是,Client3向排序仅比它靠前的节点Lock2注册Watcher,用于监听Lock2节点是否存在。这意味着Client3同样抢锁失败,进入了等待状态。

springboot基于zookeeper实现分布式锁_第11张图片

这样一来,Client1得到了锁,Client2监听了Lock1,Client3监听了Lock2。这恰恰形成了一个等待队列,很像是Java当中ReentrantLock所依赖的

释放锁

释放锁分为两种情况:

1.任务完成,客户端显示释放

当任务完成时,Client1会显示调用删除节点Lock1的指令。
springboot基于zookeeper实现分布式锁_第12张图片

2.任务执行过程中,客户端崩溃

获得锁的Client1在任务执行过程中,如果Duang的一声崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点Lock1会随之自动删除。

springboot基于zookeeper实现分布式锁_第13张图片

由于Client2一直监听着Lock1的存在状态,当Lock1节点被删除,Client2会立刻收到通知。这时候Client2会再次查询ParentLock下面的所有节点,确认自己创建的节点Lock2是不是目前最小的节点。如果是最小,则Client2顺理成章获得了锁。

springboot基于zookeeper实现分布式锁_第14张图片

同理,如果Client2也因为任务完成或者节点崩溃而删除了节点Lock2,那么Client3就会接到通知。

springboot基于zookeeper实现分布式锁_第15张图片

最终,Client3成功得到了锁。

Springboot实现zookeeper分布式锁
这里使用第三方库Curator来实现对zookeeper的操作
Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等等。

Curator的maven如下

 <!-- zookeeper 分布式锁、注意zookeeper版本  这里对应的是3.4.6   对应版本查询网址https://curator.apache.org/zk-compatibility.html-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.10.0</version>
        </dependency>

Zookeeper锁的帮助工具类ZkLockUtil


import java.util.concurrent.TimeUnit;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

/**
 * zookeeper 分布式锁
 * @author 科帮网 By https://blog.52itstyle.com
 */
public class ZkLockUtil{

    private static String address = "123.229.94.231:2181";

    public static CuratorFramework client;

    static{
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        client = CuratorFrameworkFactory.newClient(address, retryPolicy);
        client.start();
    }
    /**
     * 私有的默认构造子,保证外界无法直接实例化
     */
    private ZkLockUtil(){};
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
     * 针对一件商品实现,多件商品同时秒杀建议实现一个map
     */
    private static class SingletonHolder{
        /**
         * 静态初始化器,由JVM来保证线程安全
         * 参考:http://ifeve.com/zookeeper-lock/
         * 这里建议 new 一个
         */
        private  static InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
    }
    public static InterProcessMutex getMutex(){
        return SingletonHolder.mutex;
    }
    //获得了锁
    public static boolean acquire(long time, TimeUnit unit){
        try {
            return getMutex().acquire(time,unit);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    //释放锁
    public static void release(){
        try {
            getMutex().release();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


service实现调用

@Transactional
	public Result startSeckilZksLock(long seckillId, long userId) {
		boolean res=false;
		try {
			
			res = ZkLockUtil.acquire(3,TimeUnit.SECONDS);
			if(res){
				String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
				Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
				Long number =  ((Number) object).longValue();
				if(number>0){
					SuccessKilled killed = new SuccessKilled();
					killed.setSeckillId(seckillId);
					killed.setUserId(userId);
					killed.setState((short)0);
					killed.setCreateTime(new Timestamp(new Date().getTime()));
					dynamicQuery.save(killed);
					nativeSql = "UPDATE seckill  SET number=number-1 WHERE seckill_id=? AND number>0";
					dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
				}else{
					return Result.error(SeckillStatEnum.END);
				}
			}else{
				return Result.error(SeckillStatEnum.MUCH);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			if(res){//释放锁
				ZkLockUtil.release();
			}
		}
		return Result.ok(SeckillStatEnum.SUCCESS);
	}

你可能感兴趣的:(springboot,zookeeper,分布式锁)