利用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分布式锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-09-12

SpringBoot中使用redis做分布式锁的方法

一.模拟问题 最近在公司遇到一个问题,挂号系统是做的集群,比如启动了两个相同的服务,病人挂号的时候可能会出现同号的情况,比如两个病人挂出来的号都是上午2号.这就出现了问题,由于是集群部署的,所以单纯在代码中的方法中加锁是不能解决这种情况的.下面我将模拟这种情况,用redis做分布式锁来解决这个问题. 1.新建挂号明细表 2.在idea上新建项目 下图是创建好的项目结构,上面那个parent项目是其他项目不用管它,和新建的没有关系 3.开始创建controller,service,dao(mapp

SpringBoot + Spring Cloud Consul 服务注册和发现详细解析

什么是Consul Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其它分布式服务注册与发现的方案,Consul 的方案更"一站式",内置了服务注册与发现框架.分布一致性协议实现.健康检查.Key/Value 存储.多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等).使用起来也较为简单.Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux.windows和Mac OS X):安装包仅包含一个可执行文件

SpringBoot整合Redis正确的实现分布式锁的示例代码

前言 最近在做分块上传的业务,使用到了Redis来维护上传过程中的分块编号. 每上传完成一个分块就获取一下文件的分块集合,加入新上传的编号,手动接口测试下是没有问题的,前端通过并发上传调用就出现问题了,并发的get再set,就会存在覆盖写现象,导致最后的分块数据不对,不能触发分块合并请求. 遇到并发二话不说先上锁,针对执行代码块加了一个JVM锁之后问题就解决了. 仔细一想还是不太对,项目是分布式部署的,做了负载均衡,一个节点的代码被锁住了,请求轮询到其他节点还是可以进行覆盖写,并没有解决到问题啊

Redis分布式锁升级版RedLock及SpringBoot实现方法

分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式.但是现在公司都是流行分布式架构,在分布式环境下,如何保证不同节点的线程同步执行呢?因此就引出了分布式锁,它是控制分布式系统之间互斥访问共享资源的一种方式. 在一个分布式系统中,多台机器上部署了多个服务,当客户端一个用户发起一个数据插入请求时,如果没有分布式锁机制保证,那么那多台机器上的多个服务可能进行并发插

SpringBoot之使用Redis实现分布式锁(秒杀系统)

一.Redis分布式锁概念篇 建议直接采用Redis的官方推荐的Redisson作为redis的分布式锁 1.1.为什么要使用分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug! 注意这是单机应用,也就是所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间! 后来业务发展,需要做集群,一个应用需要部署到几台机

SpringBoot使用Redis实现分布式锁

前言 在单机应用时代,我们对一个共享的对象进行多线程访问的时候,使用java的synchronized关键字或者ReentrantLock类对操作的对象加锁就可以解决对象的线程安全问题. 分布式应用时代这个方法却行不通了,我们的应用可能被部署到多台机器上,运行在不同的JVM里,一个对象可能同时存在多台机器的内存中,怎样使共享对象同时只被一个线程处理就成了一个问题. 在分布式系统中为了保证一个对象在高并发的情况下只能被一个线程使用,我们需要一种跨JVM的互斥机制来控制共享资源的访问,此时就需要用到

Spring boot2X Consul如何使用Feign实现服务调用

这篇文章主要介绍了spring boot2X Consul如何使用Feign实现服务调用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 服务调用有两种方式: A.使用RestTemplate 进行服务调用 B.使用Feign 进行声明式服务调用 上一次写了使用RestTemplate的方式,这次使用Feign的方式实现 服务注册发现中心使用Consul 启动Consul consul agent -dev spring boot 版本 2.2.

SpringBoot 集成 ShedLock 分布式锁的示例详解

目录 一:ShedLock 简介 二:配置maven 依赖 三:SchedulerLock 基于 Redis 的配置 四:在启动类中添加 @EnableScheduling 标签 五:test 测试案例 一:ShedLock 简介 ShedLock是一个在分布式环境中使用的定时任务框架,用于解决在分布式环境中的多个实例的相同定时任务在同一时间点重复执行的问题.ShedLock确保计划的任务最多同时执行一次.如果一个任务正在一个节点上执行,它会获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任

Spring boot2X Consul如何通过RestTemplate实现服务调用

这篇文章主要介绍了spring boot2X Consul如何通过RestTemplate实现服务调用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Consul可以用于实现分布式系统的服务发现与配置 服务调用有两种方式: A.使用RestTemplate 进行服务调用 负载均衡--通过Ribbon注解RestTemplate B.使用Feign 进行声明式服务调用 负载均衡--默认使用Ribbon实现 先使用RestTemplate来实现 1

spring cloud consul注册的服务报错critical的解决

测试spring cloud 使用consul注册服务的时候,出现critical,如下: 怎么解决这个问题,现在只能看到health check检查失败了. 受限调用这个请求Get http://consulIp:8500/v1/agent/checks,调完请求,就会拿到返回数据: { ...... "service:test-service-xx-xx-xx-xx": { "Node": "zookeeper-server1", "

spring cloud consul使用ip注册服务的方法示例

我测试spring cliud使用consul作为注册中心的时候,发现服务注册的时候,注册的都是hostname,比如: 注册了一个commonservice,在consul中是这样的: { "ID":"commonservice123", "address":"testcommonserver" ........ } 这肯定是不对的. 加入我有一个服务payservice需要调用commonservice,payservic

详解使用Spring Cloud Consul实现服务的注册和发现

首先安装consul环境,参照之前的文章:https://www.jb51.net/article/141789.htm 项目规划,2个服务端,1个客户端 首先来看服务端, 一:服务端1: 项目依赖 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-clou

详解Spring Cloud Consul 实现服务注册和发现

Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为基于 JVM 的云应用开发中涉及的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式.通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂.易部署和易维护的分布式系统开发工具包. Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Sprin

Spring boot2X负载均衡和反向代理实现过程解析

这篇文章主要介绍了Spring boot2X负载均衡和反向代理实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 zuul 是netflix开源的一个API Gateway 服务器 所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序. 作为一个边界性质的应用程序,Zuul提供了动态路由.监控.弹性负载和安全功能. 实现反向代理 1.服务注册发现中心Consul 启动 consul agent -dev 2.服务端

spring cloud 使用Zuul 实现API网关服务问题

通过前面几次的分享,我们了解了微服务架构的几个核心设施,通过这些组件我们可以搭建简单的微服务架构系统.比如通过Spring Cloud Eureka搭建高可用的服务注册中心并实现服务的注册和发现: 通过Spring Cloud Ribbon或Feign进行负载均衡:通过Spring Cloud Hystrix进行服务容错保护以避免故障蔓延.微服务搭建好了之后我们肯定会提供给外部系统一些统一的RESTFul API服务接口进行调用, 但是当外部系统调用我们的RESTful API的时候,怎么确定它

SpringCloud实战之Feign声明式服务调用

在前面的文章中可以发现当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下,并且显得好傻. 那么有没有更好的解决方案呢?答案是确定的有,Netflix已经为我们提供了一个框架:Feign. Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单.Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义