Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 344|回复: 0

Redis分布式锁解决抢购问题

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-5-18 22:49:10 | 显示全部楼层 |阅读模式

    首先分享一个业务场景-抢购。一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次都去数据库查询显然是不合适的,因此把库存信息存入Redis中,利用redis的锁机制来控制并发访问,是一个不错的解决方案。

    首先是一段业务代码:

    @Transactional
    public void orderProductMockDiffUser(String productId){ //1.查库存 int stockNum = stock.get(productId); if(stocknum == 0){ throw new SellException(ProductStatusEnum.STOCK_EMPTY); //这里抛出的异常要是运行时异常,否则无法进行数据回滚,这也是spring中比较基础的 }else{ //2.下单 orders.put(KeyUtil.genUniqueKey(),productId);//生成随机用户id模拟高并发 sotckNum = stockNum-1; try{ Thread.sleep(100); } catch (InterruptedExcption e){ e.printStackTrace(); } stock.put(productId,stockNum); } }

    这里有一种比较简单的解决方案,就是synchronized关键字。

    public synchronized void orderProductMockDiffUser(String productId)

    这就是java自带的一种锁机制,简单的对函数加锁和释放锁。但问题是这个实在是太慢了,感兴趣的可以可以写个接口用apache ab压测一下。

    ab -n 500 -c 100 http://localhost:8080/xxxxxxx

    下面就是redis分布式锁的解决方法。首先要了解两个redis指令
    SETNX 和 GETSET,可以在redis中文网上找到详细的介绍。
    SETNX就是set if not exist的缩写,如果不存在就返回保存value并返回1,如果存在就返回0。
    GETSET其实就是两个指令GET和SET,首先会GET到当前key的值并返回,然后在设置当前Key为要设置Value。

    首先我们先新建一个RedisLock类:

    @Slf4j
    @Component
    public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; /*** * 加锁 * @param key * @param value 当前时间+超时时间 * @return 锁住返回true */ public boolean lock(String key,String value){ if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){//setNX 返回boolean return true; } //如果锁超时 *** String currentValue = stringRedisTemplate.opsForValue().get(key); if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue)<System.currentTimeMillis()){ //获取上一个锁的时间 String oldvalue = stringRedisTemplate.opsForValue().getAndSet(key,value); if(!StringUtils.isEmpty(oldvalue)&&oldvalue.equals(currentValue)){ return true; } } return false; } /*** * 解锁 * @param key * @param value * @return */ public void unlock(String key,String value){ try { String currentValue = stringRedisTemplate.opsForValue().get(key); if(!StringUtils.isEmpty(currentValue)&&currentValue.equals(value)){ stringRedisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { log.error("解锁异常"); } } }

    这个项目是springboot的项目。首先要加入redis的pom依赖,该类只有两个功能,加锁和解锁,解锁比较简单,就是删除当前key的键值对。我们主要来说一说加锁这个功能。
    首先,锁的value值是当前时间加上过期时间的时间戳,Long类型。首先看到用setiFAbsent方法也就是对应的SETNX,在没有线程获得锁的情况下可以直接拿到锁,并返回true也就是加锁,最后没有获得锁的线程会返回false。 最重要的是中间对于锁超时的处理,如果没有这段代码,当秒杀方法发生异常的时候,后续的线程都无法得到锁,也就陷入了一个死锁的情况。我们可以假设CurrentValue为A,并且在执行过程中抛出了异常,这时进入了两个value为B的线程来争夺这个锁,也就是走到了注释*的地方。currentValue==A,这时某一个线程执行到了getAndSet(key,value)函数(某一时刻一定只有一个线程执行这个方法,其他要等待)。这时oldvalue也就是之前的value等于A,在方法执行过后,oldvalue会被设置为当前的value也就是B。这时继续执行,由于oldValue==currentValue所以该线程获取到锁。而另一个线程获取的oldvalue是B,而currentValue是A,所以他就获取不到锁啦。多线程还是有些乱的,需要好好想一想。
    接下来就是在业务代码中加锁啦:首要要@Autowired注入刚刚RedisLock类,不要忘记对这个类加一个@Component注解否则无法注入

     
    private static final int TIMEOUT= 10*1000; @Transactional public void orderProductMockDiffUser(String productId){ long time = System.currentTimeMillions()+TIMEOUT; if(!redislock.lock(productId,String.valueOf(time)){ throw new SellException(101,"换个姿势再试试") } //1.查库存 int stockNum = stock.get(productId); if(stocknum == 0){ throw new SellException(ProductStatusEnum.STOCK_EMPTY); //这里抛出的异常要是运行时异常,否则无法进行数据回滚,这也是spring中比较基础的 }else{ //2.下单 orders.put(KeyUtil.genUniqueKey(),productId);//生成随机用户id模拟高并发 sotckNum = stockNum-1; try{ Thread.sleep(100); } catch (InterruptedExcption e){ e.printStackTrace(); } stock.put(productId,stockNum); } redisLock.unlock(productId,String.valueOf(time)); }
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-15 09:01 , Processed in 0.067515 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表