单机架构场景解决并发问题
1: 无锁版商品购买
public boolean snatch(int goodsId){
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(String.valueOf(goodsId));
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(String.valueOf(goodsId),String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
return true;
}
问题 : 当大量用户抢购时候,非常容易造成某个商品的重复出售问题; 不会有超卖问题;redis是单线程的;
2: Lock加锁版本商品
public boolean snatch1(int goodsId){
Lock lock = new ReentrantLock();
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(String.valueOf(goodsId));
int stock = Integer.parseInt(stockStr);
lock.lock();
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(String.valueOf(goodsId),String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
lock.unlock();
return true;
}
问题 : 没锁住获取库存的代码,导致很多用户同样会出现问题
3: synchronized加锁
public synchronized boolean snatch2(int goodsId){
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(String.valueOf(goodsId));
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(String.valueOf(goodsId),String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
return true;
}
问题 : 锁住了整个方法,但是导致单体下不适用抢购代码
4: nginx搭建演示
upstream redis{
server 192.168.25.111:8080;
server 192.168.25.111:8081;
server 192.168.25.111:8082;
}
location /dxx
{
proxy_pass http://redis/;
}
集群架构场景解决并发问题
1: 基于SETNX锁
https://redis.io/commands/setnx/
public boolean snatch3(int goodsId){
//redis的setnx命令;如果存在这个值,就不会再设置了;
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent("lock", "ldx");
if(!locked){ //没拿到锁,代表获取商品失败
return false;
}
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(String.valueOf(goodsId));
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(String.valueOf(goodsId),String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
//解锁
stringRedisTemplate.delete("lock");
return true;
}
问题 : 有死锁bug,当代码出现异常,不会进行解锁
2: try解决死锁BUG
public boolean snatch4(int goodsId){
//redis的setnx命令;如果存在这个值,就不会再设置了;
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent("lock", "ldx");
if(!locked){ //没拿到锁,代表获取商品失败
return false;
}
try{
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(String.valueOf(goodsId));
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(String.valueOf(goodsId),String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
return true;
}finally {
stringRedisTemplate.delete("lock");
}
}
问题 : 多个商品之间,拥有同样的锁;如果是不同商品,会把其他人的锁给解掉
3: 多商品锁BUG
public boolean snatch5(int goodsId){
//每个商品都是自己单独的锁
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), "ldx");
if(!locked){ //没拿到锁,代表获取商品失败
return false;
}
try{
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(goodsId+"stock");
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(goodsId+"stock",String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
return true;
}finally {
//释放锁
stringRedisTemplate.delete(String.valueOf(goodsId));
}
}
问题 : 如果程序执行时,加上锁.这时候有一台机器重启了,导致代码没有解锁
4: 添加超时机制,解决被锁问题
public boolean snatch6(int goodsId){
//设置超时时间,指定5秒释放锁;
//Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), "ldx");
//stringRedisTemplate.expire(String.valueOf(goodsId),5, TimeUnit.SECONDS);
//保证原子性
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), "ldx",5, TimeUnit.SECONDS);
if(!locked){ //没拿到锁,代表获取商品失败
return false;
}
try{
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(String.valueOf(goodsId));
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(String.valueOf(goodsId),String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
return true;
}finally {
//释放锁
stringRedisTemplate.delete(String.valueOf(goodsId));
}
}
一般的场景下,这样的代码已经可以使用了.基本上很难复现问题,就算有问题,也直接修改数据库排查就好了.但是在真实的互联网大并发的场景下,这个肯定是不行的.因为他们可以达到1s生成1万甚至10万订单的情况.
问题 : 多个请求依次来进行处理,其中某一个请求处理时间需要 10s时间, 第二个请求需要8s时间,第三个请求需要4s时间;但是当前锁的超时时间是5s;可能会导致循环交替解锁,进而引发锁失效的逻辑BUG.
5: 解决锁失效问题
public boolean snatch7(int goodsId){
//设置超时时间,指定5秒释放锁;
//Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), "ldx");
//stringRedisTemplate.expire(String.valueOf(goodsId),5, TimeUnit.SECONDS);
//定义一个随机UUID
String lockValue = "lockldx" + System.currentTimeMillis() + Math.random() + goodsId;
//保证原子性
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), lockValue,5, TimeUnit.SECONDS);
if(!locked){ //没拿到锁,代表获取商品失败
return false;
}
try{
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(goodsId+"stock");
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(goodsId+"stock",String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
return true;
}finally {
//释放锁
if(lockValue.equals(stringRedisTemplate.opsForValue().get(String.valueOf(goodsId)))){
stringRedisTemplate.delete(String.valueOf(goodsId));
}
}
}
问题 : 这样解决了自己释放自己的锁的问题,但是没有本质上解决这个超时时间控制的问题
6: 刷新超时时间
public boolean snatch8(int goodsId){
//设置超时时间,指定5秒释放锁;
//Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), "ldx");
//stringRedisTemplate.expire(String.valueOf(goodsId),5, TimeUnit.SECONDS);
//定义一个随机UUID
String lockValue = null;
try {
lockValue = "lockldx" + System.currentTimeMillis() + Math.random() + InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
lockValue = UUID.randomUUID().toString();
}
int locktime = 5;//超时时间,单位s
//保证原子性
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), lockValue,locktime, TimeUnit.SECONDS);
if(!locked){ //没拿到锁,代表获取商品失败
return false;
}
String lockValue2 = lockValue;
try{
long now = System.currentTimeMillis();//获取锁的时间
//开启线程,去对过期时间实时扫描
Thread th = new Thread(){
public void run() {
while (true){
//线程是否中断
if(Thread.currentThread().isInterrupted()){
break;
}
//如果获取到了,代表已经超时了
stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(goodsId), lockValue2, locktime/2, TimeUnit.SECONDS);
}
}
};
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(goodsId+"stock");
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(goodsId+"stock",String.valueOf(stock));
//th.stop();
th.interrupt();
}else{
System.out.println("stock is zero");
//th.stop();
th.interrupt();
return false;
}
return true;
}finally {
//释放锁
if(lockValue.equals(stringRedisTemplate.opsForValue().get(String.valueOf(goodsId)))){
stringRedisTemplate.delete(String.valueOf(goodsId));
}
}
}
问题 : 这种方案初步看起来没问题,但是经不起推敲,因为线程的不可控以及其他的处理.所以我们使用开源组件Redisson成熟的方案
7: 基于Redisson实现
public boolean snatch8(int goodsId){
//定义一个随机UUID
String lockValue = null;
try {
lockValue = "lockldx" + System.currentTimeMillis() + Math.random() + InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
lockValue = UUID.randomUUID().toString();
}
int locktime = 5;//超时时间,单位s
RLock lock = redissonClient.getLock(lockValue);
lock.lock(locktime,TimeUnit.SECONDS);
//lock.tryLock(locktime,TimeUnit.SECONDS);//不阻塞
try{
//获取商品库存
String stockStr = stringRedisTemplate.opsForValue().get(goodsId+"stock");
int stock = Integer.parseInt(stockStr);
if(stock > 0){
System.out.println("buy success, stock :" + stock--);
stringRedisTemplate.opsForValue().set(goodsId+"stock",String.valueOf(stock));
}else{
System.out.println("stock is zero");
return false;
}
return true;
}finally {
//释放锁
lock.unlock();
}
}