当前位置:   article > 正文

SpringBoot电商项目实战--Curator分布式锁实现方案_spring curator

spring curator

一、引言:

上一篇文章,介绍了:《学会Zookeeper分布式锁--让面试官对你刮目相看》

网上的分布式锁文章千篇一律,而此文从实际高并发场景深入浅出,缘由剖析,不管是应对面试官的层层"逼问",还是实际项目,相信都能游刃有余,你学会了吗?还不会建议请先去看下哦。

1、分布式锁,场景描述:

分布式锁用途:在分布式环境下协同共享资源的使用。

2、分布式锁思路分析

锁特点:

  • 排他性:同一时间,只有一个线程能获得;

  • 阻塞性:其它未抢到的线程阻塞等待,直到锁被释放,再继续抢;

  • 可重入性:线程获得锁后,后续是否可重复获取该锁(避免死锁)。

当然,还要考虑性能开销等问题。

3、常规的分布式锁解决方案有哪几种:

  • 文件系统:同一个目录下,不能存在同名文件

  • 数据库锁:主键 、 唯一约束  、for  update

  • 基于Redis的分布式锁:setnx、set、Redisson

  • 基于ZooKeeper的分布式锁:类似文件系统

对比分析:

  1. 使用数据库锁会有单机性能、单机故障等问题,锁没有失效时间,容易出现死锁,当然可以部署群集,也会出现各种各样的问题,性能开销高,这里不详细介绍。

  2. Redis缓存实现分布式锁,相对复杂,因为没有类似zk的watch监听通知机制,需要自己另外实现;而且Redis可能会出现死锁(或短时间内死锁),比如,获取到锁的线程挂了,必须等到该节点过期时间到了,才能删除。

  3. 而Zookeeper分布式锁可靠性比Redis好,实现相对简单,但由于需要创建节点、删除节点等,所以效率相比Redis要低。

那我们在实际项目中如何选择呢?

原则上如果并发量不是特别大,追求可靠性,那么首选zookeeper。而Redis实现的分布式锁响应更快,对并发的支持性能更好,如果为了效率,首选redis实现。

上篇文章已经介绍了使用原生的Zookeeper实现的分布式锁方案,本文将讲解使用现成的框架Curator实现的分布式锁方案。


二、Curator简介

Zookeeper已经流行了这么多年,实际上基于zk的分布式锁目前已经有现成的实现框架,Curator就是Netflix开源的一套ZooKeeper客户端框架,它提供了zk场景的绝大部分实现,使用Curator就不必关心其内部算法,Curator提供了来实现分布式锁,用方法获取锁,以及用方法释放锁,同其他锁一样,方法需要放在finally代码块中,确保锁能正确释放。

ZooKeeper可以被用来实现分布式锁,具体是使用“临时顺序节点”实现(假如使用“临时节点”将会出现惊群效应,上篇有介绍)。

Curator提供了四种分布式锁,分别是:

  • InterProcessMutex:分布式可重入排它锁

  • InterProcessSemaphoreMutex:分布式排它锁

  • InterProcessReadWriteLock:分布式读写锁

  • InterProcessMultiLock:将多个锁作为单个实体管理的容

获取锁

我们可以在Zookeeper下创建一个指定的父节点作为分布式锁,每个zk客户端尝试连接zk服务获取分布式锁时,都将在此父节点下创建一个临时顺序节点,分两种情况:

  • 如果创建的临时顺序节点是父节点下的首个子节点(最小),则获取锁成功,执行相应业务逻辑,然后释放锁。

  • 如果创建的临时顺序节点并不是该父节点下最小的子节点,则去对比比自己小的节点注册watcher监听,只监听比自己小的上一个节点,进入阻塞等待。当前一个节点被删除时会触发Watch事件,进而唤醒当前阻塞线程。

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

重入性

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

因为重入性只考虑同一个客户端、同一个JVM、同一个线程,所以可以不用考虑判断ConcurrentMap中的Owner线程的并发问题。

释放锁

释放锁时,对应可重入分布式锁,首先重入次数减一,然后判断重入次数是否已经为0:

  • 如果重入次数为0,则删除当前客户端线程对应的临时顺序节点,删除操作会触发次节点的Watch事件,如果有别的客户端线程正在阻塞等待,则会通过Watch机制唤醒。

  • 如果重入次数非0,则说明还未完全释放锁,直接返回即可。

