Java高并发保证商品下单库存准确性示例

之前面试的时候有人问我怎么保证秒杀系统中库存的准确性,我当时想的是通过mysql的for update加锁的方式来实现,这种方式虽然能一定程度保证库存的准确性,但是并不适用于高并发下的秒杀系统
下面我来讲下怎么用Redis的increment方法和Redisson分布式锁来更加快速高效的保证库存准确性

首先放上代码

		// 先去查询商品库存信息
        // 优先去Redis里面去查,通过increment保证线程安全,如果Redis里面的库存显示不足则去刷新数据库库存
        long stockNum = redisUtil.decr("stock-num" + addOrderParam.getProductNo(), addOrderParam.getNum());
        // 如果Redis库存为空则去数据库获取新的库存信息
        if (stockNum < 0) {
            // 通过Redisson配置分布式全局锁保证数据安全性
            RLock stockLock = redissonClient.getLock("GetProductNum" + addOrderParam.getProductNo());
            try {
                //尝试加锁, 最多等待3秒, 10秒后自动解锁
                boolean getStockLock = stockLock.tryLock(3, 10, TimeUnit.SECONDS);
                if (getStockLock) {
                    ApiResult<Integer> apiResultNum = stockFeignService.getProductNum(addOrderParam.getProductNo());
                    log.info("商品库存数量:" + apiResultNum.toString());
                    if (apiResultNum.getCode() != 200 || apiResultNum.getData() < 1 || apiResultNum.getData() < addOrderParam.getNum()) {
                        throw new BusinessException(500, "商品库存不足");
                    }
                    redisUtil.set("stock-num" + addOrderParam.getProductNo(), apiResultNum.getData() - addOrderParam.getNum());
                }
            } catch (Exception e) {
                log.error("新增订单查询商品库存异常:" + e.getMessage(), e);
                throw new BusinessException(500, "查询商品库存信息异常");
            } finally {
                //解锁
                if (stockLock.isLocked() && stockLock.isHeldByCurrentThread()) {
                    stockLock.unlock();
                }
            }
        }

        // 通过MQ异步去处理扣减库存操作,加快下单响应速度
        rocketMQTemplate.asyncSend("reduce_stock_mq", JSON.toJSONString(addOrderParam), new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("推送扣减库存消息成功:" + JSON.toJSONString(addOrderParam));
            }

            @Override
            public void onException(Throwable throwable) {
                log.error("推送扣减库存消息失败:" + JSON.toJSONString(addOrderParam) + "------>" + throwable.getMessage());
            }
        });

redisUtil.decr里面调用的是increment方法

	/**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return long
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

对这个方法的解释如下:

Redis的increment方法的实现原理非常简单,它实际上是对key对应的值进行加1或减1操作,并返回操作后的值。具体实现方式如下:

 - 如果key不存在,则将其值设置为0。
 - 如果key对应的值可以被解释为数字,则将其进行加1或减1操作,并返回操作后的值。
 - 如果key对应的值不能被解释为数字,则返回错误信息。
 
需要注意的是,Redis的increment方法是原子操作,也就是说在高并发的环境下可以保证其正确性。
如果多个客户端同时对同一个key进行自增或自减操作,Redis会按照客户端的请求顺序进行处理,并保证最终结果是正确的。

可以看到这个方法是线程安全的,我们根据商品ID或者编号将库存放到Redis中,然后通过increment来计算库存保证准确性

当Redis里面库存stockNum不足时,我们再去库存系统获取新的库存信息,为了保证获取库存这个操作的安全性,我们需要用到Redisson分布式锁

这个Redisson使用非常简单
引入jar包,再加上一个配置就可以了

		<!--引入redisson作为所有分布式锁,分布式对象等功能-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.0</version>
        </dependency>
package com.sakura.common.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Sakura
 * @date 2023/8/12 10:56
 */
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient getRedisson() {
        //1.创建配置
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://192.168.43.118:6379")
                //.setAddress("redis://192.168.43.128:6379") // 如果是集群模式这里可以填多个Redis地址
                .setPassword("px123456"); // 如果有密码就需要加上这个
        //2.根据config创建处RedissonClient实例
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

在要使用的地方通过redissonClient获取一个锁就可以

@Autowired
private RedissonClient redissonClient;

RLock lock = redissonClient.getLock("myLock");

lock.lock();
try {
    // 执行需要保护的操作
} finally {
    lock.unlock();
}

最后我们将扣减库存的操作交给MQ

package com.sakura.stock.mq;

import com.alibaba.fastjson.JSONObject;
import com.sakura.common.constant.RocketMqConstant;
import com.sakura.stock.mapper.StockMapper;
import com.sakura.stock.param.ReduceStockParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author Sakura
 * @date 2023/8/12 16:48
 */
@Component
@Slf4j
@RocketMQMessageListener(topic = RocketMqConstant.REDUCE_STOCK_TOPIC, consumerGroup = RocketMqConstant.REDUCE_STOCK_CONSUMER_GROUP)
public class ReduceStockMQListener implements RocketMQListener<ReduceStockParam> {

    @Autowired
    private StockMapper stockMapper;

    @Override
    public void onMessage(ReduceStockParam message) {
        log.info("MQ收到扣减库存消息:" + JSONObject.toJSONString(message));
        int num = stockMapper.decreaseStock(message.getProductNo(), message.getNum());
        if (num < 1) {
            log.info("MQ扣减库存信息异常:" + JSONObject.toJSONString(message));
        }

    }
}

这里我用的是乐观锁的方式,要是想更安全可以用悲观锁,但是对应的性能肯定会有所下降

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sakura.stock.mapper.StockMapper">

    <update id="decreaseStock">
        update t_stock
        set product_num = product_num - #{num}
        where product_no = #{productNo} and product_num - #{num} &gt;= 0
    </update>

</mapper>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java仓库物资出入库管理系统是一种通过使用Java编程语言开发的电脑软件,用于管理仓库中物资的出入库情况。该系统主要包括以下几个功能模块: 1. 物资入库管理:系统提供了物资入库功能,可以记录物资的数量、规格、生产日期等信息,并自动生成入库单。同时,该系统可以自动更新物资库存。 2. 物资出库管理:系统提供物资出库功能,可以根据需求单进行物资的出库操作。出库时,系统会自动更新物资库存,并记录出库时间、目的地等信息。 3. 物资查询与统计:系统可以根据不同的查询条件,如物资名称、规格、编码等,实现物资的查找。同时,还可以对物资的入库、出库情况进行统计分析,为仓库管理人员提供决策支持。 4. 库存管理:该系统能够实时更新库存信息,提供库存预警功能,当库存数量低于一定阈值时,系统会发出警告通知,提醒库管人员及时补充物资。 5. 报表打印:系统具备报表打印功能,可以根据用户需求生成各项报表,如入库单、出库单、库存清单等。 通过采用Java编程语言进行开发,该系统具有良好的平台兼容性,可以在不同操作系统上运行。同时,Java语言还具有较高的安全性和可靠性,可以有效保护仓库物资和数据的安全。 总之,Java仓库物资出入库管理系统可以提高仓库物资管理的效率和准确性,为仓库管理人员提供方便快捷的信息处理和管理工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子非衣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值