The first: use synchronized (only applicable to a single tomcat)
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct")
public String toDeduct(){
synchronized (this) {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
Long stock1 = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println("扣减成功,剩余库存:" + stock1);
}else{
System.out.println("扣减失败,库存不足");
}
return "end";
}
}
The second: use the SETNX command (multiple tomcats)
@GetMapping("/deduct_stock")
public String deductStock() throws InterruptedException {
String lockKey = "lockKey";
//设计唯一key值防止锁失效问题
String clientId = UUID.randomUUID().toString();
//设计锁超时时间,防止服务器宕机时锁没有释放掉(finally语句没有执行)
Boolean result = stringRedisTemplate.opsForValue().
setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);//jedis.setnx(key,value);
//若键 key 已经存在, 则 SETNX 命令不做任何动作。result==false
if (!result) {
return "正在排队。。。";
}
try {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println("扣减成功,剩余库存:" + realStock);
}else{
System.out.println("扣减失败,库存不足");
}
} finally {
//防止锁失效问题,在多线程的情况下,每个线程只释放自己创建的锁,线程之间互不干预。
if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
The third type: use the Redission framework (the principle is the same as the second type)
1. Configure the config class
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient getRedisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
//添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
return Redisson.create(config);
}
}
@GetMapping("/deduct_stock1")
public String deductStock1(){
String lockKey = "lockKey";
RLock redissonLock = redisson.getLock(lockKey);
try {
redissonLock.lock();
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println("扣减成功,剩余库存:" + realStock);
}else{
System.out.println("扣减失败,库存不足");
}
} finally {
redissonLock.unlock();
}
return "end";
}
Redission principle:
Redission uses Lua scripts, and the implementation principle is similar to the second one.
When the business code processing time is very long, like mysql slow query, etc., you can check whether the lock is still held every 10 seconds in the try code block. If you hold it, extend the lock time to prevent the business code from being executed "lockKey" "It's the expiration time.
Test environment setup
Redis command list
Set the Redis password
pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.7</version>
</dependency>
application.properties:
server.port=8090
# redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=888888
spring.redis.jedis.pool.max-active=500
spring.redis.jedis.pool.max-idle=1000
spring.redis.jedis.pool.max-wait=6000ms
spring.redis.jedis.pool.min-idle=4
Nginx load balancing:
upstream redislock{
server 127.0.0.1:8080 weight = 1;
server 127.0.0.1:8090 weight = 1;
}
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://redislock;
}
}
Jmeter stress test
Send http request:
set the sending address: it
means to send 200 requests within 0 seconds, and then send 200 * 3 times after processing, a total of 200 * 4 requests:
result set:
Test result:
no oversold problem