现在的多数电商公司都会采用分布式架构,分布式部署,如下单,简单的描述
拿订单秒杀模块举例,通常我们实现秒杀会采用redis缓存技术,活动举行前把相应的库存数存储到redis里边
由图看以看出,在高并发的场景下,很容易造成库存的不一致问题。传统的锁像 synchronized 在此场景下就会失效。
此时,我们需要一把锁,在多个线程下都能上锁。
思考:
- redis的 setnx方法:redis执行setnx 添加数据时,若redis中存在相同的key,那么redis中的数据不会发生变化。
- redis是单线程的
那么,我们是不是可以用setnx来实现一把分布式锁呢?可以的,直接上代码
/**
* 模拟订单库存接口 秒杀
*
* @return
*/
@RequestMapping
public String testStock() {
String lockKey = "testStock";
String lockValue = UUID.randomUUID().toString();
// 获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 30, TimeUnit.SECONDS);
if (!lock) {
return "服务器繁忙 请稍后";
}
try {
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if (stock < 0) {
//todo 业务逻辑
return "库存不足";
}
//todo 业务逻辑
int currStock = stock - 1;
redisTemplate.opsForValue().set("stock", currStock + "");
return "扣款成功 当前库存" + currStock;
} finally {
// 判断是否是当前线程的锁
if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
//释放锁
redisTemplate.delete(lockKey);
}
}
}
此时,这把分布式锁可以说是较为完善了,那么我们再来思考一个问题
我们获取一把30s的锁是能保证30s内此线程能完成此接口的业务逻辑呢?万一遇到mysql慢查询?一条sql执行一分钟?
采取解决解决方案:锁续命
因为分布式问题存在比较多的不唯一性,实现起来较为麻烦。此时我们引入 redisson 来解决分布式锁的问题
引入依赖
<!-- redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
配置类
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Bean
public Redisson redisson() {
final String address = "redis://" + host + ":" + port;
Config config = new Config();
config.useSingleServer()
.setAddress(address)
.setTimeout(3000)
.setPingConnectionInterval(1000)
.setDatabase(0);
return (Redisson) Redisson.create(config);
}
}
使用redisson获取分布式锁
@RequestMapping
public String testStock() {
String lockKey = "testStock";
RLock lock = redisson.getLock(lockKey);
try {
boolean tryLock = lock.tryLock(0L, TimeUnit.SECONDS); //获取锁
if (!tryLock) {
return "服务器繁忙";
}
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if (stock < 0) {
//todo 业务逻辑
return "库存不足";
}
//todo 业务逻辑
int currStock = stock - 1;
redisTemplate.opsForValue().set("stock", currStock + "");
return "扣款成功 当前库存" + currStock;
} finally {
lock.unlock();//释放锁
}
}
redisson lock.lock()方法底层也是获取一把过期时间为30s的锁,帮我们实现了锁续命的问题。(自行阅读代码,就不赘述了)
推荐阅读