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集群环境

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值