互联网秒杀
抢优惠卷
接口幂等性校验
本篇将会通过实战模拟一下用redis解决高并发的分布式锁,循序渐进让大家觉得分布式不再那么遥不可及
用到的工具:Springboot项目,ngnix,redis,Jmeter
@RequestMapping("/deduct_stock0")
public String deductStock0() {
try {
//加锁
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 + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
}
} catch (Exception e){
e.printStackTrace();
}
return "end";
}
电商平台的减库存操作。
启动两个SpringBoot来模拟2台tomcat集群下的场景。一个是8080端口,一个是8090端口
那么接下来组件ngnix代理访问这俩个
Linux安装ngnix不会的参考这篇Linux下安装ngnix,
修改配置文件,做个反向代理,运行:
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
默认是50个库存
运行如下:
可以很明显的发现超卖了,因为两边有相同的剩余库存。所以我们知道
//加锁
synchronized (this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
}
synchronized 的加锁是JVM级别,也就是集群下是不会有效的。那对于分布式的场景应该怎么控制呢?分布式锁就来了。
我的之前博客说过这么一句话setnx,redis里没有就会执行成功
String lockKey = "product_001";
//redis里没有就会执行成功
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "tzlock");
if (!result) {
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
stringRedisTemplate.delete(lockKey);
return "end";
那我们看下结果8090服务器
8080服务器
可以很明显的发现锁是成功的了
那这么写有没有什么问题
@RequestMapping("/deduct_stock0")
public String deductStock0() {
String lockKey = "product_001";
//redis里没有就会执行成功
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "tzlock");
if (!result) {
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
stringRedisTemplate.delete(lockKey);
return "end";
}
如果发生异常怎么办?所以要加处理
如果执行到中间的代码,直接宕机了?就要加个超时时间,我们要把超时的设置和key绑定起来。
@RequestMapping("/deduct_stock0")
public String deductStock0() {
String lockKey = "product_001";
String clientId = UUID.randomUUID().toString();
try{
//redis里没有就会执行成功
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
if (!result) {
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
stringRedisTemplate.delete(lockKey);
} finally {
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
String clientId = UUID.randomUUID().toString();
代表我自己线程加的锁不能被别的线程动。谁加的锁谁去释放!!
到这里,我们的分布式锁其实已经是差不多了,那我们看下其他的方案,介绍一款成熟的框架Redisson
这个在分布式场景下功能很强大,分布式锁,分布式对象等等
在启动类加入
@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://192.168.3.27:6379").setDatabase(0);
//集群模式
//config.useClusterServers().addNodeAddress("你的ip");
return (Redisson) Redisson.create(config);
}
}
IndexController看下
@RequestMapping("/deduct_stockByRedision")
public String deductStock() {
String lockKey = "product_001";
RLock redissonLock = redisson.getLock(lockKey);
try {
//加锁
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 {
redissonLock.unlock();
}
return "end";
}