`

Redis分布式锁解决抢购问题

    博客分类:
  • JAVA
阅读更多
废话不多说,首先分享一个业务场景-抢购。一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次都去数据库查询显然是不合适的,因此把库存信息存入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));
}

大功告成了!比synchronized快了不知道多少倍,再也不会被老板骂了!
分享到:
评论

相关推荐

    基于Redis实现分布式锁以及任务队列

     双十一刚过不久,大家都知道在天猫、京东、苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这...

    【大厂面试题】分布式详细解析及其答案

    2、Redis锁结合使用场景,需要注意什么?超时问题如何解决 在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及...

    商品秒杀系统(限时抢购系统)

    zookeeper分布式锁 自定义注解 统一封装返回 切面使用 设计模式使用 事物、回滚使用 docker、nginx使用 图片服务器OSS使用 stram、lambda使用 多线程、线程池使用 定时任务使用 短信验证、邮件服务使用 JWT验证TOKEN...

    redis-cluster-4.0.1集群镜像

    什么是redis? Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。 它通过提供多种键值数据类型来适应不同场景下的存储需求。...(秒杀、抢购、12306等等) 5. 应用排行榜。 6. 网站访问统计。

    alibabacloud-redis-training-demo

    2021阿里云开发者社区演示资料课程介绍演示资料,代码,视频走进Redis Redis的整体架构,... Redis的高并发实战:抢购系统IO模型和问题,资源竞争与分布式锁,如何利用Redis高并发原理做抢购系统。 Redis的生态DMS /

    PHP商品秒杀问题解决方案实例详解【mysql与redis】

    主要介绍了PHP商品秒杀问题解决方案,结合实例形式详细分析了php结合mysql与redis实现商品秒杀功能的相关操作技巧及注意事项,需要的朋友可以参考下

    Redis集群的相关详解

    注意!要求使用的都是redis3.0以上的版本,...分布式集群架构中的session分离。 聊天室的在线好友列表。 任务队列。(秒杀、抢购、12306等等) 应用排行榜。 网站访问统计。 数据过期处理(可以精确到毫秒) 2.Redis集

    Java实现秒杀系统实现

    秒杀系统是一种高并发的应用场景,主要特点是在极短的时间内,大量用户同时访问系统进行抢购操作...监控和日志:对秒杀系统进行监控,关注系统的吞吐量、响应时间等指标,及时发现并解决问题。同时记录系统日志,......

    second:秒杀抢购系统,通过Rocketmq、redis、布隆过滤器、主从服务器、验证码等技术进行优化

    功能实现的目标1-基于springBoot平台,集成Redis,实现注册、登录、商品展示、商品详情、订单详情以及秒杀抢购功能2-解决缓存穿透、缓存雪崩及缓存击穿等问题3-添加消息队列消峰4-利用JMeter进行压测5-master-slave...

    基于springBoot实现的一个分布式电商系统,本科毕业设计,java web全栈开发学习模板

    基于springBoot实现的一个分布式电商系统,开发里程: 4月21日 搭建springBoot平台 4月21日 集成mybatis框架 4月21日 集成mycat数据库中间件 4月22日 后台认证集成shiro,采用单库 商品数据处理采用分库分表 商城...

    基于java开发的综合性的B2C平台系统源码(包括前台商城系统和后台管理系统)+项目说明.zip

    采用分布式锁+RQ 实现抢购秒杀 采用 RabbitMQ 实现数据最终一致性 【备注】 主要针对计算机相关专业的正在做毕设的学生和需要项目实战的Java学习者。 也可作为课程设计、期末大作业。包含:项目源码、项目说明等,该...

    秒杀系统企业级实战应用(真实工业界案例)视频教程

    本课程是基于大型互联网的真实架构进行讲解,秒杀系统技术架构(Spring+SpringMVC+Mybatis+Dubbo+Druid+Ehcache+Redis+RabbitMQ+Zookeeper+jQuery+ajax),技术涵盖JavaEE技术,分布式服务技术,高并发技术,缓存技术...

    spring-boot-seckill:spring-boot分布式高并发秒杀系统

    分散秒杀系统开发环境JDK1.7,Maven,Mysql,Eclipse,SpringBoot1.5.10,zookeeper3.4.6,kafka_2.11,redis-2.8.4,curator-2.10.0项目介绍SpringBoot开发案例从0到1构建分布式秒杀系统,项目案例基本成型,逐步...

    PHP秒杀系统 高并发高性能的极致挑战 从万次到亿万次的性能优化,从单机到分布式的架构升级

    本课由360架构师亲授,以360真实秒杀系统为切入点, 从秒杀的功能入手,分层递进讲解,逐步让大家掌握系统的设计、架构以及优化...加入机器人服务识别,自动完成安全认证 黄牛无法重复、多次下单 保证抢购系统公平稳定

    百度地图毕业设计源码-spring-boot-seckill:分布式秒杀系统案例

    分布式秒杀系统 开发环境 JDK1.7、Maven、Mysql、Eclipse、SpringBoot1.5.10、zookeeper3.4.6、kafka_2.11、redis-2.8.4、curator-2.10.0 启动说明 启动前 请配置 application.properties 中相关redis、zk以及kafka...

    基于微服务SpringBoot的商城高并发抢单系统:商城高并发抢单系统-秒杀系统,快速构建自己的商品秒杀平台

    该项目是模拟互联网高并发场景实现了一套商城秒杀系统,项目前后端分离,实现的功能包括用户登录、查看商品列表、查看秒杀商品详情、秒杀商品下单、下单结果通过邮件(短信)通知用户、用户超时...在秒杀抢购之前我们用

    商城秒杀系统源代码及数据库脚本.zip

    Spring Boot实战者,微服务或分布式系统架构实战者,秒杀系统和高并发实战者,中间件实战者 你将会学到: 学习如何基于Spring Boot构建秒杀系统或者高并发业务系统,以及构建系统时采用的前后端技术栈 内容简介: 本...

    Dubbox+Spring Boot+Docker电子书

    本书围绕秒杀抢购应用场景 ,对当下流行的 Dubbox+Sp ing Boot+ Docker 微服务架构解决方案进行讲解。主要内容包括微服务架构介绍、 Dub box 原理及运用、使用 Spring Boot实现微服务 使用 ActiveMQ Redis 承载高兴...

    开源bbs源码java-miaosha:miaosha

    2.tomcat内嵌的容器优化方案来解决线程瓶颈问题;通过管道优化方案来简化了非keepalive下的网络建联开销; 分布式扩展: 1.负载均衡设计; 2.水平扩展/垂直扩展; 查询优化之多级缓存: 1.多级缓存屏障系统,优化了...

Global site tag (gtag.js) - Google Analytics