对前面讲解 redisson 实现分布式锁的文章做个补充(上篇文章地址),上篇文章测试的不太准确,本篇将使用jmeter专业测试工具,模拟并发请求
背景:启动 redis,6379端口,SpringCloud微服务,模拟秒杀抢购场景,100库存,用jmeter测试,起300个线程并发请求2次,总计600个请求数,最后查看库存是否为负数,证明分布式锁是否锁住了库存。
- 注册中心 10025端口
- 消费者服务 9700端口
- 秒杀服务 8083、8084,启动2个服务
测试流程如下:
- 1.启动注册中心,消费者服务与秒杀服务都注册到注册中心
- 2.启动消费者服务,通过feign 以负载均衡方式调用秒杀服务
- 3.启动秒杀服务,请求秒杀服务时,修改商品库存,商品库存存储在redis中,默认100
- 4下面是整体架构图
库存设置为100
通过jmeter执行http请求,模拟用户抢购
查看结果,从jmeter上可以看到有600个请求,有13个失败了
再看redis中的库存,为0,说明锁住了,没有发生超卖现象
总共测试了多次,贴出有代表性的:
- 1秒内启动 200线程,循环请求2次,总计400请求,成功了几十个请求,其余请求被hystrix降级返回了
- 2秒内启动 300线程,循环请求2次,总计600请求,请求全部成功,库存为0
- 3秒内启动 500线程,循环请求2次,总计1000请求,请求全部成功,库存为0
- 5秒内启动 1000线程,循环请求2次,总计2000请求,请求全部成功,库存为0
说明在1秒内启动200个线程并发请求,程序无法处理过来
再贴出秒杀服务的主要代码,注册中心与消费者服务的代码请查看 SpringCloud 实战系列
秒杀服务主要依赖了 redisson 与 netty , 其他都是微服务开发需引入的
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web 模块-->
<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>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
启动主类的代码如下:
@EnableDiscoveryClient
@SpringBootApplication
public class DistributedLockApplication {
public static void main(String[] args) {
SpringApplication.run(DistributedLockApplication.class, args);
}
//添加redisson的bean
@Bean
public Redisson redisson(){
Config config = new Config();
//此示例是单库的,可以是主从、sentinel、集群等模式
config.useSingleServer().setAddress("redis://localhost:6379");
return (Redisson)Redisson.create(config);
}
}
然后是提供秒杀功能的控制器
@RestController
public class IndexController {
private static String commodityCount = "commodityCount";//商品key
private static String lockKey = "testRedisson";//分布式锁的key
@Autowired private StringRedisTemplate redisTemplate;
@Autowired private Redisson redisson;
/**
* 查询是否健康
* @return
*/
@RequestMapping(value = "/health" , method = RequestMethod.GET)
public String health(){
return "health";
}
/**
* 设置商品数量为100个
* @param value
* @return
*/
@RequestMapping("/setValue")
public String setValue(int value){
redisTemplate.opsForValue().set(commodityCount , value + "");
return "success";
}
/**
* 模拟秒杀抢购,并发200个请求过来,查看是否出现超卖
* @return
*/
@RequestMapping("/spike")
public String spike(){
String flag = "success";
RLock lock = redisson.getLock(lockKey);
try{
//lock.lockAsync(5 , TimeUnit.SECONDS);
//lock.lock(5, TimeUnit.SECONDS); //设置60秒自动释放锁 (默认是30秒自动过期)
Future<Boolean> res = lock.tryLockAsync(100, 5, TimeUnit.SECONDS);
boolean result = res.get();
System.out.println("result:" + result);
if(result){
int stock = Integer.parseInt(redisTemplate.opsForValue().get(commodityCount).toString());
if(stock > 0){
redisTemplate.opsForValue().set(commodityCount,(stock-1)+"");
}else{
flag = "fail";
}
}
}catch (Exception e){
e.printStackTrace();
}
finally{
lock.unlock(); //释放锁
}
return flag;
}
}
源码已上传至码云,获取源码
redisson config配置参考:https://github.com/redisson/redisson/wiki/2.-Configuration#261-single-instance-settings