Raft分布式一致性算法实现—Etcd分布式锁(秒杀)

Posted boonya

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Raft分布式一致性算法实现—Etcd分布式锁(秒杀)相关的知识,希望对你有一定的参考价值。

目录

项目基础配置

pom.xml依赖配置

 Redis模板配置

分布式锁配置

application.yml配置

 核心分布式锁配置

抽象锁

ETCD分布式锁

API接口测试

测试日志打印

实现秒杀

参考文章


本文通过以redis扣减库存来实现ETCD分布式锁讲解。

项目基础配置

pom.xml依赖配置

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--ETCD https://etcd.io/ 分布式存储解决方案:支持分布式锁-->
        <dependency>
            <groupId>io.etcd</groupId>
            <artifactId>jetcd-core</artifactId>
            <version>${jetcd.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

 Redis模板配置

package com.boonya.spring.locks.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean("redisTemplateAlias")
    public RedisTemplate<String, Object> redisTemplateAlias(LettuceConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //序列化设置 ,这样为了存储操作对象时正常显示的数据,也能正常存储和获取
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }

    @Bean("stringRedisTemplateAlias")
    public StringRedisTemplate stringRedisTemplateAlias(LettuceConnectionFactory factory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(factory);
        return stringRedisTemplate;
    }
}

分布式锁配置

package com.boonya.spring.locks.config;

import com.boonya.spring.locks.etcd.EtcdDistributedLock;
import io.etcd.jetcd.Client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

/**
 * Etcd分布式锁配置
 */
@Configuration
public class EtcdDistributedLockConfig {

    @Value("${etcd.url:\\"http://localhost:2379\\"}")
    String etcdUrl;

    @Value("${etcd.lockKey:lock-key}")
    String lockKey;

    @Bean
    public EtcdDistributedLock etcdDistributedLock(){
        Client client =  Client.builder().endpoints(etcdUrl).build();
        return new EtcdDistributedLock(client,lockKey,3L, TimeUnit.SECONDS);
    }
}

application.yml配置

spring:
  # REDIS 分布式KV存储方案配置
  redis:
    database: 10  #Redis索引0~15,默认为0
    host: 127.0.0.1
    port: 6379
    password:  #密码(默认为空)
    lettuce: # 这里标明使用lettuce配置
      pool:
        max-active: 50   #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1  #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 10     #连接池中的最大空闲连接
        min-idle: 10     #连接池中的最小空闲连接
    timeout: 10000    #连接超时时间(毫秒)
# ETCD 分布式存储方案配置
etcd:
  url: http://localhost:2379
  lockKey: stock-lock

 核心分布式锁配置

抽象锁

package com.boonya.spring.locks.base;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基于锁的抽象类
 */
public abstract class AbstractDistributedLock extends ReentrantLock implements Lock {

    @Override
    public void lock() {
        super.lock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        super.lockInterruptibly();
    }

    @Override
    public boolean tryLock() {
        return super.tryLock();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return super.tryLock(time,unit);
    }

    @Override
    public void unlock() {
        super.unlock();
    }

    @Override
    public Condition newCondition() {
        return super.newCondition();
    }
}

ETCD分布式锁

package com.boonya.spring.locks.etcd;

import com.boonya.spring.locks.base.AbstractDistributedLock;
import com.google.common.collect.Maps;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.Lease;
import io.etcd.jetcd.Lock;
import io.etcd.jetcd.lock.LockResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ETCD 分布式锁实现
 *
 * @See https://zhuanlan.zhihu.com/p/369680338
 */
@Slf4j
public class EtcdDistributedLock extends AbstractDistributedLock {

