看到这个面试题的时候,脑海中立马浮现出“队列”,那就先来解释下队列吧~
队列和堆栈一样,都是常用的数据结构,特点是先进先出
参考: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();
方案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: 获取列表指定范围内的元素