精华内容
下载资源
问答
  • 经过前面的过滤,超卖的可能性比较低了提前将商品库存缓存起来,到下单购买的时候,用户购买了就减1,每次都通过库存缓存判断一下,如果为0就显示已抢完。 五、页面静态设计:尽量静态缓存化【CDN那些这里不做考虑】...

    背景:

    做电商网站,经常会有各种秒杀和热门商品,所以高并发的处理一直是电商最重要的事情。这里记录下当初自己是如何处理的!!!

    设置条件:

    1、本文设计到的并发处理均是针对纵向,不针对横向扩展,即只设计从PHP层面到数据库层面的处理,不涉及多台服务器,集群、大带宽等的横向设计。
    2、本文中涉及到的高并发并不是淘宝京东等几百万几千万等的高并发,仅仅只是普通最多上万的并发处理
    3、本文不对悲观锁乐观锁做设计

    问题:普通电商中的秒杀中的并发问题,超卖问题?

    实例:商品数量为100,秒杀人数为10000,整点开始秒杀

    秒杀大概流程: ①商品详情点击购买(秒杀)--》②输入信息提交订单--》③进行支付

    解决思路:
    1、人数阀门设计
    2、会员排队设计
    3、问答问题设计
    4、库存缓存设计
    5、页面静态设计

    思路理解:
    一、人数阀门设计:进行用户人群过滤。
    商品数量只有100份,秒杀人数有10000人,那么我们就设计1道阀门(根据情况,可以设计3道或者2道都可以的)。
    在整点的时候,我们对点击了“购买”按钮后,我们只运行500人进入信息填写页面,信息填写完成后提交订单。效果如下:
    ①商品详情点击购买(秒杀)--》②输入信息提交订单--》③进行支付

           10000人         500人         (这里也可以设计阀门,只允许多少人进入支付)
    其他未进入的如何处理乃?显示已抢完或者排队等待(这就是后面要提到的排队系统设计)。


    二、会员排队设计:对用户进行排队,排在前面的先购买

    这相当于是消息队列模式了,如果秒杀是立即知道结果,排队可能会有点鸡肋。
    在第二步②输入信息提交订单后进行排队,排在前面的先购买,排在后面的后购买


    三、问答问题设计:过滤掉一些反应慢的用户
    在第一步①点击购买后跳转到问题页面,用户必须回答正确问题后,方可进入后面的流程


    四、库存缓存设计:缓存库存,判断用户购买的商品是否还有,不读取数据库,速度快,也不会增加数据库负担,

    经过前面的过滤,超卖的可能性比较低了提前将商品库存缓存起来,到下单购买的时候,用户购买了就减1,每次都通过库存缓存判断一下,如果为0就显示已抢完。


    五、页面静态设计:尽量静态缓存化【CDN那些这里不做考虑】
    第一步①商品详情页面,尽量进行缓存,减轻大批量用户在访问商品页面的时候,大量查询数据库。
    问答问题页面:全静态,加载快,无数据库负担。
    排队等待页面:全静态,加载快,无数据库负担。
    排队结束页面:全静态,加载快,无数据库负担。


    小试牛刀:
    上面说了那么多废话,总归在一起,流程大概就成了下面这样:
    ①商品详情点击购买(秒杀)  --》 ②进入问题回答页面      --》③排队等待 --》 ④输入信息提交订单    --》 ⑤进行支付
        页面缓存     问题过滤     阀门过滤      缓存库存减少
                           页面缓存      页面缓存

    附件:
    一、参考资料
    ①、淘宝阀门设计
    ②、各大电商网站秒杀流程
    ③、网络上面的各种文献资料

    小结:

    1、在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的
    必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况。当接到用户秒杀提交订单的情况下,先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败在将数据递增1(加锁/解锁),否则表示交易成功。
    当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求。

    2、这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大,要用缓存。
    把你要卖出的商品比如10个商品放到缓存中;然后在redis里设置一个计数器来记录请求数,这个请求书你可以以你要秒杀卖出的商品数为基数,比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。

    3、首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。
    这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。
    乐观锁,就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。
    悲观锁,就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。
    除了加锁的方式也可以使用接收锁定的方式,思路是在数据库中设计一个状态标识位,用户在对数据进行修改前,将状态标识位标识为正在编辑的状态,这样其他用户要编辑此条记录时系统将发现有其他用户正在编辑,则拒绝其编辑的请求,类似于你在操作系统中某文件正在执行,然后你要修改该文件时,系统会提醒你该文件不可编辑或删除。

    4、不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。

    5、实际应用中,并不是让mysql去直面大并发读写,会借助“外力”,比如缓存、利用主从库实现读写分离、分表、使用队列写入等方法来降低并发读写。

    申明:

    本人对并发处理并不深入,所知道的知识都是来源于网络资料和各种网站参考。虽然我这样的设计已经用于系统中,并且基本上解决了普通的这些问题,但是这样的设计可能存在一定的问题或者不完善,或者根本就是错误的。

    展开全文
  • 第一步:在减少库存的时候进行判断stock_count>0 ... @Mapper public interface GoodsDao { @Update("update seckill_goods set stock_count=stock_count-1 where goods_id=#{...以上2个步骤就能解决秒杀超卖问题

    第一步:在减少库存的时候进行判断stock_count>0

    package com.jack.seckill.dao;
    
    @Mapper
    public interface GoodsDao {
    	@Update("update seckill_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
    	public void reduceStock(SeckillGoods g);
    }

    第二步:在mysql数据库添加唯一索引

    以上2个步骤就能解决秒杀超卖问题

    展开全文
  • import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import ...

    目录

    mapper

    需要的两张表 

    乐观锁讲解

    修改代码为乐观锁

    减库存乐观锁、行锁总结


    流程:

    1.库存>0就减库存,并记录减了库存的用户到抢购成功记录表中,
    2.前台定时实时ajax调用接口查询是否抢购成功。

    采用乐观锁防止库存<0

    乐观锁就是认为不会产生数据访问冲突。比如update修改商品status为2
    update 表 set status=2, version=version+1
    where id=#{id} and version=#{version};

    package com.chuangqi.defense.controller;
    
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    import javax.annotation.Resource;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import com.chuangqi.defense.mapper.Test_01Mapper;
    import lombok.extern.log4j.Log4j2;
    
    /**
     1.抢购:http://127.0.0.1:81/subtractInventory?phone=18713901060&inventoryId=110
     2.查询是否抢购成功:http://127.0.0.1:81/getInventoryLog?phone=18713901060&inventoryId=110
     *
     * @author qizhentao
     * @version V1.0
     */
    @Log4j2
    @RestController
    public class Test_01 {
    
    	@Resource
    	private RedisTemplate<String, Object> redisTemplate;
    	
    	@Autowired
    	private Test_01Mapper mapper;
    	
        /**
    	 * 抢购接口
    	 *  1.减库存
    	 *  2.记录抢购成功用户
    	 * @param inventoryId
    	 * @param phone
    	 * @return
    	 */
    	@RequestMapping("subtractInventory")
    	public String add(Integer inventoryId, String phone){
    		// 1.限制同一用户5秒内只能请求一次,使用setNX命令:已存在返回false,不存在就保存并返回true,超时时间5000毫秒
    		Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(phone, inventoryId, 5000, TimeUnit.MILLISECONDS);
    		if(!setIfAbsent){
    			return "later on...";
    		}
    		
    		// 2.减库存
    		int i = mapper.subtractInventory(inventoryId, phone);
    		if(i == 1){
    			// 3.异步添加用户抢购成功记录表
    			addInventoryLog(inventoryId, phone);
    			return "ok";
    		}else{
    			return "no";
    		}
    	}
    	
    	/**
    	 * 减库存后,添加用户抢购成功记录表中
    	 * 	1. 并且添加到redis中,
    	 *  2. 在前台实时查询是否抢购成功的时候去查询redis即可
    	 * @param inventoryId
    	 * @param phone
    	 * @return
    	 */
    	@Async
    	public boolean addInventoryLog(int inventoryId, String phone){
    		// 1.添加数据到数据库
    		int i = mapper.addInventoryLog(inventoryId, phone, new Date());
    		// 2.也可同步到redis中
    		// redisTemplate.opsForValue().set(phone, inventoryId);
    		return i == 1 ? true : false;
    	}
    	
    	/**
    	 * 前台定时ajax实时查询是否抢购成功
    	 * @param inventoryId
    	 * @param phone
    	 * @return
    	 */
    	@RequestMapping("getInventoryLog")
    	public String getInventoryLog(int inventoryId, String phone){
    		// 查询数据库,也可改为查询redis
    		Integer id = mapper.getInventoryLog(inventoryId, phone);
    		// redisTemplate.opsForValue().get(inventoryId)
    		if(id != null){
    			return phone+"用户抢购成功!";
    		}else{
    			return null;
    		}
    	}
    
    }
    

    mapper

    package com.chuangqi.defense.mapper;
    
    import java.util.Date;
    
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;
    
    public interface Test_01Mapper {
        
    	@Update("update inventory set quantity = quantity - 1 where inventoryId = #{inventoryId} and quantity > 0")
    	int subtractInventory(@Param("inventoryId")int inventoryId, @Param("phone")String phone);
    
    	@Insert("insert into inventory_log (phone, inventoryId, dateTime) values(#{phone},#{inventoryId},#{date})")
    	int addInventoryLog(@Param("inventoryId")int inventoryId, @Param("phone")String phone, @Param("date")Date date);
    
    	@Select("select id from inventory_log where phone = #{phone} and inventoryId = #{inventoryId} limit 1")
    	Integer getInventoryLog(@Param("inventoryId")int inventoryId, @Param("phone")String phone);
    	
    }

    需要的两张表 

    库存表:
    DROP TABLE IF EXISTS `inventory`;
    CREATE TABLE `inventory` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `inventoryId` int(11) DEFAULT NULL  COMMENT '库存id',
      `quantity` int(11) DEFAULT NULL COMMENT '库存数量',
      `updateTime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `deadline` datetime DEFAULT NULL COMMENT '截止时间',
      `insertTime` datetime DEFAULT NULL COMMENT '新增时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    
    用户抢购成功记录表:
    DROP TABLE IF EXISTS `inventory_log`;
    CREATE TABLE `inventory_log` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `phone` varchar(255) DEFAULT NULL COMMENT '用户手机号',
      `inventoryId` int(11) DEFAULT NULL COMMENT '库存id',
      `dateTime` datetime DEFAULT NULL  COMMENT '抢购成功时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1155 DEFAULT CHARSET=utf8;

    乐观锁讲解

    以上所写的减库存:"update inventory set quantity = quantity - 1 where inventoryId = #{inventoryId} and quantity > 0"

    并不全是乐观锁。

    行锁与乐观锁相比

     

    乐观锁原理


    修改代码为乐观锁

    1. inventory库存表添加添加version版本号字段

    2. 修改库存sql语句

    package com.chuangqi.defense.mapper;
    
    import java.util.Date;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;
    
    public interface Test_01Mapper {
        
    		// 行锁减库存
    	@Update("update inventory set quantity = quantity - 1 where inventoryId = #{inventoryId} and quantity > 0")
    	int subtractInventory(@Param("inventoryId")int inventoryId);
    
    	// 乐观锁减库存
    	@Update("UPDATE inventory SET quantity = quantity - 1, version = version + 1 WHERE inventoryId = #{inventoryId} AND quantity > 0 AND version = #{version}")
    	int subtractInventoryOptimisticByVersion(@Param("inventoryId")int inventoryId, @Param("version")Integer version);
    	
    	// 用户抢购成功,记录到抢购成功记录表
    	@Insert("insert into inventory_log (phone, inventoryId, dateTime) values(#{phone},#{inventoryId},#{date})")
    	int addInventoryLog(@Param("inventoryId")int inventoryId, @Param("phone")String phone, @Param("date")Date date);
    
    	// 查询用户是否抢购成功
    	@Select("select id from inventory_log where phone = #{phone} and inventoryId = #{inventoryId} limit 1")
    	Integer getInventoryLog(@Param("inventoryId")int inventoryId, @Param("phone")String phone);
    
    	// 查询商品库存是否存在,返回版本号
    	@Select("select version from inventory where inventoryId = #{inventoryId} limit 1")
    	Integer getInventory(@Param("inventoryId")Integer inventoryId);
    }

    -- 分解乐观锁
    -- 1.根据库存id获取版本值,version返回4
    SELECT b.version FROM    inventory b WHERE b.inventoryId = #{库存Id}
    -- 2.版本值作为条件进行乐观锁(无锁)进行update
    UPDATE inventory SET quantity = quantity - 1, version = a.version + 1 WHERE inventoryId = #{库存Id} AND quantity > 0 AND a.version = 4

    3. 业务层修改

       3.1.根据库存id查询商品库存是否存在,返回版本号  

       3.2.调用减库存方法,改为使用乐观锁的减库存的sql:Test_01Mapper.subtractInventoryOptimisticByVersion()接口方法。

    减库存乐观锁、行锁总结

    // 如果采用提前生成库存数量保存到redis后,就没必要使用乐观锁了,直接使用行锁机制就行
    // 因为:能在redis抢到令牌的,就已经代表抢到了商品,所以使用乐观锁和行锁是没什么区别的
    // 乐观锁:如果有100库存,同时来一百线程,有的线程会查询到相同的版本号,所以不会一次减完100个库存。
    // 行锁:两个线程操作同一行数据,由于InnoDB为行锁,在A会话未提交时,B会话只有阻塞等待。如果操作不同行,则不会出 -现阻塞情况。防止了数据一致性的问题

    展开全文
  • java什么是超卖现象?

    2021-11-02 17:49:05
    实际的库存量 的现象 成为“超卖” 解释:假设某商品X卖剩下最后一件,线程A(客户)进来下单,下单完成,还没来得及减库存,这时线程B又进来了,判断还有库存,继续下单,这时出现超卖,库存实际有一件商品,却卖出...

    当出现  商品的销售量 > 实际的库存量 的现象 成为“超卖”

    解释:假设某商品X卖剩下最后一件,线程A(客户)进来下单,下单完成,还没来得及减库存,这时线程B又进来了,判断还有库存,继续下单,这时出现超卖,库存实际有一件商品,却卖出了2件商品

    展开全文
  • 如果在并发的环境下,可以用rabbitMQ设置只有一个消费者来保证,如果有多个消费者呢??咋解决呀
  • JedisUtil.java package com . jake . mallseckill . utils ; import redis . clients . jedis . Jedis ; import redis . clients . jedis . JedisPool ; import redis . clients . jedis . ...
  • 问题:下单成功的条件是什么? 结果:首先库存大于购买量,然后更新库存和销量时原始库存没变。 结论:所以在用户库存满足的情况下,如果更新库存和销量时原始库存有变,那么继续给用户下单的机会。...
  • 库存超卖问题 针对秒杀建议选择下单扣库存的方式:首先查询redis缓存库存是否充足先扣库存再落订单数据,可以防止订单生成了没有库存的超卖问题扣库存的时候先扣数据库库存,再扣减redis库存,保证在同一个事务里,...
  • 最近在学习 《JAVA多线程编程实战指南》这本书,学到内部锁 synchronized 这里,自己就编写Demo演示模拟售票中超卖的线程安全问题,首先我的代码如下: package com.sailing.thread.entity; import ...
  • 超卖现象及解决

    千次阅读 2018-07-23 21:14:20
    本项目的超卖类似于电商的秒杀超卖现象 1.不同用户在读请求的时候,发现商品库存足够,然后同时发起请求,进行秒杀操作,减库存,导致库存减为负数。 2.同一个用户在有库存的时候,连续发出多个请求,两个请求同时...
  • 探索电商秒杀超卖和并发问题的解决之道——多线程编程
  • 在众多抢购活动中,在有限的商品数量的限制下如何保证抢购到商品的用户数不能大于商品数量,也就是不能出现超卖的问题;还有就是抢购时会出现大量用户的访问,如何提高用户体验效果也是一个问题,也就是要解决秒杀...
  • 超卖现象 当多个线程请求数据库查询库存余量时,显示有余量,但是当进行扣减库存时,库存已经用完了,但那个线程并不知道,依旧去扣减库存,造成库存为负数的情况,于是乎就出现了超发现象。 1、悲观锁 发生超卖现象...
  • 点击关注公众号,Java干货及时送达作者:叁滴水博客:https://blog.csdn.net/qq_30285985/前言在多个人同时对一个商品下单时,如果处理的不得当会存在超卖的现象...
  • java 用redis如何处理电商平台,秒杀、抢购超卖

    万次阅读 多人点赞 2016-06-29 14:34:19
    二、现在我们来解读下这段代码,看看作者的意图,以及问题点在什么地方,这样帮助更多的人了解,在电商平台如何处理在抢购、秒杀时出现的超卖的情况处理 1、参数说明, 上面checkSoldCountByRedisDate方法,有4个...
  • 其实像阿里p7岗位的需求也没那么难(但也不简单),扎实的Java基础+无短板知识面+对某几个开源技术有深度学习+阅读过源码+算法刷题,这一套下来p7岗差不多没什么问题,还是希望大家都能拿到高薪offer吧。
  • ``` @Autowired private JdbcTemplate jdbcTemplate;...按我自己的理解,tomcat是以队列的方式一条一条处理请求的,代码走完了,下个线程才会执行,z怎么会出现超卖的行为呢?有知道的大神帮忙解答一下么?
  • volatile是Java程序员必备的基础,也是面试官非常喜欢问的一个话题,本文跟大家一起开启vlatile学习之旅,如果有不正确的地方,也麻烦大家指出哈,一起相互学习~ 1.volatile的用法 2.vlatile变量的作用 3.现代...
  • 点击关注公众号,Java干货及时送达今天和同事讨论库存防超卖问题,发现虽然只是简单的库存扣减场景,却隐藏着很多坑,一不小心就容易翻车,让西瓜推土机来填平这些坑。单实例环境 一般电商体系防止...
  • 超卖现象二:解决方法 基于Synchronized锁解决超卖问题(最原始的锁) 基于ReentrantLock锁解决超卖问题(并发包中的锁) 基于ReentrantLock锁(可重复锁)解决超卖问题 package ...
  • 我们项目中的抢购订单采用的是分布式锁来解决的,有一次,运营做了一个飞天茅台的抢购活动,库存100瓶,但是却超卖了100瓶!要知道,这个地球上飞天茅台的稀缺性啊!!! 事故定为P0级重大事故...只能坦然接受。...
  • (由于MySQL事务的特性,这种方法只能降低超卖的数量,但是不可能完全避免超卖) update number set x=x-1 where (x -1 ) >= 0; 解决方案1: 将存库从MySQL前移到Redis中,所有的写操作放到内存中,由于Redis中不...
  • 需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库。 ...这个业务场景,主要是解决读...
  • 起因 项目中要做预约功能,首先每天的余票都是有上限的,自然不能出现超卖的情况 解决过程 一波三折 解决方案 思考
  • } } 分布式锁工具类 - RedisDistributedLock.java @Component public class RedisDistributedLock { /** * 成功获取锁标示 */ private static final String LOCK_SUCCESS = "OK"; /** * 成功解锁标示 */ private ...
  • redis分布式锁解决秒杀业务下超卖问题 加入架包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <...
  • 超卖问题解决大全

    千次阅读 多人点赞 2020-02-17 00:36:39
    文章目录什么是库存超卖??方式一:扣减库存时限制库存量大于0方式二:悲观锁方式三:乐观锁方式四:将请求放队列方式五:通过事务控制超卖方式六使用redis分布式来解决(悲观锁)基于redis实现分布式锁基于redisson...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,735
精华内容 2,294
关键字:

java超卖

java 订阅