解决商品超卖的方案和思路

文章讨论了电商系统中防止商品超卖的两种主要解决方案:悲观锁和乐观锁。悲观锁通过同步关键字synchronized限制并发,但可能导致用户体验下降;乐观锁则利用Redis消息队列和事务处理,保证数据一致性且性能较高。文章提供了使用Redis实现乐观锁防止超卖的Java代码示例。
摘要由CSDN通过智能技术生成

1. 确认需求和技术方案

一般在电商系统和=或者秒杀系统中都有出现一种商品超卖的问题存在,原因就是再大量并发请求的时候导致了数据库的脏读和不可重复读,从而造成了商品的下单数量大于了商品的库存数量。
一般来说常用的解决超卖的方案有两种:

  • 方案一:悲观锁(不推荐)
  • 方案二:乐观锁

2. 使用两种方案来解决问题

方案一:悲观锁
  对于方案一的解决方法有很多,比如在对要加锁的方法上加入synchronized同步字段,使接口被访问时至多同时只能被一个线程访问,其他的请求都会处于阻塞状态,直至上一个请求处理结束,线程被释放,下一个请求再进来,使每一次进来的请求都需要排队,一次一个。
 这种方案可以有效的解决超卖问题,每一次请求都可以保证数据库数据的一致性,但是也有缺点,用户的体验就不是不好了,后面的的用户在等待很久之后可能还是会提示请求失败,商品已经买完了。

    @PutMapping("")
    public Object doorder(@ApiParam(name = "DTO" @RequestBody DTO dto){
        try {
            //悲观锁
            synchronized (this){
                //业务层的减库存方法和下单方法
                ...
                return;
            }
        }catch (Exception e){   
        	//这里也可以使用自定义的方式捕获异常并返回想要异常
            e.printStackTrace();
            return ...;
        }
    }

 但需要强调的一点是synchronized不能和@Transactional一起使用,也不要在Service层的方法上加synchronized,因为Service层的业务方法大多加有事务控制,和悲观锁联合使用的时候,悲观锁解锁的时间比事务提交的时间早,可能会导致少量请求在上一次事务未完全提交就进来,最终还是会导致少量的超卖出现。所以尽量在接口中加上同步代码块来控制业务的访问。

方案二:乐观锁
 对于方案二来说,并发数较大的情况下就需要使用消息队列来保证数据的一致性。常用的有Redis消息队列来防止超卖,下面给出的是用Redis的消息队列来进行商品超卖的控制。

  • 一:获取商品库存。
  • 二:判断库存是否充足,如果充足则继续执行,否则返回错误信息。
  • 三:使用Redis作为消息队列,将购买请求放入队列中。
  • 四:开启一个线程来消费队列中的购买请求,对于每一个请求,按照以下步骤处理。
    • a:获取商品库存。
    • b:判断库存是否充足,如果充足则继续执行,否则返回错误信息。
    • c:将购买请求的处理结果返回给用户。

下面是完整的Java代码实现。

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>3.0.0</version>
</dependency>
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;

public class RedisDemo {

    public static void main(String[] args) {

        // 创建redis连接池
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(8);
        jedisPoolConfig.setMaxIdle(8);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "localhost", 6379, 3000, null);

        // 乐观锁实现防止超卖
        for (int i = 0; i &lt; 100; i++) {
            new Thread(() -&gt; {
                String watchKey = "goods:001";
                String userKey = "user:001";
                String requestId = "request:001";
                int buyNum = 1;

                Jedis jedis = null;
                try {
                    jedis = jedisPool.getResource();
                    // 监视watchKey
                    jedis.watch(watchKey);
                    // 获取商品库存
                    int stock = Integer.parseInt(jedis.get(watchKey));
                    if (stock &lt; buyNum) {
                        jedis.unwatch();
                        System.out.println("库存不足!");
                        return;
                    }

                    // 开始事务
                    Transaction transaction = jedis.multi();
                    // 减少库存
                    transaction.decrBy(watchKey, buyNum);
                    // 成功的订单数
                    int successCount = Integer.parseInt(jedis.get(userKey));
                    // 提交事务
                    transaction.exec();
                    // 增加成功的订单数
                    successCount++;
                    jedis.set(userKey, String.valueOf(successCount));

                    System.out.println(Thread.currentThread().getName() + " 抢购成功!");
                } finally {
                    if (jedis != null) {
                        jedis.close();
                    }
                }
            }, "用户" + (i + 1)).start();
        }

        // 消费消息队列,处理购买请求
        new Thread(() -&gt; {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                while (true) {
                    // 从消息队列中获取购买请求
                    String request = jedis.rpop(requestId);
                    if (request == null) {
                        continue;
                    }

                    // 开始事务
                    Transaction transaction = jedis.multi();
                    // 获取商品库存
                    int stock = Integer.parseInt(jedis.get(watchKey));
                    if (stock &lt; buyNum) {
                        continue;
                    }
                    // 减少库存
                    transaction.decrBy(watchKey, buyNum);
                    // 提交事务
                    transaction.exec();
                    // 返回结果给用户
                    jedis.set(request, "success");
                }
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }, "消费者").start();
    }
}

 使用Redis防止超卖的一种很常见的做法,其主要是通过Redis的原子操作incr或decr实现。其特点有如下几点:

  1. 快速高效:Redis是内存级别的缓存,读写速度非常快,可以满足高并发场景下的业务需求,提升系统性能。

  2. 可靠性高:Redis支持事务和CAS命令,防止并发操作导致数据不一致的问题,保证数据的一致性和可靠性。

  3. 简单易用:Redis有丰富的数据结构和详细的API文章,易上手和维护。

  4. 运维成本低:Redis有非常好的集群和监控方案,也支持自动化运维,能减少运维成本和复杂度。

  5. 灵活可扩展:Redis支持主从复制和分片,可以根据业务需求快速扩展集群,以应对高并发场景的需求。

3.总结

 悲观锁和乐观锁的方式相比较,悲观锁是在对库存表进行操作时预先加锁,确保同一时刻只有一个线程能够访问和修改库存数据。但是因为加锁的原因,就会导致其他线程需要等待锁释放才能进行操作,影响并发性能,并且给用户的体验感非常差。
 而使用Redis,基于Redis的原子操作能够更好地保证数据的安全性,并且性能更高,在大并发的场景下会有很好的性能表现。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值