    private Client client;
    private Lock lockClient;
    private Lease leaseClient;
    private String lockKey;
    //锁路径,方便记录日志
    private String lockPath;
    //租约有效期。作用 1:客户端崩溃,租约到期后自动释放锁,防止死锁 2:正常执行自动进行续租
    private Long leaseTTL;
    //续约锁租期的定时任务,初次启动延迟,默认为1s,根据实际业务需要设置
    private Long initialDelay = 0L;
    //定时任务线程池
    ScheduledExecutorService scheduledExecutorService;
    //线程与锁对象的映射
    private final ConcurrentMap<Thread, LockData> threadData = Maps.newConcurrentMap();

    public EtcdDistributedLock(Client client, String lockKey, Long leaseTTL, TimeUnit unit) {
        this.client = client;
        if(null != client){
            this.lockClient = this.client.getLockClient();
            this.leaseClient =  this.client.getLeaseClient();
        }
        this.lockKey = lockKey;
        this.leaseTTL = unit.toNanos(leaseTTL);
        this.scheduledExecutorService = Executors.newScheduledThreadPool(10);
    }

    @Override
    public void lock() {
        Thread currentThread = Thread.currentThread();
        log.info(currentThread.getName() + "加锁...");
        try {
            log.info("线程:{} 创建租约...",currentThread.getName());
            //创建租约,记录租约id
            long leaseId = leaseClient.grant(TimeUnit.NANOSECONDS.toSeconds(leaseTTL)).get().getID();
            //续租心跳周期
            long period = leaseTTL - leaseTTL / 5;
            //启动定时续约
            scheduledExecutorService.scheduleAtFixedRate(new KeepAliveTask(leaseClient, leaseId), initialDelay, period,TimeUnit.NANOSECONDS);

            // 设置锁对象数据
            LockData lockData = new LockData(lockKey, currentThread);
            lockData.setLeaseId(leaseId);

            // 加锁响应对象
            LockResponse lockResponse = lockClient.lock(ByteSequence.from(lockKey.getBytes()), leaseId).get();
            // 设置锁标记
            lockData.lockCount.incrementAndGet();
            if (lockResponse != null) {
                lockPath = lockResponse.getKey().toString(StandardCharsets.UTF_8);
                log.info("线程:{} 加锁成功,锁路径:{}", currentThread.getName(), lockPath);
            }
            //加锁成功,设置锁对象
            threadData.put(currentThread, lockData);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void unlock() {
        Thread currentThread = Thread.currentThread();
        log.info(currentThread.getName() + " 释放锁...");
        LockData lockData = threadData.get(currentThread);
        log.info(currentThread.getName() + " lockData " + lockData);
        if (lockData == null) {
            throw new IllegalMonitorStateException("线程:" + currentThread.getName() + " 没有获得锁,lockKey:" + lockKey);
        }
        // 正常解锁时计数器应该是正数
        if(lockData.lockCount.get()>0){
            // 减去锁计数
            int lockCount = lockData.lockCount.decrementAndGet();
            if (lockCount > 0) {
                throw new IllegalMonitorStateException("线程:" + currentThread.getName() + " 锁次数为正数,lockKey:" + lockKey);
            }
            if (lockCount < 0) {
                throw new IllegalMonitorStateException("线程:" + currentThread.getName() + " 锁次数为负数,lockKey:" + lockKey);
            }
            try {
                //正常释放锁
                if (lockPath != null) {
                    lockClient.unlock(ByteSequence.from(lockPath.getBytes())).get();
                    log.info("线程:{} 正常释放锁!",currentThread.getName());
                }
                //删除租约
                if (lockData.getLeaseId() != 0L) {
                    leaseClient.revoke(lockData.getLeaseId());
                    log.info("线程:{} 删除租约!",currentThread.getName());
                }
            } catch (InterruptedException | ExecutionException e) {
                log.error("线程:{} 解锁失败:[{}]", currentThread.getName(), e);
                e.printStackTrace();
            } finally {
                //移除当前线程资源
                threadData.remove(currentThread);
            }
            log.info("线程:{} 释放锁", currentThread.getName());
        }
    }

    @Data
    class LockData{
        public  AtomicInteger lockCount = new AtomicInteger(0);
        long leaseId;
        String lockKey;
        Thread currentThread;

        public LockData(String lockKey, Thread currentThread) {
            this.lockKey = lockKey;
            this.currentThread = currentThread;
        }
    }

    @Data
    class KeepAliveTask implements Runnable {
        private Lease leaseClient;
        private long leaseId;

        KeepAliveTask(Lease leaseClient, long leaseId) {
            this.leaseClient = leaseClient;
            this.leaseId = leaseId;
        }

        @Override
        public void run() {
            leaseClient.keepAliveOnce(leaseId);
        }
    }
}

API接口测试

package com.boonya.spring.locks.api;

import com.boonya.spring.locks.etcd.EtcdDistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@RestController
@RequestMapping("/etcd")
public class EtcdLockController {

    @Autowired
    EtcdDistributedLock etcdDistributedLock;

    @Qualifier("stringRedisTemplateAlias")
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 商品库存删除
     * @param sku
     * @return
     */
    @RequestMapping("/stock/delete/{sku}")
    public String deleteStock(@PathVariable("sku") String sku){
        try{
            etcdDistributedLock.lock();
            stringRedisTemplate.delete(sku);
            etcdDistributedLock.unlock();
        }catch (Exception e){
            e.printStackTrace();
            return "error";
        }
        return "success";
    }

    /**
     * 商品库存更正
     * @param sku
     * @param number
     * @return
     */
    @RequestMapping("/stock/update/{sku}/{number}")
    public String updateStock(@PathVariable("sku") String sku, @PathVariable("number") int number){
        try{
            etcdDistributedLock.lock();
            stringRedisTemplate.boundValueOps(sku).set(number+"");
            etcdDistributedLock.unlock();
        }catch (Exception e){
            e.printStackTrace();
            return "error";
        }
        return "success";
    }

    /**
     * 商品库存扣减
     * @param sku
     * @param number
     * @return
     */
    @RequestMapping("/stock/reduce/{sku}/{number}")
    public String reduceStock(@PathVariable("sku") String sku, @PathVariable("number") int number){
        try{
            etcdDistributedLock.lock();
            String value = stringRedisTemplate.boundValueOps(sku).get();
            log.info("原库存剩余数量stock=[{}]",value);
            if(null != value){
                int stock = Integer.valueOf(value.trim());
                AtomicInteger sum = new AtomicInteger(stock);
                if(sum.get() >= number){
                    int count = sum.addAndGet(-number);
                    log.info("库存剩余数量stock=[{}]",count);
                    stringRedisTemplate.boundValueOps(sku).set(count+"");
                }
            }
            etcdDistributedLock.unlock();
        }catch (Exception e){
            e.printStackTrace();
            return "error";
        }
        return "success";
    }
}

测试日志打印

"C:\\Program Files\\Java\\jdk1.8.0_291\\bin\\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53657,suspend=y,server=n -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=53656 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:C:\\Users\\boonya\\.IntelliJIdea2018.3\\system\\captureAgent\\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\charsets.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\deploy.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\access-bridge-64.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\cldrdata.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\dnsns.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\jaccess.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\jfxrt.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\localedata.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\nashorn.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\sunec.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\sunjce_provider.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\sunmscapi.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\sunpkcs11.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\ext\\zipfs.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\javaws.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\jce.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\jfr.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\jfxswt.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\jsse.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\management-agent.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\plugin.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\resources.jar;C:\\Program Files\\Java\\jdk1.8.0_291\\jre\\lib\\rt.jar;C:\\DEVELOPERS\\learning\\spring-features\\spring-locks\\target\\classes;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot-starter-web\\2.5.1\\spring-boot-starter-web-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot-starter\\2.5.1\\spring-boot-starter-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot\\2.5.1\\spring-boot-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot-autoconfigure\\2.5.1\\spring-boot-autoconfigure-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot-starter-logging\\2.5.1\\spring-boot-starter-logging-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\ch\\qos\\logback\\logback-classic\\1.2.3\\logback-classic-1.2.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\ch\\qos\\logback\\logback-core\\1.2.3\\logback-core-1.2.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\apache\\logging\\log4j\\log4j-to-slf4j\\2.14.1\\log4j-to-slf4j-2.14.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\apache\\logging\\log4j\\log4j-api\\2.14.1\\log4j-api-2.14.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\slf4j\\jul-to-slf4j\\1.7.30\\jul-to-slf4j-1.7.30.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\jakarta\\annotation\\jakarta.annotation-api\\1.3.5\\jakarta.annotation-api-1.3.5.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\yaml\\snakeyaml\\1.28\\snakeyaml-1.28.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot-starter-json\\2.5.1\\spring-boot-starter-json-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\fasterxml\\jackson\\core\\jackson-databind\\2.12.3\\jackson-databind-2.12.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\fasterxml\\jackson\\core\\jackson-annotations\\2.12.3\\jackson-annotations-2.12.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\fasterxml\\jackson\\core\\jackson-core\\2.12.3\\jackson-core-2.12.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\fasterxml\\jackson\\datatype\\jackson-datatype-jdk8\\2.12.3\\jackson-datatype-jdk8-2.12.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\fasterxml\\jackson\\datatype\\jackson-datatype-jsr310\\2.12.3\\jackson-datatype-jsr310-2.12.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\fasterxml\\jackson\\module\\jackson-module-parameter-names\\2.12.3\\jackson-module-parameter-names-2.12.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot-starter-tomcat\\2.5.1\\spring-boot-starter-tomcat-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\apache\\tomcat\\embed\\tomcat-embed-core\\9.0.46\\tomcat-embed-core-9.0.46.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\apache\\tomcat\\embed\\tomcat-embed-el\\9.0.46\\tomcat-embed-el-9.0.46.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\apache\\tomcat\\embed\\tomcat-embed-websocket\\9.0.46\\tomcat-embed-websocket-9.0.46.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-web\\5.3.8\\spring-web-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-beans\\5.3.8\\spring-beans-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-webmvc\\5.3.8\\spring-webmvc-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-aop\\5.3.8\\spring-aop-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-context\\5.3.8\\spring-context-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-expression\\5.3.8\\spring-expression-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\boot\\spring-boot-starter-data-redis\\2.5.1\\spring-boot-starter-data-redis-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\data\\spring-data-redis\\2.5.1\\spring-data-redis-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\data\\spring-data-keyvalue\\2.5.1\\spring-data-keyvalue-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\data\\spring-data-commons\\2.5.1\\spring-data-commons-2.5.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-tx\\5.3.8\\spring-tx-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-oxm\\5.3.8\\spring-oxm-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-context-support\\5.3.8\\spring-context-support-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\lettuce\\lettuce-core\\6.1.2.RELEASE\\lettuce-core-6.1.2.RELEASE.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-common\\4.1.65.Final\\netty-common-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-handler\\4.1.65.Final\\netty-handler-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-resolver\\4.1.65.Final\\netty-resolver-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-buffer\\4.1.65.Final\\netty-buffer-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-codec\\4.1.65.Final\\netty-codec-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-transport\\4.1.65.Final\\netty-transport-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\projectreactor\\reactor-core\\3.4.6\\reactor-core-3.4.6.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\reactivestreams\\reactive-streams\\1.0.3\\reactive-streams-1.0.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\etcd\\jetcd-core\\0.5.7\\jetcd-core-0.5.7.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\etcd\\jetcd-common\\0.5.7\\jetcd-common-0.5.7.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\guava\\guava\\30.1.1-jre\\guava-30.1.1-jre.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\guava\\failureaccess\\1.0.1\\failureaccess-1.0.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\guava\\listenablefuture\\9999.0-empty-to-avoid-conflict-with-guava\\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\checkerframework\\checker-qual\\3.8.0\\checker-qual-3.8.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\j2objc\\j2objc-annotations\\1.3\\j2objc-annotations-1.3.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-core\\1.37.0\\grpc-core-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-api\\1.37.0\\grpc-api-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-context\\1.37.0\\grpc-context-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\code\\gson\\gson\\2.8.7\\gson-2.8.7.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\android\\annotations\\4.1.1.4\\annotations-4.1.1.4.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\codehaus\\mojo\\animal-sniffer-annotations\\1.19\\animal-sniffer-annotations-1.19.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\errorprone\\error_prone_annotations\\2.4.0\\error_prone_annotations-2.4.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\perfmark\\perfmark-api\\0.23.0\\perfmark-api-0.23.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\code\\findbugs\\jsr305\\3.0.2\\jsr305-3.0.2.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-netty\\1.37.0\\grpc-netty-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-codec-http2\\4.1.65.Final\\netty-codec-http2-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-codec-http\\4.1.65.Final\\netty-codec-http-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-handler-proxy\\4.1.65.Final\\netty-handler-proxy-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\netty\\netty-codec-socks\\4.1.65.Final\\netty-codec-socks-4.1.65.Final.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-protobuf\\1.37.0\\grpc-protobuf-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\protobuf\\protobuf-java\\3.12.0\\protobuf-java-3.12.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\api\\grpc\\proto-google-common-protos\\2.0.1\\proto-google-common-protos-2.0.1.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-protobuf-lite\\1.37.0\\grpc-protobuf-lite-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-stub\\1.37.0\\grpc-stub-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\io\\grpc\\grpc-grpclb\\1.37.0\\grpc-grpclb-1.37.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\protobuf\\protobuf-java-util\\3.12.0\\protobuf-java-util-3.12.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\slf4j\\slf4j-api\\1.7.30\\slf4j-api-1.7.30.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\net\\jodah\\failsafe\\2.4.0\\failsafe-2.4.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\javax\\annotation\\javax.annotation-api\\1.3.2\\javax.annotation-api-1.3.2.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\com\\google\\auto\\service\\auto-service-annotations\\1.0\\auto-service-annotations-1.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\projectlombok\\lombok\\1.18.20\\lombok-1.18.20.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\apache\\commons\\commons-pool2\\2.9.0\\commons-pool2-2.9.0.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-core\\5.3.8\\spring-core-5.3.8.jar;C:\\DEVELOPERS\\MAVEN\\apache-maven-3.5.0\\repository\\org\\springframework\\spring-jcl\\5.3.8\\spring-jcl-5.3.8.jar;C:\\Program Files\\JetBrains\\IntelliJ IDEA 2018.3.3\\lib\\idea_rt.jar" com.boonya.spring.locks.SpringLocksApplication
Connected to the target VM, address: '127.0.0.1:53657', transport: 'socket'

  .   ____          _            __ _ _
 /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
 \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.1)

2021-06-16 15:00:11.682  INFO 22228 --- [           main] c.b.spring.locks.SpringLocksApplication  : Starting SpringLocksApplication using Java 1.8.0_291 on DESKTOP-NHMGL6Q with PID 22228 (C:\\DEVELOPERS\\learning\\spring-features\\spring-locks\\target\\classes started by boonya in C:\\DEVELOPERS\\learning\\spring-features)
2021-06-16 15:00:11.685  INFO 22228 --- [           main] c.b.spring.locks.SpringLocksApplication  : No active profile set, falling back to default profiles: default
2021-06-16 15:00:12.231  INFO 22228 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2021-06-16 15:00:12.233  INFO 22228 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2021-06-16 15:00:12.255  INFO 22228 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 9 ms. Found 0 Redis repository interfaces.
2021-06-16 15:00:12.609  INFO 22228 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-06-16 15:00:12.616  INFO 22228 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-06-16 15:00:12.616  INFO 22228 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.46]
2021-06-16 15:00:12.690  INFO 22228 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-06-16 15:00:12.690  INFO 22228 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 952 ms
2021-06-16 15:00:13.510  INFO 22228 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-06-16 15:00:13.521  INFO 22228 --- [           main] c.b.spring.locks.SpringLocksApplication  : Started SpringLocksApplication in 2.365 seconds (JVM running for 3.273)
2021-06-16 15:00:27.886  INFO 22228 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-06-16 15:00:27.886  INFO 22228 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-06-16 15:00:27.887  INFO 22228 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2021-06-16 15:00:27.914  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-1加锁...
2021-06-16 15:00:27.914  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-1 创建租约...
2021-06-16 15:00:27.941  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-2加锁...
2021-06-16 15:00:27.941  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-2 创建租约...
2021-06-16 15:00:28.049  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-3加锁...
2021-06-16 15:00:28.049  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-3 创建租约...
2021-06-16 15:00:28.145  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-4加锁...
2021-06-16 15:00:28.145  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-4 创建租约...
2021-06-16 15:00:28.238  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-5加锁...
2021-06-16 15:00:28.238  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-5 创建租约...
2021-06-16 15:00:28.349  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-6加锁...
2021-06-16 15:00:28.349  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-6 创建租约...
2021-06-16 15:00:28.442  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-7加锁...
2021-06-16 15:00:28.442  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-7 创建租约...
2021-06-16 15:00:28.541  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-1 加锁成功,锁路径:stock-lock/694d7a12da195401
2021-06-16 15:00:28.548  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-8加锁...
2021-06-16 15:00:28.548  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-8 创建租约...
2021-06-16 15:00:28.641  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-9加锁...
2021-06-16 15:00:28.641  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-9 创建租约...
2021-06-16 15:00:28.724  INFO 22228 --- [nio-8080-exec-1] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[9000]
2021-06-16 15:00:28.725  INFO 22228 --- [nio-8080-exec-1] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8900]
2021-06-16 15:00:28.728  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-1 释放锁...
2021-06-16 15:00:28.728  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-1 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246785, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-1,5,main])
2021-06-16 15:00:28.734  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-1 正常释放锁!
2021-06-16 15:00:28.735  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-4 加锁成功,锁路径:stock-lock/694d7a12da1953fd
2021-06-16 15:00:28.736  INFO 22228 --- [nio-8080-exec-4] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8900]
2021-06-16 15:00:28.736  INFO 22228 --- [nio-8080-exec-4] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8800]
2021-06-16 15:00:28.736  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-1 删除租约!
2021-06-16 15:00:28.736  INFO 22228 --- [nio-8080-exec-1] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-1 释放锁
2021-06-16 15:00:28.737  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-4 释放锁...
2021-06-16 15:00:28.737  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-4 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246781, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-4,5,main])
2021-06-16 15:00:28.739  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-4 正常释放锁!
2021-06-16 15:00:28.740  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-4 删除租约!
2021-06-16 15:00:28.740  INFO 22228 --- [nio-8080-exec-4] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-4 释放锁
2021-06-16 15:00:28.740  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-2 加锁成功,锁路径:stock-lock/694d7a12da195402
2021-06-16 15:00:28.741  INFO 22228 --- [nio-8080-exec-2] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8800]
2021-06-16 15:00:28.741  INFO 22228 --- [nio-8080-exec-2] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8700]
2021-06-16 15:00:28.742  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-2 释放锁...
2021-06-16 15:00:28.742  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-2 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246786, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-2,5,main])
2021-06-16 15:00:28.745  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-2 正常释放锁!
2021-06-16 15:00:28.745  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-5 加锁成功,锁路径:stock-lock/694d7a12da1953fc
2021-06-16 15:00:28.745  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-2 删除租约!
2021-06-16 15:00:28.745  INFO 22228 --- [nio-8080-exec-2] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-2 释放锁
2021-06-16 15:00:28.746  INFO 22228 --- [nio-8080-exec-5] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8700]
2021-06-16 15:00:28.747  INFO 22228 --- [nio-8080-exec-5] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8600]
2021-06-16 15:00:28.748  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-5 释放锁...
2021-06-16 15:00:28.748  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-5 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246780, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-5,5,main])
2021-06-16 15:00:28.748  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-10加锁...
2021-06-16 15:00:28.748  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-10 创建租约...
2021-06-16 15:00:28.750  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-5 正常释放锁!
2021-06-16 15:00:28.751  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-5 删除租约!
2021-06-16 15:00:28.751  INFO 22228 --- [nio-8080-exec-5] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-5 释放锁
2021-06-16 15:00:28.751  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-7 加锁成功,锁路径:stock-lock/694d7a12da195403
2021-06-16 15:00:28.751  INFO 22228 --- [nio-8080-exec-7] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8600]
2021-06-16 15:00:28.751  INFO 22228 --- [nio-8080-exec-7] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8500]
2021-06-16 15:00:28.753  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-7 释放锁...
2021-06-16 15:00:28.753  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-7 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246787, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-7,5,main])
2021-06-16 15:00:28.755  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-7 正常释放锁!
2021-06-16 15:00:28.756  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-7 删除租约!
2021-06-16 15:00:28.756  INFO 22228 --- [nio-8080-exec-7] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-7 释放锁
2021-06-16 15:00:28.756  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-6 加锁成功,锁路径:stock-lock/694d7a12da1953f9
2021-06-16 15:00:28.757  INFO 22228 --- [nio-8080-exec-6] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8500]
2021-06-16 15:00:28.757  INFO 22228 --- [nio-8080-exec-6] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8400]
2021-06-16 15:00:28.758  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-6 释放锁...
2021-06-16 15:00:28.758  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-6 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246777, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-6,5,main])
2021-06-16 15:00:28.762  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-6 正常释放锁!
2021-06-16 15:00:28.762  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-6 删除租约!
2021-06-16 15:00:28.763  INFO 22228 --- [nio-8080-exec-6] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-6 释放锁
2021-06-16 15:00:28.763  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-3 加锁成功,锁路径:stock-lock/694d7a12da1953fb
2021-06-16 15:00:28.764  INFO 22228 --- [nio-8080-exec-3] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8400]
2021-06-16 15:00:28.764  INFO 22228 --- [nio-8080-exec-3] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8300]
2021-06-16 15:00:28.765  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-3 释放锁...
2021-06-16 15:00:28.765  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-3 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246779, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-3,5,main])
2021-06-16 15:00:28.767  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-3 正常释放锁!
2021-06-16 15:00:28.767  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-3 删除租约!
2021-06-16 15:00:28.767  INFO 22228 --- [nio-8080-exec-3] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-3 释放锁
2021-06-16 15:00:28.768  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-8 加锁成功,锁路径:stock-lock/694d7a12da195411
2021-06-16 15:00:28.769  INFO 22228 --- [nio-8080-exec-8] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8300]
2021-06-16 15:00:28.769  INFO 22228 --- [nio-8080-exec-8] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8200]
2021-06-16 15:00:28.770  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-8 释放锁...
2021-06-16 15:00:28.771  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-8 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246801, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-8,5,main])
2021-06-16 15:00:28.774  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-8 正常释放锁!
2021-06-16 15:00:28.774  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-8 删除租约!
2021-06-16 15:00:28.774  INFO 22228 --- [nio-8080-exec-8] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-8 释放锁
2021-06-16 15:00:28.774  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-9 加锁成功,锁路径:stock-lock/694d7a12da195415
2021-06-16 15:00:28.775  INFO 22228 --- [nio-8080-exec-9] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8200]
2021-06-16 15:00:28.775  INFO 22228 --- [nio-8080-exec-9] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8100]
2021-06-16 15:00:28.776  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-9 释放锁...
2021-06-16 15:00:28.776  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-9 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246805, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-9,5,main])
2021-06-16 15:00:28.779  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-9 正常释放锁!
2021-06-16 15:00:28.779  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-9 删除租约!
2021-06-16 15:00:28.779  INFO 22228 --- [nio-8080-exec-9] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-9 释放锁
2021-06-16 15:00:28.792  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-10 加锁成功,锁路径:stock-lock/694d7a12da195425
2021-06-16 15:00:28.803  INFO 22228 --- [io-8080-exec-10] c.b.spring.locks.api.EtcdLockController  : 原库存剩余数量stock=[8100]
2021-06-16 15:00:28.803  INFO 22228 --- [io-8080-exec-10] c.b.spring.locks.api.EtcdLockController  : 库存剩余数量stock=[8000]
2021-06-16 15:00:28.805  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-10 释放锁...
2021-06-16 15:00:28.805  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : http-nio-8080-exec-10 lockData EtcdDistributedLock.LockData(lockCount=1, leaseId=7587855168576246821, lockKey=stock-lock, currentThread=Thread[http-nio-8080-exec-10,5,main])
2021-06-16 15:00:28.808  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-10 正常释放锁!
2021-06-16 15:00:28.808  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-10 删除租约!
2021-06-16 15:00:28.808  INFO 22228 --- [io-8080-exec-10] c.b.s.locks.etcd.EtcdDistributedLock     : 线程:http-nio-8080-exec-10 释放锁

