1、熟悉的PHP框架有哪些?简单介绍下PHP框架中类的自动加载是怎么实现的
laravel、thinkphp、ci、yii
PHP类自动加载实现步骤:
1.新建一个类文件:Autoload.php
2.在文件中定义一个类,类名与文件名同名
{
public function display($name='小红花')
{
return "送一一朵".$name;
}
}
3.新建一个文件,定义一个类,类中自定义一个静态的方法::load
class Demo{
public static function load($class_name,$ext='.php')
{
if(file_exists($class_name.$ext)){
requrie_once($class_name.$ext);
echo $class_name.$extss;
}else{
echo "文件不存在";
}
}
}
$obj = new Autoload();
echo $obj->display()
}
参数1:静态方法load所在的类
参数2:静态方法load()
两个参数需要放在数组里
2、请介绍下PHP-FPM工作原理和模型是怎样的?请介绍下PHP-FPM中动态和静态模式之间的优缺点分别是什么
PHP-FPM工作原理:
master进程只有一个,负责监听端口,接收来自服务器的请求,而worker进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个PHP解释器。
从FPM接收到请求,到处理完毕,其具体的流程如下
1).FPM的master进程接收到请求。
2).master进程根据配置指派特定的worker进程进行请求处理,如果没有可用进程,返回错误,这也是我们配合Nginx遇到502错误比较多的原因。
3).worker进程处理请求,如果超时,返回504错误。
4).请求处理结束,返回结果。
php-fpm的进程数可以根据设置分为动态和静态
- 静态:直接开启指定数量的php-fpm进程,不再增加或者减少;
- 动态:开始的时候开启一定数量的php-fpm进程,当请求量变大的时候,动态的增加php-fpm进程数到上限,当空闲的时候自动释放空闲的进程数到一个下限
对于内存比较紧张的服务器,建议采用动态的方式,动态方式因为会结束掉多余 的进程,可以回收释放一些内存
对于比较大内存的服务器来说,设置为静态的话会提高效率。因为频繁开关php-fpm进程也会有时滞,所以内存够大的情况下开静态效果会更好。数量也可以根据 内存/30M 得到。比如说2GB内存的服务器,可以设置为50;4GB内存可以设置为100等。
3、请简要介绍下你在简历当中提到的在项目中优化SQL的背景以及具体的优化方式(在回答完之前是怎么优化的之后,面试官层层深入,要求将接口的响应速率优化至200MS以内)
4、请简要介绍下PHP SPL是什么,主要用途是用来做什么的?PHP的两次请求落在同一台机器上的同一个worker进程里,他们之间的变量是否是共享的?
SPL,全称 Standard PHP Library 中文是 标准PHP类库。是php内置的一些拓展类和拓展接口,其内容包含数据结构、迭代器、接口、异常、SPL函数,文件处理等内容。SPL拓展只能用于PHP5.3版本及以后,并且不需要进行额外的配置,可以直接使用。详细信息可以进入PHP官方网站 https://www.php.net/spl查看。 这里主要说明SPL中的数据结构内容的使用。
(1)栈
栈是一种先进先出的数据结构。并且只能对栈的两端进行操作,进栈或者出栈。SplStack类通过使用一个双向链表来提供栈的主要功能。将出栈想象成遍历一个相反的数组的过程
$stack->push('张三<br>');//入栈
$stack->push('李四<br>');
$stack->unshift("王五");//将’王五‘放入栈底
echo $stack->pop();//出栈 李四
echo $stack->pop();//张三
echo $stack->pop();//王五
(2)队列
队列是一种先进先出的数据结构。SplQueue 类同样通过使用一个双向链表来提供队列的主要功能。
$queue->enqueue(5);//入队列
$queue->enqueue(2);
$queue->enqueue(1);
$queue->enqueue(3);
echo $queue->dequeue(); //出队列 5
echo $queue->dequeue(); //2
echo $queue->dequeue(); //1
echo $queue->dequeue(); //3
(3)堆
堆就是为了实现优先队列而设计的一种数据结构,它是通过构造二叉堆实现。其中根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆,最大堆(SplMaxHeap)和最小堆(SplMinHeap)都继承它实现的堆主要用于排序。
$heap = new SplMaxHeap();
$heap->insert('E');
$heap->insert('B');
$heap->insert('D');
$heap->insert('A');
$heap->insert('C');
echo $heap->extract().'<br>'; # E
echo $heap->extract().'<br>'; # D
// 最小堆 降序输出
$heap = new SplMinHeap();
$heap->insert('E');
$heap->insert('B');
$heap->insert('D');
$heap->insert('A');
$heap->insert('C');
echo $heap->extract().'<br>';
echo $heap->extract().'<br>';
最大堆:堆中每个父节点的元素值都大于等于其孩子结点(如果存在);
最小堆:堆中每个父节点的元素值都小于等于其孩子结点(如果存在);
(4)固定数组
//固定数组$i = 1000000;
$fixbtime = microtime(true);
$fixstart = memory_get_usage();
$fixArray = new SplFixedArray($i);//生成长度为i的固定数组
$fixend = memory_get_usage();
$fixetime = microtime(true);
//普通数组
$btime2 = microtime(true);
$arr = array_fill(0, $i, null);
$end = memory_get_usage();
//生成固定长度的固定数组和普通数组所用时间
echo $fixetime - $fixbtime, PHP_EOL; //固定数组 0.0065009593963623
echo microtime(true) - $btime2, PHP_EOL; //普通数组 0.1734619140625
//生成固定长度的固定数组和普通数组所占内存
echo $fixend - $fixstart, PHP_EOL; //固定数组 4000280 byte
echo $end - $fixend, PHP_EOL; //普通数组 52194712 byte
在内存和时间方面,固定数组都比普通数组的消耗少了很多。但是对于固定数组来说,对内存的申请一步到位了,当内存不够时候会报错,当内存用不完时,也不会释放,只能浪费。同时,固定数组都是索引数组,不能使用除了整数以外的key。
PHP的两次请求落在同一台机器上的同一个worker进程里,他们之间的变量是否是共享的?
不是共享的,每次请求结束后,对应脚本创建的资源会释放。不过可以借助于第三方,比如redis实现变量的共享
5、接口并发量比较高,缓存失效,多个请求都会击穿缓存然后去请求库,从中获取数据,请问这种场景下该如何处理?
如何解决缓存雪崩问题:
在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效。
setRedis(Key,value,time + Math.random() * 10000);
或者设置热点数据永远不过期,有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就完事了,不要设置过期时间),电商首页的数据也可以用这个操作,保险。
说一下缓存穿透和击穿,可以说说他们跟雪崩的区别么?
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
至于缓存击穿嘛,这个跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
缓存击穿的话,设置热点数据永远不过期。或者加上互斥锁就能搞定了
缓存穿透我会在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等。
从缓存取不到的数据,在数据库中也没有取到,这时也可以将对应Key的Value对写为null、位置错误、稍后重试这样的值具体取啥问产品,或者看具体的场景,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。
这样可以防止攻击用户反复用同一个id暴力攻击,但是我们要知道正常用户是不会在单秒内发起这么多次请求的,那网关层Nginx本渣我也记得有配置项,可以让运维大大对单个IP每秒访问次数超出阈值的IP都拉黑。
Redis还有一个高级用法布隆过滤器(Bloom Filter)这个也能很好的防止缓存穿透的发生,他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
6、请介绍下redis分布式锁的使用场景以及你在项目中具体解决了什么问题?请解释下setnx为什么是一个原子性的命令
1.分布式锁的两种实现方式
(1)zookeeper
(2) redis分布式锁,一般使用redisson框架
要了解Redis分布式锁需要先了解两条命令
setnx是set if not exists(如果不存在则set)的简写
返回1
setnx ab kk
返回0
get ab
返回love
如上,如果不存在set成功返回1,如果存在,返回0
将值value关联到key,并将key的生存时间设置为seconds(秒为单位)
如果key已经存在,setex命令将覆写旧值
setex是一个原子性操作,也就是关联值和设置生存时间两个动作会在同一时间内完成
setex lock 10 test
OK
get lock
test
ttl lock
-2
get lock
nil
如果使用setnx设置,一旦一个线程挂掉了,但是锁还没有释放,后面的线程就永远得不到锁,就死锁了。
所以使用setex命令,设置一个过期时间,就算线程1挂掉了,也会在失效时间到了,自动释放锁。
7、对redis的底层数据结构是否有过了解,请简单介绍一下其中的几种
8、对redis hash扩容的过程是否有过了解,请简单介绍一下
9、PHP当中的数组功能是比较强大的,即可以顺序遍历也可以当做HASH去使用,请问底层实现的数据结构是怎样的;HASH表是怎么实现顺序遍历的;纯下标和纯HASH key混合在一起,怎么保证顺序遍历
PHP数组的底层使用散列表实现。
散列表数据结构定义如下(位于Zend/zend_types.h)
这个散列表中有很多成员,我们挑几个比较重要的来讲讲:
arData:散列表中保存存储元素的数组,其内存是连续的,arData指向数组的起始位置;
nTableSize:数组的总容量,即可以容纳的元素数,arData 的内存大小就是根据这个值确定的,它的大小的是2的幂次方,最小为8,然后按照 8、16、32…依次递增;
nTableMask:这个值在散列函数根据 key 的哈希值映射元素的时候用到,它的值实际就是 nTableSize 的负数,即 nTableMask = -nTableSize,用位运算来表示就是 nTableMask = ~nTableSize+1;
nNumUsed、nNumOfElements:nNumUsed 是指数组当前使用的 Bucket 数,但不是数组有效元素个数,因为某个数组元素被删除后并没有立即从数组中删除,而是将其标记为 IS_UNDEF,只有在数组需要扩容时才会真正删除,nNumOfElements 则表示数组中有效的元素数量,即调用 count 函数返回值,如果没有扩容,nNumUsed 一直递增,无论是否删除元素;
nNextFreeElement:这个是给自动确定数值索引使用的,默认从 0 开始,比如 $arr[] = 200,这个时候 nNextFreeElement 值会自动加 1;
pDestructor:当删除或覆盖数组中的某个元素时,如果提供了这个函数句柄,则在删除或覆盖时调用此函数,对旧元素进行清理;
u:这个联合体结构主要用于一些辅助作用
Bucket 的结构比较简单,主要用来保存元素的 key 和 value,以及一个整型的 h(散列值,或者叫哈希值):如果元素是数值索引,则其值就是数值索引的值;如果是字符串索引,那么其值就是 key 通过 Time33 算法计算得到的散列值,h 的值用来最终映射元素的存储位置。Bucket 的数据结构如下:
10、介绍下MYSQL中 Innodb的索引是怎么实现的;主键索引和非主键索引有什么区别;什么情况下可以避免回表
11、MYSQL中事物是否有使用过,在使用事物的过程中是否有什么自己的原则
12、在我们对数据库进行查询、写入操作的时候,如果表里数据量比较大性能会下降,(使用量上来)性能下降的原因是什么,怎么优化
13、在我们写接口的时候通常会依赖于第三方提供的一些服务,服务与服务之间的数据一致性应该如何保证(结合自己的业务场景简单聊一聊)
14、订单服务调用库存扣减服务,怎么保证数据一致性(实际的做法是怎样的)
15、库存、金融之类的一些服务如果接口支持重试机制的话,接口需要提供什么特性才可以支持?如何实现接口的幂等性