利用consul在spring boot中实现分布式锁场景分析

 更新时间:2021年09月15日 16:39:21   作者:wenwuxianren  
这篇文章通过场景分析给大家介绍如何利用consul在spring boot中实现简单的分布式锁功能,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

因为在项目实际过程中所采用的是微服务架构,考虑到承载量基本每个相同业务的服务都是多节点部署,所以针对某些资源的访问就不得不用到用到分布式锁了。

这里列举一个最简单的场景,假如有一个智能售货机,由于机器本身的原因不能同一台机器不能同时出两个商品,这就要求在在出货流程前针对同一台机器在同一时刻出现并发创建订单时只能有一笔订单创建成功,但是订单服务是多节点部署的,所以就不得不用到分布式锁了。

以上只是一种简单的业务场景,在各种大型互联网实际应用中,需要分布式锁的业务场景会更多,综合比较了业界基于各种中间件来实现的分布式锁方案,然后结合实际业务最终决定采用consul来实现,因为我们的项目中采用了consul做注册中心,并且consul天生可以保证一致性(这点类似zk),当然zk也能实现分布式锁,但是这里不对这点做过多讨论。

redis虽然也能实现分布式锁,但是可能因为场景比较复杂,如果redis采用cluster部署的话,如果某一主节点出现故障的话,有一定几率会出现脑裂现象,这样就可能会让竞争者在并发时同时获得到锁,这样可能会破坏掉后面的业务,当然出现这种情况的概率很低,但是也不能完全排除,因为redis的根本不能保证强一致性导致的。

好了,这里说的最简单的分布式锁的意思是,多个竞争者同一时间并发去获得锁时,获取失败的就直接返回了,获取成功的继续后续的流程,然后在合适的时间释放锁,并且为锁加了超时时间,防止获得到锁的进程或线程在未来得及释放锁时自己挂掉了,导致资源处于一直被锁定的状态无法得到释放。主要的实现逻辑就是这样,如果有人想实现获得锁失

败的竞争者一直继续尝试获得,可以基于该示例进行修改,加上自旋逻辑就OK。

以下是锁实现代码:

package com.lyb.consullock;

import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.agent.model.NewCheck;
import com.ecwid.consul.v1.kv.model.PutParams;
import com.ecwid.consul.v1.session.model.NewSession;
import com.ecwid.consul.v1.session.model.Session;
import lombok.Data;


import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;


public class DistributedLock{
    private ConsulClient consulClient;

    /**
     * 构造函数
     * @param consulHost 注册consul的client或服务端的Ip或主机名,或域名
     * @param consulPort 端口号
     */
    public DistributedLock(String consulHost,int consulPort){
        consulClient = new ConsulClient(consulHost,consulPort);
    }

    /**
     * 获得锁的方法
     * @param lockName 竞争的资源名
     * @param ttlSeconds 锁的超时时间,超过该时间自动释放
     * @return
     */
    public LockContext getLock(String lockName,int ttlSeconds){
        LockContext lockContext = new LockContext();
        if(ttlSeconds<10 || ttlSeconds > 86400) ttlSeconds = 60;
        String sessionId = createSession(lockName,ttlSeconds);
        boolean success = lock(lockName,sessionId);
        if(success == false){
            consulClient.sessionDestroy(sessionId,null);
            lockContext.setGetLock(false);

            return lockContext;
        }

        lockContext.setSession(sessionId);
        lockContext.setGetLock(true);

        return lockContext;
    }

    /**
     * 释放锁
     * @param sessionID
     */
    public void releaseLock(String sessionID){
        consulClient.sessionDestroy(sessionID,null);
    }

    private String createSession(String lockName,int ttlSeconds){
        NewCheck check = new NewCheck();
        check.setId("check "+lockName);
        check.setName(check.getId());
        check.setTtl(ttlSeconds+"s"); //该值和session ttl共同决定决定锁定时长
        check.setTimeout("10s");
        consulClient.agentCheckRegister(check);
        consulClient.agentCheckPass(check.getId());

        NewSession session = new NewSession();
        session.setBehavior(Session.Behavior.RELEASE);
        session.setName("session "+lockName);
        session.setLockDelay(1);
        session.setTtl(ttlSeconds + "s"); //和check ttl共同决定锁时长
        List<String> checks = new ArrayList<>();
        checks.add(check.getId());
        session.setChecks(checks);
        String sessionId = consulClient.sessionCreate(session,null).getValue();

        return sessionId;
    }

    private boolean lock(String lockName,String sessionId){
        PutParams putParams = new PutParams();
        putParams.setAcquireSession(sessionId);

        boolean isSuccess = consulClient.setKVValue(lockName,"lock:"+ LocalDateTime.now(),putParams).getValue();

        return isSuccess;
    }

    /**
     * 竞争锁时返回的对象
     */
    @Data
    public class LockContext{
        /**
         * 获得锁成功返回该值,比便后面用该值来释放锁
         */
        private String session;
        /**
         * 是否获得到锁
         */
        private boolean isGetLock;
    }
}

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lyb</groupId>
    <artifactId>consul-lock</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>consul-lock</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

