一般电商网站都会遇到秒杀、特价之类的活动,大促活动有一个共同特点就是访问量激增,在高并发下会出现成千上万人抢购一个商品的场景。虽然在系统设计时会通过限流、异步、排队等方式优化,但整体的并发还是平时的数倍以上,参加活动的商品一般都是限量库存,如何防止库存超卖,避免并发问题呢?分布式锁就是一个解决方案。
“分布式锁”是用来解决分布式应用中“并发冲突”的一种常用手段,实现方式一般有基于zookeeper及基于redis二种
先看下下面的代码,实现方式01,没有锁,当5个线程同时访问就乱了。
private String impl01() {
String stockStr = stringRedisTemplate.opsForValue().get("stock");
if (StringUtils.isEmpty(stockStr)) return "库存为空";
int stock = Integer.parseInt(stockStr);
if (stock > 0) {
int newStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", newStock + "");
System.out.printf("扣减成功 from %s to %s \n", stock, newStock);
return "扣减成功";
} else {
System.out.printf("扣减失败 from %s \n", stock);
return "扣减失败";
}
}
首先想到是加 synchronized。 而 synchronized 锁是属于JVM级别的,也就是在在单个机器上有效。而在微服务架构下,一般都是集群部署,一个服务会部署到不同计算机上。就又有问题了。
使用SETNX实现分布式锁,
try{
// 先获得 锁,如果获得失败,则提示错误码
Boolean lock_lock = stringRedisTemplate.opsForValue().setIfAbsent("LOCK_LOCK", "1");
if (!lock_lock) return "获得锁失败,无法继续";
....这里是上面的业务代码。略
}finally{
// 完事后释放 锁
stringRedisTemplate.delete("LOCK_LOCK");
}
不过它也是有缺陷的: 超时 设置的10s时间 时,导致未完成任务,就把锁释放了,就乱套了。准确的10s真的合适吗, 如果在真正高并发的场景下,可能锁就会面临“一直失效”或“永久失效”。 1.存在请求释放锁时释放掉的并不是自己的锁 2.超时时间过短,存在代码未执行完便自动释放
Redisson 是一个连接 Redis 的客户端包。充分的利用了 Redis 键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。比如 锁 等。
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
代码
private synchronized String impl06() {
RLock rLock = redisson.getLock("LOCK_LOCK_2");
try {
rLock.lock();
//----------------------
String res = "";
String stockStr = stringRedisTemplate.opsForValue().get("stock");
if (StringUtils.isEmpty(stockStr)) return "库存为空";
int stock = Integer.parseInt(stockStr);
if (stock > 0) {
int newStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", newStock + "");
res = String.format("扣减成功 from %s to %s \n", stock, newStock);
System.out.println(res);
} else {
res = String.format("扣减失败 from %s \n", stock);
System.out.println(res);
}
return res;
} finally {
// 完事后释放 锁
rLock.unlock();
}
}
@Bean
RedissonClient Redisson() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://192.168.1.111:6379")
.setDatabase(1);
return Redisson.create(config);
}
在思考解决方案时我们首先想到CAP原则(一致性、可用性、分区容错性),
并发量没有那么高,可以用zookeeper来做分布式锁,但是它的并发能力远远不如Redis。如果你对并发要求比较高的话,那就用Redis