SpringBoot电商项目实战--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

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot 整合分布式锁有很多种方法,其中一种是使用第三方框架,比如 Redis 和 Zookeeper。 使用 Redis 实现分布式锁可以使用 Redis 的原子操作实现,如 SETNX 命令。 使用 Zookeeper 实现分布式锁可以使用 Zookeeper 的临时有序节点特性。 此外,还有一些开源的分布式锁框架,例如:jedis、curator等。 在使用之前,请仔细研究这些框架实现原理和使用方法,以保证分布式锁的正确性。 ### 回答2: Spring Boot是一个用于简化Spring应用程序开发的框架,它提供了很多便利的特性和功能。而分布式锁是一种用于保证在分布式系统中多个实例之间的数据一致性的技术。 在Spring Boot中整合分布式锁可以采用多种方式,常用的有基于数据库的分布式锁和基于Redis的分布式锁。下面以基于Redis的分布式锁为例进行说明: 首先,需要引入Redis的依赖,可以使用Spring Data Redis来与Redis进行交互。 接下来,创建一个分布式锁的工具类,该工具类需要实现获取锁和释放锁的方法。获取锁的过程可以使用Redis的setnx方法,该方法在指定的key不存在时才会设置成功,可以用来实现分布式锁的竞争。而释放锁的过程可以使用Redis的del方法来删除锁。 在需要加锁的代码块中,首先通过工具类获取锁,如果获取成功则执行业务逻辑,执行完毕后释放锁。如果获取失败则进行重试或放弃。 需要注意的是,分布式锁的设计要考虑并发性和可靠性,可以采用基于时间的自动释放锁,避免因为锁未被释放导致的死锁等问题。 总结起来,使用Spring Boot整合分布式锁的过程包括引入Redis依赖、创建分布式锁工具类、在需要加锁的代码块中获取锁和释放锁。这样可以保证在分布式环境下保证数据一致性和并发性。 ### 回答3: Spring Boot是一种用于创建和部署独立、可扩展且生产级别的Java应用程序的开发框架。要实现分布式锁的整合,我们可以使用Spring Boot提供的各种工具和库来处理分布式锁的相关问题。 首先,我们可以使用Spring Data Redis作为实现分布式锁的主要工具。Redis是一种开源的高性能键值对存储数据库,它具备分布式锁所需的原子性和并发性。我们可以使用Redis的setnx命令来尝试获取锁,并使用expire命令来设置锁的过期时间。在Spring Boot中,我们可以使用Spring Data Redis来操作Redis,并将其集成到我们的应用程序中。 其次,我们可以使用Spring Cloud的分布式锁解决方案,如Zookeeper、Etcd等。这些解决方案提供了高可用、高性能的分布式协调服务,可以用于实现分布式锁。在Spring Boot中,我们可以使用Spring Cloud Zookeeper或Spring Cloud Etcd来创建和管理分布式锁。 另外,我们还可以使用分布式锁的开源库,如Curator、Redlock等。这些库提供了方便易用的分布式锁实现,可以帮助我们更容易地整合和使用分布式锁功能。在Spring Boot中,我们可以将这些库集成到项目中,并使用其提供的API来实现分布式锁。 总结起来,Spring Boot提供了丰富的工具和库来整合和使用分布式锁。我们可以选择合适的工具和库来实现分布式锁的相关功能,并根据实际需求进行配置和使用。无论是使用Redis、Zookeeper、Etcd还是开源库,Spring Boot都提供了相应的支持和集成方式,使得分布式锁的整合变得更加简单和便捷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值