测试代码:

package com.lyb.consullock;

import org.junit.Assert;
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.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsulLockApplicationTests {
    @Autowired
    private ServiceConfig serviceConfig;
    @Test
    public void lockSameResourer() {
        //针对相同资源在同一时刻只有一个线程会获得锁
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for (int a=0;a<20;a++){
            threadPool.submit(
                    () -> {
                        for (int i = 0;i < 100; i++) {
                            DistributedLock lock = new DistributedLock(
                                    serviceConfig.getConsulRegisterHost(),
                                    serviceConfig.getConsulRegisterPort());

                            DistributedLock.LockContext lockContext = lock.getLock("test lock", 10);
                            if (lockContext.isGetLock()) {
                                System.out.println(Thread.currentThread().getName() + "获得了锁");
                                try {
                                    TimeUnit.SECONDS.sleep(1);
                                    lock.releaseLock(lockContext.getSession());
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }else {
                                //System.out.println(Thread.currentThread().getName() + "没有获得锁");
                            }
                        }
                    });
        }

        try {
            TimeUnit.MINUTES.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void lockDiffResource(){
        //针对不通的资源所有线程都应该能获得锁
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for (int a=0;a<20;a++){
            threadPool.submit(
                    () -> {
                        for (int i = 0;i < 100; i++) {
                            DistributedLock lock = new DistributedLock(
                                    serviceConfig.getConsulRegisterHost(),
                                    serviceConfig.getConsulRegisterPort());

                            DistributedLock.LockContext lockContext = lock.getLock("test lock"+Thread.currentThread().getName(), 10);
                            if (lockContext.isGetLock()) {
                                System.out.println(Thread.currentThread().getName() + "获得了锁");
                                try {
                                    TimeUnit.SECONDS.sleep(1);
                                    lock.releaseLock(lockContext.getSession());
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }else {
                                //System.out.println(Thread.currentThread().getName() + "没有获得锁");
                                Assert.assertTrue(lockContext.isGetLock());
                            }
                        }
                    });
        }

        try {
            TimeUnit.MINUTES.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

项目路径:

https://github.com/wenwuxianren/consul-lock

到此这篇关于利用consul在spring boot中实现最简单的分布式锁的文章就介绍到这了,更多相关spring boot分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现猜数字小游戏详解流程

    Java实现猜数字小游戏详解流程

    猜数字是兴起于英国的益智类小游戏,起源于20世纪中期,一般由两个人或多人玩,也可以由一个人和电脑玩。游戏规则为一方出数字,一方猜,今天我们来用Java把这个小游戏写出来练练手
    2021-10-10
  • SpringAOP实现自定义接口权限控制

    SpringAOP实现自定义接口权限控制

    本文主要介绍了SpringAOP实现自定义接口权限控制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-11-11
  • 使用IDEA创建SpringBoot项目的方法步骤

    使用IDEA创建SpringBoot项目的方法步骤

    这篇文章主要介绍了使用IDEA创建SpringBoot项目的方法步骤,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • SpringBoot统一数据返回格式的实现示例

    SpringBoot统一数据返回格式的实现示例

    本文主要介绍了SpringBoot统一数据返回格式,它提高了代码的可维护性和一致性,并改善了客户端与服务端之间的通信,具有一定的参考价值,感兴趣的可以了解一下
    2024-05-05
  • 详解Java网络编程

    详解Java网络编程

    网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。本文介绍了一些网络编程基础的概念,并用Java来实现TCP和UDP的Socket的编程,来让读者更好的了解其原理
    2021-06-06
  • Springboot登录验证的统一拦截处理的实现

    Springboot登录验证的统一拦截处理的实现

    如果不进行统一的拦截处理,每次用户请求你都要去进行用户的信息验证,所以本文主要介绍了Springboot登录验证的统一拦截处理的实现,感兴趣的可以了解一下,感兴趣的可以了解一下
    2023-09-09
  • Mybatis详解动态SQL以及单表多表查询的应用

    Mybatis详解动态SQL以及单表多表查询的应用

    MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑,下面这篇文章主要给大家介绍了关于Mybatis超级强大的动态SQL语句的相关资料,需要的朋友可以参考下
    2022-06-06
  • Java设计模式七大原则之合成复用原则详解

    Java设计模式七大原则之合成复用原则详解

    合成复用原则(Composite Reuse Principle),即尽量使用组合/聚合的方式,而不是使用继承。本文将为大家具体介绍一下Java设计模式七大原则之一的合成复用原则,需要的可以参考一下
    2022-02-02
  • mybatis-plus 查询时排除字段方法的两种方法

    mybatis-plus 查询时排除字段方法的两种方法

    我们在开发应用时,在某些应用场景下查询有时需要排除某些字段,本文主要介绍了两种方法,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • SpringBoot中使用Ehcache的详细教程

    SpringBoot中使用Ehcache的详细教程

    EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。这篇文章主要介绍了SpringBoot中使用Ehcache的相关知识,需要的朋友可以参考下
    2020-08-08

最新评论