1、pom引入如下curator依赖

        <!-- curator:zk客户端 -->        <dependency>            <groupId>org.apache.curator</groupId>            <artifactId>curator-framework</artifactId>            <version>4.2.0</version>        </dependency>        <dependency>            <groupId>org.apache.curator</groupId>            <artifactId>curator-recipes</artifactId>            <version>4.2.0</version>        </dependency>

2、yml配置文件

curator:  connectionTimeoutMs: 5000  # 连接超时时间  elapsedTimeMs: 5000   #重试间隔时间  retryCount: 3  #重试次数  sessionTimeoutMs: 60000  # session超时时间  connectString: 127.0.0.1:2181   # zookeeper 地址

3、curator配置类,读取配置属性,并注册bean到spring ioc容器

CuratorFrameworkFactory类提供了两个方法,一个工厂方法newClient,一个构建方法build。使用工厂方法newClient可以创建一个默认的实例, 而build构建方法可以对实例进行定制。当CuratorFramework实例构建完成, 紧接着调用start()方法。

@Configuration@ConfigurationProperties(prefix = "curator")@Datapublic class CuratorConfig {    private int retryCount;    private int elapsedTimeMs;    private String connectString;    private int sessionTimeoutMs;    private int connectionTimeoutMs;        @Bean(initMethod = "start")    public CuratorFramework curatorFramework() {        RetryPolicy retryPolicy = new ExponentialBackoffRetry(elapsedTimeMs, retryCount);        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()                .connectString(connectString)                .sessionTimeoutMs(sessionTimeoutMs)                .retryPolicy(retryPolicy)                .build();        return curatorFramework;    }}

4、新建一个订单服务实现类

@Slf4j@Servicepublic class CuratorDisLockOrderServiceImpl implements OrderService {    private static OrderCodeGenerator codeGenerator = new OrderCodeGenerator();    private static String LOCK_PATH = "/distribute-lock";    @Autowired    private CuratorFramework curatorFramework;    @Override    public String createOrder() {        String orderCode = "";        InterProcessMutex lock = new InterProcessMutex(curatorFramework, LOCK_PATH);        try {            lock.acquire();            //生成订单编号            orderCode = codeGenerator.getOrderCode();            log.info(Thread.currentThread().getName()+"-->获取锁成功-->生成订单编号:{}",orderCode);        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                lock.release();                log.info(Thread.currentThread().getName() + "-->释放锁成功。");            } catch (Exception e) {                e.printStackTrace();            }        }        // TODO 具体写自己的生成订单业务        return orderCode;    }}

注:​InterProcessMutex通过在zookeeper的某路径节点下创建临时顺序节点来实现分布式锁,即每个线程(跨进程的线程)获取同一把锁前,都需要在同样的路径下创建一个节点,节点名字由uuid + 递增序列组成。而通过对比自身的序列数是否在所有子节点的第一位,来判断是否成功获取到了锁。当获取锁失败时,它会添加watcher来监听前一个节点的变动情况,然后进行等待状态。直到watcher的事件生效将自己唤醒,或者超时时间异常返回。

5、新建一个controller,提供一个下单http接口方法

@RestController@Slf4jpublic class OrderController {    @Autowired    private  OrderService orderService;    /**     * 模拟高并发场景,多线程,并发下单     * @return     */    @RequestMapping("/order")    public String createOrdertTest(){        //并发线程数        int count = 20;        //循环屏障        CyclicBarrier cb  = new CyclicBarrier(count);        //模拟高并发场景,多线程,创建订单        for(int i=0; i<count; i++){            new Thread(new Runnable() {                @Override                public void run() {                    log.info(Thread.currentThread().getName()+"--我已经准备好了");                    try {                        //等待所有线程启动准备好,才一起往下执行                        cb.await();                    } catch (InterruptedException | BrokenBarrierException e) {                        e.printStackTrace();                    }                    //创建订单                    orderService.createOrder();                }            }).start();        }        return "ok";    }}

6、运行应用,访问 http://localhost:8080/order ,观察控制台,订单编号没有重复。

-->是不是感觉比Zookeeper原生实现的分布式锁简单0.0

有兴趣了解Curator源码是如何实现分布式锁,可以参考如下图示:

参考资料:https://blog.csdn.net/xuefeng0707/article/details/80588855

看完了,是不是又学会一个技能,点个赞,关注如下公众号 “阿甘正专” 下再走吧0.0

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/954341
推荐阅读
相关标签
  

闽ICP备14008679号