商品秒杀的实现思路参考

常规秒杀

来源于:https://gitee.com/52itstyle/spring-boot-seckill

Aop锁

使用ReentrantLock重入锁,事物提交后再释放锁。

  • aop实现

@Component
@Scope
@Aspect
@Order(1)
//order越小越是最先执行,但更重要的是最先执行的最后结束。order默认值是2147483647
public class LockAspect {
	/**
     * 思考:为什么不用synchronized
     * service 默认是单例的,并发下lock只有一个实例
     */
	private static  Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁  
	
	//Service层切点     用于记录错误日志
	@Pointcut("@annotation(com.itstyle.seckill.common.aop.Servicelock)")  
	public void lockAspect() {
		
	}
	
    @Around("lockAspect()")
    public  Object around(ProceedingJoinPoint joinPoint) { 
    	lock.lock();
    	Object obj = null;
		try {
			obj = joinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
			throw new RuntimeException();       
		} finally{
			lock.unlock();
		}
    	return obj;
    } 
}

  • 应用部分
@Servicelock
	@Transactional(rollbackFor = Exception.class)
	public Result startSeckilAopLock(long seckillId, long userId) {
		//来自码云码友<马丁的早晨>的建议 使用AOP + 锁实现
		String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
		Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
		Long number =  ((Number) object).longValue();
		if(number>0){
			nativeSql = "UPDATE seckill  SET number=number-1 WHERE seckill_id=?";
			dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
			SuccessKilled killed = new SuccessKilled();
			killed.setSeckillId(seckillId);
			killed.setUserId(userId);
			killed.setState(Short.parseShort(number+""));
			killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
			dynamicQuery.save(killed);
		}else{
			return Result.error(SeckillStatEnum.END);
		}
		return Result.ok(SeckillStatEnum.SUCCESS);
	}
数据库悲观锁
  • 基于数据库悲观锁实现,更新加锁并判断剩余数量。
/**
     * SHOW STATUS LIKE 'innodb_row_lock%'; 
     * 如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高
     */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Result startSeckilDBPCC_TWO(long seckillId, long userId) {
		/**
		 * 单用户抢购一件商品没有问题、但是抢购多件商品不建议这种写法 UPDATE锁表
		 */
		String nativeSql = "UPDATE seckill  SET number=number-1 WHERE seckill_id=? AND number>0";
		int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
		if(count>0){
			SuccessKilled killed = new SuccessKilled();
			killed.setSeckillId(seckillId);
			killed.setUserId(userId);
			killed.setState((short)0);
			killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
			dynamicQuery.save(killed);
			return Result.ok(SeckillStatEnum.SUCCESS);
		}else{
			return Result.error(SeckillStatEnum.END);
		}
	}
数据库乐观锁

基于数据库乐观锁实现,先查询商品版本号,然后根据版本号更新,判断更新数量。少量用户抢购的时候会出现 少买 的情况。

@Transactional(rollbackFor = Exception.class)
	public Result startSeckilDBOCC(long seckillId, long userId, long number) {
		Seckill kill = seckillRepository.findOne(seckillId);
		/**
		 * 剩余的数量应该要大于等于秒杀的数量
		 */
		if(kill.getNumber()>=number){
			//乐观锁
			String nativeSql = "UPDATE seckill  SET number=number-?,version=version+1 WHERE seckill_id=? AND version = ?";
			int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId,kill.getVersion()});
			if(count>0){
				SuccessKilled killed = new SuccessKilled();
				killed.setSeckillId(seckillId);
				killed.setUserId(userId);
				killed.setState((short)0);
				killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
				dynamicQuery.save(killed);
				return Result.ok(SeckillStatEnum.SUCCESS);
			}else{
				return Result.error(SeckillStatEnum.END);
			}
		}else{
			return Result.error(SeckillStatEnum.END);
		}
	}

进程内队列

基于进程内队列 LinkedBlockingQueue 实现,同步消费

  • 队列结构
  /** 用于多线程间下单的队列 */
static BlockingQueue<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(QUEUE_MAX_SIZE);

/**
   * 生产入队
   * @param kill
   * @throws InterruptedException
   * add(e) 队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异常——AbstractQueue 
   * put(e) 队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。
   * offer(e) 队列未满时,返回true;队列满时返回false。非阻塞立即返回。
   * offer(e, time, unit) 设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。 
   */
    public  Boolean  produce(SuccessKilled kill) {
    	return blockingQueue.offer(kill);
    }
    /**
     * 消费出队
     * poll() 获取并移除队首元素,在指定的时间内去轮询队列看有没有首元素有则返回,否者超时后返回null
     * take() 与带超时时间的poll类似不同在于take时候如果当前队列空了它会一直等待其他线程调用notEmpty.signal()才会被唤醒
     */
    public  SuccessKilled consume() throws InterruptedException {
        return blockingQueue.take();
    }
  • 线程消费
  @Override
    public void run(ApplicationArguments var){
        new Thread(() -> {
            LOGGER.info("提醒队列启动成功");
            while(true){
                try {
                    //进程内队列
                    SuccessKilled kill = SeckillQueue.getSkillQueue().consume();
                    if(kill!=null){
                        Result result =
                                seckillService.startSeckilAopLock(kill.getSeckillId(), kill.getUserId());
                        if(result.equals(Result.ok(SeckillStatEnum.SUCCESS))){
                            LOGGER.info("用户:{}{}",kill.getUserId(),"秒杀成功");
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
Disruptor队列

基于高性能队列 Disruptor实现,同步消费

  • 工具类
public class DisruptorUtil {
	
	static Disruptor<SeckillEvent> disruptor;
	static{
		SeckillEventFactory factory = new SeckillEventFactory();
		int ringBufferSize = 1024;
		ThreadFactory threadFactory = runnable -> new Thread(runnable);
		disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory);
		disruptor.handleEventsWith(new SeckillEventConsumer());
		disruptor.start();
	}
	
	public static void producer(SeckillEvent kill){
		RingBuffer<SeckillEvent> ringBuffer = disruptor.getRingBuffer();
		SeckillEventProducer producer = new SeckillEventProducer(ringBuffer);
		producer.seckill(kill.getSeckillId(),kill.getUserId());
	}
}
  • 生产者

public class SeckillEventProducer {
	
	private final static EventTranslatorVararg<SeckillEvent> translator = (seckillEvent, seq, objs) -> {
        seckillEvent.setSeckillId((Long) objs[0]);
        seckillEvent.setUserId((Long) objs[1]);
    };

	private final RingBuffer<SeckillEvent> ringBuffer;
	
	public SeckillEventProducer(RingBuffer<SeckillEvent> ringBuffer){
		this.ringBuffer = ringBuffer;
	}
	
	public void seckill(long seckillId, long userId){
		this.ringBuffer.publishEvent(translator, seckillId, userId);
	}
}

  • 消费者

public class SeckillEventConsumer implements EventHandler<SeckillEvent> {

	private final static Logger LOGGER = LoggerFactory.getLogger(SeckillEventConsumer.class);
	
	private ISeckillService seckillService = (ISeckillService) SpringUtil.getBean("seckillService");
	
	@Override
    public void onEvent(SeckillEvent seckillEvent, long seq, boolean bool) {
		Result result =
				seckillService.startSeckilAopLock(seckillEvent.getSeckillId(), seckillEvent.getUserId());
		if(result.equals(Result.ok(SeckillStatEnum.SUCCESS))){
			LOGGER.info("用户:{}{}",seckillEvent.getUserId(),"秒杀成功");
		}
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值