实现秒杀

秒杀业务场景要保证分布式一致性和不允许超卖,大多数参与秒杀的客户端都不能走到真正扣减库存。所以我们在实现秒杀业务时,只需要判定是否有库存,秒杀场景下的请求量巨大,所以参与秒杀的商品都是有限的,比如10,50,100..这样的商品数量。

  /**
     * 秒杀商品库存扣减
     * @param sku
     * @return
     */
    @RequestMapping("/stock/second/{sku}")
    public String secondStock(@PathVariable("sku") String sku){
        Thread thread = Thread.currentThread();
        try{
            // 第一次查询库存:是否进入秒杀
            String value = stringRedisTemplate.boundValueOps(sku).get();
            log.info("{}秒杀原库存剩余数量stock=[{}]",thread.getName(),value);
            int stock = null==value?0:Integer.valueOf(value.trim());
            if(stock>0){
                etcdDistributedLock.lock();
                // 第二次查询库存:执行秒杀库存扣减
                value = stringRedisTemplate.boundValueOps(sku).get();
                stock = Integer.valueOf(value.trim());
                AtomicInteger sum = new AtomicInteger(stock);
                if(sum.get() >= 1){
                    int count = sum.addAndGet(-1);
                    log.info("{}秒杀库存剩余数量stock=[{}]",thread.getName(),count);
                    stringRedisTemplate.boundValueOps(sku).set(count+"");
                }
                etcdDistributedLock.unlock();
                log.info("{}秒杀成功!",thread.getName());
            }else{
                log.info("{}秒杀失败,库存卖光了!",thread.getName());
            }
        }catch (Exception e){
            e.printStackTrace();
            return "error";
        }
        return "success";
    }

使用Jmeter压测:

秒杀没有扣成功的情况:

参考文章

windows系统下etcd的安装与使用

etcd分布式存储解决方案详解

使用docker-compose搭建etcd集群环境

以上是关于Raft分布式一致性算法实现—Etcd分布式锁(秒杀)的主要内容,如果未能解决你的问题,请参考以下文章

etcd Raft 源码剖析

etcd Raft 源码剖析

etcd Raft 源码剖析

etcd Raft 源码剖析

ETCD服务

etcd使用