最新新闻:

1.不多发2.不少发下单涉及的一些步骤(组图)

时间:2022-08-28 14:01:57来源:网络整理

注意,不要迷路;不断更新Java相关的技术和信息!!!

问题:

一件商品的库存只有100件,现在有1000个或更多用户购买,每个用户计划同时购买1到几件商品。如何保证库在高并发场景下是安全的。

1.不多

2.很多帖子

下订单涉及的一些步骤

1.下单

2.同时下单和储备库存

3.支付

4.付款成功扣除库存

5.取消订单

6.返回保留库存

何时抢占库存

选项 1:添加到购物车时预先购买库存。

选项 2:下订单时预留库存。

选项 3:付款时预留库存。

分析:

方案一:加入购物车并不代表用户一定会购买。如果此时开始预留库存,会导致想买的人没有加入购物车。不想购买的人继续囤积库存。显然,这种方法是不可取的。

方案二:商品加入购物车后,选择下单,此时去预留库存。用户选择付费,说明用户的购买欲望强于第一个方案。订单也有时间限制电商系统 高并发库存系统设计,比如半小时。半个多小时后,系统会自动取消订单,退回预留的库存。

选项3:在订单成功付款后保留库存。只有100个用户可以支付成功,900个用户支付失败。用户体验不好,就像你走在一条光明的路上,一路顺风顺水,突然被告知无法通行。而且,支付过程也是一个比较复杂的过程。如果和去库存结合起来,就会变得更加复杂。

总而言之:

选项2更合理。

重复订单问题

解决方案

/**
     * 先生成 token 保存到 Redis
     * token 作为 key , 并设置过期时间 时间长度 根据任务需求
     * value 为数字 自增判断 是否使用过
     *
     * @param user
     * @return
     */
public String createToken(User user) {
	String key = "placeOrder:token:" + user.getId();
	String token = UUID.randomUUID().toString();
	//保存到Redis
	redisService.set(key + token, 0, 1000L);
	return token;
}
/**

     * 校验下单的token是否有效
     * @param user
     * @param token
     * @return
     */
public Boolean checkToken(User user, String token) {
	String key = "placeOrder:token:" + user.getId();
	if (null != redisService.get(key + token)) {
		long times = redisService.increment(key + token, 1);
		if (times == 1) {
			//利用increment 原子性 判断是否 该token 是否使用
			return true;
		} else {
			// 已经使用过了
		}
		//删除
		redisService.remove(key + token);
	}
	return false;
}

当同一个用户或多个用户同时抢购一个产品时,如何安全地同时扣减库存?

数据库操作商品库存:

/**
 * Created by Administrator on 2017/9/8.
 */
public interface ProductDao extends JpaRepository<Product, Integer> {
	/**
     * @param pid 商品ID
     * @param num 购买数量
     * @return
     */
	@Transactional
	    @Modifying
	    @Query("update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1")
	    int reduceStock1(Integer pid, Integer num);
	/**
     * @param pid 商品ID
     * @param num 购买数量
     * @return
     */

	@Transactional
	    @Modifying
	    @Query("update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1 and  availableNum - ?2 >= 0")
	    int reduceStock2(Integer pid, Integer num);
}

下订单:

/**
     * 下单操作1
     *
     * @param req
     */
private int place(PlaceOrderReq req) {
	User user = userDao.findOne(req.getUserId());
	Product product = productDao.findOne(req.getProductId());
	//下单数量
	Integer num = req.getNum();
	//可用库存
	Integer availableNum = product.getAvailableNum();
	//可用预定
	if (availableNum >= num) {
		//减库存
		int count = productDao.reduceStock1(product.getId(), num);
		if (count == 1) {
			//生成订单
			createOrders(user, product, num);
		} else {
			logger.info("库存不足 3");
		}
		return 1;
	} else {
		logger.info("库存不足 4");
		return -1;
	}
}
/**
     * 下单操作2
     *
     * @param req
     */
