nginx–> app1,app2 ->> redis
@RestController
public class IndexController {
@Resource
StringRedisTemplate stringRedisTemplate;
/**
* 秒杀
* redis中有一个货物 stock,表示货物数量。运用是个分布式运用,去抢占这个货物,抢到了就stock--
* @return
*/
@RequestMapping("deduct_stock")
public String deductStock() {
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("抢购成功,剩余库存:" + realStock);
} else {
System.out.println("抢购失败,库存不足");
}
return "end";
}
}
上述代码单体运用就会有问题。加锁来解决。
@RestController
public class IndexController {
private ReentrantLock lock = new ReentrantLock();
@Resource
StringRedisTemplate stringRedisTemplate;
/**
* 秒杀
* redis中有一个货物 stock,表示货物数量。运用是个分布式运用,去抢占这个货物,抢到了就stock--
*
* @return
*/
@RequestMapping("deduct_stock")
public String deductStock() {
lock.lock();
try {
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("抢购成功,剩余库存:" + realStock);
} else {
System.out.println("抢购失败,库存不足");
}
} finally {
lock.unlock();
}
return "end";
}
}
单体运用时候没有问题,但是在分布式的时候,每个jvm进程之间就会并发的问题。
在redis中设置一个字段,用redis的命令setnx
设置,如果设置成功则继续执行,设置失败则直接返回。setnx
表示如果不存在就设置set if not exit
@RestController
public class IndexController {
@Resource
StringRedisTemplate stringRedisTemplate;
/**
* 秒杀
* redis中有一个货物 stock,表示货物数量。运用是个分布式运用,去抢占这个货物,抢到了就stock--
*
* @return
*/
@RequestMapping("deduct_stock")
public String deductStock() {
String lockKey = "prod_001";
String clientId = UUID.randomUUID().toString();
try {
// 1. 为了防止在抢的过程中抛出Throwable,需要最终删除key
// 2. 为了防止在抢的过程中系统挂掉,需要设置key的有效时间
// 3. 设置key有效时间的问题:致线程执行的时间大于有效时间,导致当前线程释放了别人的锁
// 4. 为了防止3,key设置为每个线程的uuid。分布式uuid重复的几率很小
Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(lockSuccess)) {
return "当前系统繁忙,请稍后重试";
}
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("抢购成功,剩余库存:" + realStock);
} else {
System.out.println("抢购失败,库存不足");
}
} finally {
stringRedisTemplate.delete(lockKey);
}
return "end";
}
}
@RestController
public class DeductStockController {
@Resource
Redisson redisson;
@Resource
StringRedisTemplate stringRedisTemplate;
@RequestMapping("deduct_stock_redisson")
public String deductStock() {
String lockKey = "prod_001";
RLock lock = redisson.getLock(lockKey);
try {
// lock.lock();
lock.lock(30, TimeUnit.SECONDS); // 锁的过期时间
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("抢购成功,剩余库存:" + realStock);
} else {
System.out.println("抢购失败,库存不足");
}
} finally {
lock.unlock();
}
return "end";
}
}
Redisson 配置
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Bean
public RedissonClient redisson() {
Config config = new Config();
config.useSingleServer().setAddress(host + ":" + port);
return Redisson.create(config);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0nUz8ZQ-1660458423409)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220404220256151.png)]
redis是单体的没有问题,redis集群(主从)可能会有问题。访问redis的时候,会优先访问空闲的redis服务器,然后空闲的redis可能还没有lockKey,所以就会导致问题。
maven相关依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.7.4version>
dependency>
8.1、启动
jmeter.bat
8.2、添加线程组(相当于一组用户)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dfzD9xAV-1660458423410)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220405163035416.png)]
8.3、添加http请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPCrFnXS-1660458423411)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220405163100451.png)]
8.4、添加结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9b5sb3fW-1660458423411)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220405163117887.png)]
8.4、参数
Number of Threads(users)
:线程个数
Ramp-up period(seconds)
:在时间内均匀的发请求
Loop count
:测试次数