Redis学习笔记(四)-- 高并发分布式锁
高并发分布式锁
在我们平时写代码过程中,会遇到很多高并发的场景,比如双十一的秒杀场景,本文就以此举例分析
Spring Boot整合Redis锁代码
1、引入依赖
<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>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2、项目启动文件
package com.redisson;
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public Redisson redisson() {
// 此为单机模式
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
return (Redisson) Redisson.create(config);
}
}
3、主要分析文件代码
// 1.0版本
@RequestMapping("/deduct_stock")
public String deductStock() {
// 初始化redis的stock的值为30
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
return "end";
}
1.0版本的代码,通过读取存贮在redis的值进行扣减,然后将值再次更新到redis中,但是针对多线程的场景下,就会有问题了,于是2.0版本来了
// 2.0版本
@RequestMapping("/deduct_stock")
public String deductStock() {
synchronized (this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
}
return "end";
}
我们在生产环境下,可能应用程序不一定部署在一台机器上,可能有很多台机器,通过nginx来进行负载,在这种情形下,synchronized是jvm进程级别锁,在集群或者分布式场景下,就不起作用了,所以我们可以用redis分布式锁
// 3.0
@RequestMapping("/deduct_stock")
public String deductStock() {
String lockKey = "product_101";
//jedis.setnx(k,v)
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhangsan");
stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
if (!result) {
return "error_code";
}
try {
//jedis.get("stock")
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
// jedis.set(key,value)
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
stringRedisTemplate.delete(lockKey);
}
return "end";
}
通过redis.setnx(k,v)并设置超时时间来实现分布式锁,但是在高并发场景下,可能存在a机器访问15s才会结束,当访问10s的时候,key已经过了超时时间但是a机器的进程没有结束,这个时候b机器也可以访问了,就存在a进程去删除b的lockKey。这个场景咋处理呢?
// 4.0
@RequestMapping("/deduct_stock")
public String deductStock() {
String lockKey = "product_101";
String clientId = UUID.randomUUID().toString();
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
if (!result) {
return "error_code";
}
try {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
其实已经有框架redisson帮我们实现了
// 5.0
@RequestMapping("/deduct_stock")
public String deductStock() {
String lockKey = "product_101";
/* 读写锁示例
RReadWriteLock readWriteLock = redisson.getReadWriteLock(lockKey);
RLock rLock = readWriteLock.readLock();
RLock wLock = readWriteLock.writeLock();
*/
RLock redissonLock = redisson.getLock(lockKey);
try {
/*rLock.lock();
wLock.lock();*/
redissonLock.lock(); //setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
/*rLock.unlock();
wLock.unlock();*/
redissonLock.unlock();
}
return "end";
}