private int place2(PlaceOrderReq req) {

电商系统 高并发库存系统设计_电商系统订单设计_龙宝参茸高补贴难去高库存

User user = userDao.findOne(req.getUserId()); Product product = productDao.findOne(req.getProductId()); //下单数量 Integer num = req.getNum(); //可用库存 Integer availableNum = product.getAvailableNum(); //可用预定 if (availableNum >= num) { //减库存 int count = productDao.reduceStock2(product.getId(), num); if (count == 1) { //生成订单 createOrders(user, product, num); } else { logger.info("库存不足 3"); } return 1; } else { logger.info("库存不足 4"); return -1; } }

方法一:

不考虑库存安全的写作:

分析:

高合并场景下,假设库存只有2件,同时进来两个请求,采购数量为2.

此时A请求获取库存,发现库存刚好够,执行扣库存下单操作。

当A的请求完成(事务还没有提交),此时B的请求也去获取库存,发现库存中还有2.。这时候,它也进行了扣减和排序操作。

2 件存货,但 4 件已售出。最终的数据库库存数量将变为-2,因此库存不安全。

方法二:

此操作可确保清单数据是安全的。

/**
     * 方法 2
     * 减可用
     * 加预占
     * 库存数据不安全
     *
     * @param req
     */
@Override

电商系统 高并发库存系统设计_电商系统订单设计_龙宝参茸高补贴难去高库存

@Transactional public void placeOrder(PlaceOrderReq req) { place2(req); }

分析:在方法一的基础上,更新库存的语句增加了可用库存的数量大于0电商系统 高并发库存系统设计,availableNum - num >= 0;本质是利用数据库的乐观锁来控制库存安全,在并发量不是很大的情况下可以在下面这样做。但是,如果是秒杀、抢购、高瞬时流量,所有的压力都会放在数据库上,可能会导致数据库宕机。

方法三:

这种方法还可以保证库存数量安全。

/**
     * 方法 3
     * 采用 Redis 锁  通一个时间 只能一个 请求修改 同一个商品的数量
     * 

* 缺点并发不高,同时只能一个用户抢占操作,用户体验不好! * * @param req */ @Override public void placeOrder2(PlaceOrderReq req) { String lockKey = "placeOrder:" + req.getProductId(); Boolean isLock = redisService.lock(lockKey); if (!isLock) { logger.info("系统繁忙稍后再试!"); return 2; } //place2(req); place1(req); //这两个方法都可以 redisService.unLock(lockKey); }

分析:

使用Redis分布式锁,强制控制同一种商品,同时只能处理一个请求下单。其他请求返回“系统忙,稍后再试!';

强制处理请​​求序列化,缺点是并发不高,处理比较慢,不适合抢购等方案。

用户体验也不好。明明库存充足,但不强。

与方案2相比,减少了数据库的压力。

方法四:

既能保证库存安全,又能满足高并发处理,但相对复杂。

/**
     * 方法 4
     * 商品的数量 等其他信息 先保存 到 Redis
     * 检查库存 与 减少库存 不是原子性,  以 increment > 0 为准

电商系统订单设计_龙宝参茸高补贴难去高库存_电商系统 高并发库存系统设计

* * @param req */
@Override public void placeOrder3(PlaceOrderReq req) { String key = "product:" + req.getProductId(); // 先检查 库存是否充足 Integer num = (Integer) redisService.get(key); if (num < req.getNum()) { logger.info("库存不足 1"); } else{ //不可在这里下单减库存,否则导致数据不安全, 情况类似 方法1; } //减少库存 long value = redisService.increment(key, -req.getNum().longValue()); //库存充足 if (value >= 0) { logger.info("成功抢购 ! "); //TODO 真正减 扣 库存 等操作 下单等操作 ,这些操作可用通过 MQ 或 其他方式 place2(req); } else { //库存不足,需要增加刚刚减去的库存 redisService.increment(key, req.getNum().longValue()); logger.info("库存不足 2 "); } }

分析:

利用 Redis 增量的原子操作来确保您的库存安全。需要提前将库存数量等其他信息保存到 Redis 中,并确保在库存更新时 Redis 也随之更新。

进来的时候先获取库存数量,再执行增量。增量 > 0。

检查库存和减少库存不是原子的。

检查库存时,技术库存充足,无法下单;否则,库存将不安全。原方法类似1.

increment 是一个原子操作,无论是哪种情况。

redisService.increment(key, -req.getNum().longValue()) >= 0 表示库存充足,可以下单。

当Service.increment(key, -req.getNum().longValue()) < 0时,无法下单,库存不足。并且需要将刚刚扣除的库存数量加回,否则扣除的数量将无法一直购买。数据库与缓存的清单不一致。

第二种方式可以满足高价、抢购等一些方案,真实扣减库存和订单可以异步执行。

订单取消后,可使用MQ退回库存等。

技术交流讨论学习区

免费获取往期Java进阶架构资料、源码、笔记、视频 Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术往期架构视频

如果觉得对你有帮助,请点赞和评论鼓励我,谢谢!

声明:文章仅代表原作者观点,不代表本站立场;如有侵权、违规,可直接反馈本站,我们将会作修改或删除处理。

图文推荐

热点排行

精彩文章

热门推荐