为了账号安全,请及时绑定邮箱和手机立即绑定

【九月打卡】第10天 阶段三 · 手把手带你快速搞定分布式锁

标签:
架构

课程名称Java架构师-技术专家
课程章节: 阶段三 · 手把手带你快速搞定分布式锁
课程讲师: 慕课讲师团队

课程内容

分布式锁实现

分布式锁要跨进程,多个进程要使用锁的话,JVM里面的锁就不生效了,就需要借助第三方组件,主流使用Redis和Zookeeper。

基于 Zookeeper 实现分布式锁

节点里面可以存储数据;
红色:持久节点;
黄色:瞬时节点,瞬时节点是不可以有子节点的;有序是指创建瞬时节点的时候,名称是按瞬时节点排序的;

瞬时节点不可再有子节点,会话结束后(重启等)瞬时节点自动消失

图片描述

Zookeeper的观察器:可以检测Zookeeper里某个节点的变化,比如节点存在和不存在,节点数据发生变化和子节点发生变化等;如果有变化,观察期会立即通知到客户端

可设置观察器的3个方法:getData()、getChildren()、exists();
节点数据发生变化,发送给客户端;
观察器只能监控一次,再监控需重新设置;

实现原理:

  • 利用Zookeeper的瞬时有序节点的特性
  • 多线程并发创建瞬时节点时,得到有序的序列;
  • 序号最小的线程获得锁;
  • 其它线程则监听自己序号的前一个序号;
  • 前一个线程执行完成,删除自己序号的节点;
  • 下一个序号的线程得到通知,继续执行;
  • 以此类推……;
  • 创建节点时,已经确定了线程的执行顺序;

图片描述
根据上述原理,编写Zookeeper分布式锁:

  1. 创建springboot项目,引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.8.0</version>
</dependency>
  1. 创建ZKLock工具类提供获取锁的功能。
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;

@Slf4j
public class ZkLock implements Watcher, AutoCloseable/*实现这个接口可以让类实现自动关闭*/ {
    private ZooKeeper zooKeeper;
    private String znode;

    public ZkLock() throws IOException {
        this.zooKeeper = new ZooKeeper("zk1:2181,zk2:2181,zk3:2181,zk4:2181,zk5:2181", 10000, this);
    }

    public boolean getLock(String businessCode) {
        //1.判断业务根节点是否存在,不存在就要创建业务根节点
        try {
            Stat stat = zooKeeper.exists("/" + businessCode, false);
            if (stat == null) {
                zooKeeper.create("/" + businessCode,/*节点路径*/
                        businessCode.getBytes()/*节点初始数据*/,
                        ZooDefs.Ids.OPEN_ACL_UNSAFE/*节点权限*/,
                        CreateMode.PERSISTENT/*节点类型 持久节点;瞬时节点*/);
            }
            //2.创建瞬时有序节点  /order/order_0000001    下划线后面会自动跟序号
            znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_",
                    businessCode.getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL/*创建瞬时有序节点*/);
            //3.判断当前瞬时节点是不是 序号最小的,获取业务根节点下的所有子节点并排序
            List<String> childrenNode = zooKeeper.getChildren("/" + businessCode, false);
            //子节点排序
            Collections.sort(childrenNode);
            //获取序号最小的子节点(第一个子节点)
            String firstNode = childrenNode.get(0);
            //4.如果创建的节点是第一个子节点,则获得锁,如果不是则要监听前一个节点
            if (znode.endsWith(firstNode)) {
                return true;
            }
            //不是第一个子节点,则监听前一个节点
            String lastNode = firstNode;
            for (String node : childrenNode) {
                if (znode.endsWith(node)) {
                    zooKeeper.exists("/" + businessCode + "/" + lastNode, true);//监听节点,如果节点消失了,会得到通知
                    break;
                } else {
                    lastNode = node;
                }
            }
            synchronized (this) {
                //等待前面节点消失时唤醒
                wait();
            }
            return true;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 释放锁:直接删除节点
     *
     * @throws Exception
     */
    @Override
    public void close() throws Exception {
        zooKeeper.delete(znode, -1);
        zooKeeper.close();
        log.info("我已经释放了锁");
    }

    /**
     * 当节点消失后,就会进入到此方法中
     *
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
            synchronized (this) {
                notify();
            }
        }
    }
}
  1. 创建ZKController测试ZKLock工具类
@RestController
@Slf4j
public class ZookeeperController {
    @GetMapping("zkLock")
    public String zookeeperLock() {
        log.info("我进入了方法");
        try (ZkLock zkLock = new ZkLock()) {
            if (zkLock.getLock("order")) {
                log.info("我获得了锁");
                Thread.sleep(15000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }
}
  1. 在8000、8001、8002、8003端口上启动这一个springboot项目,分别访问http://localhost:{端口号}/zkLock,可以发现这四个线程是依次获得锁去执行的。打开prettyZoo连接Zookeeper。当释放锁之后,瞬时节点会消失。
    图片描述

Curator分布式锁

  • 引入curator客户端,curator是基于Zookeeper的,是zookeeper客户端的升级版本
  • curator已经实现了分布式锁的方法,直接调用即可
  1. 创建springboot项目,引入Curator依赖
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.2.0</version>
</dependency>
  1. 创建获取连接的方法并注入容器
@Configuration
@Component
public class GetCuratorFramework {
    @Bean(initMethod = "start", destroyMethod = "close")//指定初始化方法和销毁方法
    public CuratorFramework getConnection() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.newClient("zk1:2181,zk2:2181,zk3:2181,zk4:2181,zk5:2181", retryPolicy);
        return client;
    }
}
  1. 在controller使用多个进程进行测试,多个进程依然是顺序执行的
@Autowired
private CuratorFramework client;

@GetMapping("curatorLock")
public String curatorLock() throws Exception {
    log.info("我进入了方法");
    InterProcessMutex lock = null;
    try {
        lock = new InterProcessMutex(client, "/order");
        if (lock.acquire(30, TimeUnit.SECONDS)) {
            log.info("我获得了锁");
            Thread.sleep(10000);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //释放锁
        if (null!=lock) lock.release();
    }
    log.info("方法执行完成");
    return "方法执行完成";
}

Redisson分布式锁

Redisson重新实现了JUC包下的工具类以及List、HashMap等集合类,让这些工具类可以跨JVM使用。Redisson是Redis客户端的升级版本,提供了分布式锁,只需调用。
在springboot项目中使用Redisson实现分布式锁:

  1. 创建Spring boot项目,引入 Resisson的starter依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.0</version>
</dependency>
  1. 进行Redisson与Redis的配置
spring:
  redis:
    host: localhost
  1. 在controller中编写测试代码进行多进程测试
@Slf4j
@RestController
public class RedissonLockController {
    @Autowired
    private RedissonClient redissonClient;
    @GetMapping("redissonLock")
    public String redissonLock() {
        RLock rLock = redissonClient.getLock("order");
        log.info("我进入了方法!!");
        try{
            rLock.lock(30, TimeUnit.SECONDS);
            log.info("我获得了锁!!!");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            log.info("我释放了锁");
            rLock.unlock();
        }
        log.info("方法执行完成");
        return "方法执行完毕";
    }
}

课程内容

今天学习了分布式锁的原理和三种实现方式。
图片描述

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
6
获赞与收藏
11

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消