模拟并发抢票
先在redis中存入一个ticket,值为50,作为被抢的票。
抢票接口:
@RestController
public class TestController {
@Autowired
StringRedisTemplate template;
@GetMapping("/test")
public String test() throws InterruptedException{
int ticket = Integer.parseInt(template.opsForValue().get("ticket"));
if (ticket > 0) {
int realTicket = ticket - 1;
template.opsForValue().set("ticket", realTicket + "");
System.out.println("抢票成功,剩余:" + realTicket + "张票");
} else {
System.out.println("出票失败!余票不足");
}
return "end";
}
}
JMeter测试:
结果:
结论:高并发下存在数据不一致。
如果加锁(synchronize、ReentrantLock)也只能保证单个微服务的一致性;如果为集群环境依然存在数据不一致的问题。
使用Redis分布式锁
因为redis为单线程模型,所以可以redis的setnx 命令实现锁:
setnx key value (set if not exists)
只在键不存在的情况下,将key的值设置为value
若key已经存在,则setnx命令不做任何动作
命令在设置成功时返回1,失败返回0
@RestController
public class TestController {
@Autowired
StringRedisTemplate template;
@GetMapping("/test")
public String test() throws InterruptedException{
//模拟某辆火车的id,下面就是对该车的票加锁
String productId = "PID222222";
//表示线程的UUID
String clientId = UUID.randomUUID().toString();
try {
//加锁 ---> setnx lockKey mylock
//同时需要设置超时,防止该微服务挂了而锁未释放,超时时间应该大于抢票逻辑的执行时间
Boolean res = template.opsForValue().setIfAbsent(productId, clientId, 10, TimeUnit.SECONDS);
if (!res) {
return "error";
}
int ticket = Integer.parseInt(template.opsForValue().get("ticket"));
if (ticket > 0) {
int realTicket = ticket - 1;
template.opsForValue().set("ticket", realTicket + "");
System.out.println("抢票成功,剩余:" + realTicket + "张票");
} else {
System.out.println("出票失败!余票不足");
}
} finally {
//校验是否为原来的线程
//不过不校验,可能出现下面的情况
/*
万一抢票逻辑执行时间大于10秒,在中间执行的时候,锁就因为超时而释放了,
并发环境下又会有第二个线程加锁,开始抢票
而此时第一个线程抢票完毕,准备释放锁,但是它释放的已经不是自己的锁了,而是第二个线程加的锁
*/
if (clientId.equals(template.opsForValue().get(productId))) {
//释放锁
template.delete(productId);
}
}
return "end";
}
}
使用Redisson构建分布式锁
依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
配置:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public Redisson redisson() {
//单机模式
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson)Redisson.create(config);
}
}
redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。
@RestController
public class TestController {
@Autowired
Redisson redisson;
@Autowired
StringRedisTemplate template;
@GetMapping("/test")
public String test() throws InterruptedException{
//模拟某辆火车的id,下面就是对该车的票加锁
String productId = "PID222222";
//获取一把锁
RLock redissonLock = redisson.getLock(productId);
try {
//加锁
redissonLock.lock(30, TimeUnit.SECONDS);
int ticket = Integer.parseInt(template.opsForValue().get("ticket"));
if (ticket > 0) {
int realTicket = ticket - 1;
template.opsForValue().set("ticket", realTicket + "");
System.out.println("抢票成功,剩余:" + realTicket + "张票");
} else {
System.out.println("出票失败!余票不足");
}
} finally {
//释放锁
redissonLock.unlock();
}
return "end";
}
}