`

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分布式锁实现抢单秒杀

    在IT行业中,尤其是在高并发的电子商务系统中,"redis分布式锁实现抢单秒杀"是一个常见的挑战。这个场景模拟了多个用户同时参与秒杀活动,系统需要确保库存的准确性和抢单的公平性,避免超卖和数据不一致的问题。...

    Redis分布式锁----乐观锁的实现,以秒杀系统为例.rar

    Redis分布式锁是构建高并发系统中的重要工具,尤其在处理如秒杀、抢购等场景时,能够确保数据的一致性和正确性。本文件“Redis分布式锁----乐观锁的实现,以秒杀系统为例”主要探讨了如何利用Redis实现乐观锁,并...

    redis分布式锁

    Redis分布式锁提供了一种简单有效的方式来解决分布式系统中的并发问题。通过对Redis命令的巧妙组合,可以实现高性能且可靠的锁机制。然而,在实际应用中还需要考虑到锁的公平性、性能优化等问题。随着分布式系统的日...

    Spring Boot+Redis 分布式锁:模拟抢单.pdf

    在抢单场景中,当多个客户端同时尝试抢购某一资源时,分布式锁可以确保在任何时刻只有一个客户端能够成功获得该资源。此外,通过控制锁的过期时间,可以有效避免资源长时间被占而无法释放的情况,确保系统的高可用性...

    分布式锁原理讲解视频资料

    分布式锁的目的是解决在分布式环境下,多节点并发访问同一资源时可能出现的数据不一致问题。它通常基于分布式协调服务,如Zookeeper或Redis等,通过心跳检测和超时机制来确保锁的正确释放。当一个节点获取到锁后,...

    如何利用Redis锁解决高并发问题详解

    Redis锁在高并发场景下被广泛使用,主要是因为其高效的数据访问速度以及丰富的数据结构,使得它成为解决并发问题的有效工具。Redis直接操作内存,相比传统数据库从硬盘读取数据,速度上有显著提升,减轻了数据库...

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

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

    解决高并发环境下Redis连接超时与超卖问题

    总的来说,通过结合Redis的乐观锁机制、连接池优化和分布式锁,我们可以有效地解决高并发环境下的连接超时和超卖问题,保障系统的稳定性和正确性。在实际项目中,还需要根据具体业务需求和系统架构进行调整和优化。

    分布式锁三种实现方式及对比

    - **Redis分布式锁**:性能优于数据库,但锁删除失败或过期时间控制不当可能导致问题,同样需要轮询,占用CPU资源。 - **Zookeeper分布式锁**:虽然性能略逊于Redis,但其高可用性和可靠性更优,实现复杂度介于...

    SpringBoot使用Redisson实现分布式锁(秒杀系统)

    主要为大家详细介绍了SpringBoot使用Redisson实现分布式锁,秒杀系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    100讲带你实战基于Redis的高并发预约抢购系统.zip

    1. **分布式锁**:在抢购过程中,防止同一商品被多个用户同时购买,我们可以利用Redis实现分布式锁。例如,40_案例实战:实现一个最简单的分布式锁,讲解了如何通过设置和删除特定键来确保同一资源在同一时刻只能被...

    分布式锁与信号量avaWeb-mast开发笔记

    分布式锁是解决多节点间数据一致性的重要工具,它允许在同一时间只有一个客户端对特定资源进行操作。常见的分布式锁实现有基于Zookeeper、Redis以及数据库乐观锁等。例如,使用Redis的SETNX命令可以实现原子性的设置...

    第九讲:分布式锁的原理及应用&秒杀设计实现.pdf

    在单机环境中,我们通常使用线程锁(如Java的`synchronized`关键字或`Lock`接口)来保证线程安全,但在分布式环境里,由于服务分布在不同的节点上,单机锁无法解决跨节点的并发问题,这时就需要引入分布式锁。...

    2021-07-28-.NET 6 秒杀项目---分布式锁落地实战.rar

    在秒杀场景下,分布式锁可以避免多个用户同时抢购同一商品,确保每个商品只有一个幸运儿。.NET 6 是微软推出的最新版本的跨平台开发框架,具有更快的性能、更好的依赖注入和更丰富的API,是构建高性能秒杀应用的理想...

    100讲带你实战基于Redis的抢购系统

    5. **分布式锁**:在多实例环境中,使用Redis的`SETNX`或`REDIS-SCRIPTS`实现分布式锁,确保并发操作的正确性。 6. **计数器**:利用Redis的原子操作,可以统计抢购人数,例如使用`INCRBY`增加购买次数。 7. **...

    通过实例解析Java分布式锁三种实现方法

    Java分布式锁是指在分布式系统中,为了避免多个节点同时操作同一个资源而造成的数据不一致和安全问题,需要使用分布式锁来保护共享资源。分布式锁可以分为三种实现方法:基于数据库实现分布式锁、基于缓存(Redis等...

    php(TP5)+redis实现秒杀抢购(不限制用户购买次数和限制用户购买次数)

    1. **分布式锁**:利用Redis的`SETNX`命令设置分布式锁,确保同一时间内只有一个请求能进行库存扣减,保证数据一致性。 2. **事务操作**:使用Redis的`MULTI`和`EXEC`命令进行事务处理,确保库存减少和订单创建的...

    redisframework-master.zip

    总结来说,“redisframework-master”项目为开发人员提供了一个实用的工具,用以利用 Redis 实现分布式锁,解决高并发场景下的资源争抢问题,保证操作的原子性和幂等性,对于构建高可用、高并发的分布式系统具有重要...

Global site tag (gtag.js) - Google Analytics