面试题--秒杀系统引发的思考

看到这个面试题的时候,脑海中立马浮现出“队列”,那就先来解释下队列吧~

队列和堆栈一样,都是常用的数据结构,特点是先进先出

参考:https://www.cnblogs.com/be-thebest/p/9983672.html

场景说明:

1.初始化队列时,生成一个队列,传入一个参数作为maxsize初始化队列把队尾rear设为0,队头front也设为0,此时queue中只有0号元素,并且rear和front都指向它。

2.入队时,先需要判断队列是否已满(front-rear == maxsize),如果已满不可在插入,如果未满则允许插入。插入时,front自增,然后依次让队列所有元素向前移动一位(让出队尾位置以便插入新元素),然后生成新的data对象插入到队尾位置。

3.出队时,判断队列是否为空(front == rear),如果为空时,无法出队。如果不为空时,删除front指向的对象,并且front自减,完成出队。

实现代码:

<?php

class data {
    //数据
    private $data;

    public function __construct($data) {
        $this->data = $data;
        echo $data . ":哥进队了!<br>".PHP_EOL;
    }

    public function getData() {
        return $this->data;
    }

    public function __destruct() {
        echo $this->data . ":哥走了!<br>".PHP_EOL;
    }
}

class queue {
    protected $front;//队头
    protected $rear;//队尾
    protected $queue = array('0' => '队尾');//存储队列
    protected $maxsize;//最大数

    public function __construct($size) {
        $this->initQ($size);
    }

    //初始化队列
    private function initQ($size) {
        $this->front = 0;
        $this->rear = 0;
        $this->maxsize = $size;
    }

    //判断队空
    public function QIsEmpty() {
        return $this->front == $this->rear;
    }

    //判断队满
    public function QIsFull() {
        return ($this->front - $this->rear) == $this->maxsize;
    }

    //获取队首数据
    public function getFrontDate() {
        return $this->queue[$this->front]->getData();
    }

    //入队
    public function InQ($data) {
        if ($this->QIsFull()) echo $data . ":我一来咋就满了!(队满不能入队,请等待!)".PHP_EOL;
        else {
            $this->front++;
            for ($i = $this->front; $i > $this->rear; $i--) {
                //echo $data;
                if (isset($this->queue[$i])) {
                    unset($this->queue[$i]);
                }
                $this->queue[$i] = $this->queue[$i - 1];
            }
            $this->queue[$this->rear + 1] = new data($data);
          print_r($this->queue);
            //echo $this->front;
            echo '入队成功!<br>'.PHP_EOL;
        }
    }

    //出队
    public function OutQ() {
        if ($this->QIsEmpty()) echo "队空不能出队!<br>".PHP_EOL;
        else {
            unset($this->queue[$this->front]);
            $this->front--;
            //print_r($this->queue);
            //echo $this->front;
            echo "出队成功!<br>".PHP_EOL;
        }
    }
}

$q = new queue(3);
$q->InQ("小苗");
$q->InQ('马帅');
$q->InQ('溜冰');
$q->InQ('张世佳');
$q->OutQ();
$q->InQ("周瑞晓");
$q->OutQ();
$q->OutQ();
$q->OutQ();
$q->OutQ();
View Code

方案1:数据库

悲观锁:增删改查都上锁

乐观锁:大约等于无锁

排他锁:配合事务用,commit后才等于解锁(暂时没发现和悲观锁有什么区别,日后在补充)

1:开启事务
2:查询库存,并显示的设置写锁(排他锁):SELECT * FROM goods WHERE id = 1 FOR UPDATE
3:生成订单
4:去库存,隐示的设置写锁(排他锁):UPDATE goods SET counts = counts – 1 WHERE id = 1
5:commit,释放锁

方案2:分布式锁(Memcached、Redis)

用redis来做一个分布式锁,reids->setnx('lock', 1) 设置一个锁,程序执行完成再del这个锁。

锁定的过程,不利于并发执行,大家都在等待锁解开,不建议使用。

 

方案3:消息队列(RabbidMQ, ActiveMQ,Kafka)

将订单请求全部放入消息队列,然后另外一个后台程序一个个处理队列中的订单请求。

并发不受影响,但是用户等待的时间较长,进入队列的订单也会很多,体验上并不好,也不建议使用。

  • 消息队列(RabbidMQ, ActiveMQ,Kafka)主要有两种使用模式:生产者->消费者,发布者->订阅者第一种方式是一对一,后者是一对多消费掉,队例中数据便不再保存,所谓阅后即焚。消息队列的存和取一般是不同的服务,用于服务之间的异步操作,解耦同步操作。
  • Redis属于NoSQL类型的缓存/存储,侧重点在支持快速反复读取。数据的产生和消费可能是同一个服务,也可以是多个,如集群。

方案4: redis递减

代码设计

  1、当用户开始参与秒杀时,将秒杀程序的请求写入Redis(uid, time)中。

  2、假如只有10个人可以秒杀成功,检查Redis已经存放数据的长度,达到上限后不再插入,说明秒杀已完成。

  3、最后循环处理存入Redis中的10条数据,然后慢慢取数据存入到数据库中。

在秒杀这一块对数据库压力特别大,如果我们直接在用户发起秒杀请求时,每次都查询数据库是否已经达到秒杀人数上限的话,会造成数据库压力巨大。现在通过Redis的一个队列list,然后把秒杀请求放入到Redis里面,最后将秒杀成功的数据通过入库程序写入到数据库,这样的话会极大缓解mysql的压力。

 

redis数据类型中的list类型

  redis 的list 是一个双向链表,可以从头部或者尾部追加数据。

  * LPUSH/LPUSHX :将值插入到(/存在的)列表头部

  * RPUSH/RPUSHX: 将值插入到(/存在的)列表尾部

  * LPOP : 移除并获取列表的第一个元素

  * RPOP: 移除并获取列表的最后一个元素

  * LTRIM: 保留指定区间内的元素

  * LLEN: 获取列表长度

  * LSET: 通过索引设置列表元素的值

  * LINDEX: 通过索引获取列表中的元素

  * LRANGE: 获取列表指定范围内的元素

posted @ 2020-04-01 17:44  花花妹子。  阅读(692)  评论(0编辑  收藏  举报