Raft分布式一致性算法实现—Etcd分布式锁(秒杀)
Posted boonya
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Raft分布式一致性算法实现—Etcd分布式锁(秒杀)相关的知识,希望对你有一定的参考价值。
目录
本文通过以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压测:
秒杀没有扣成功的情况:
参考文章
以上是关于Raft分布式一致性算法实现—Etcd分布式锁(秒杀)的主要内容,如果未能解决你的问题,请参考以下文章