相关推荐:2022年PHP面试题大汇总(收藏)
过完年之后打算寻找新的工作机会,发现之前自己对于很多基础的面试理解和学习不够深刻,为了鼓励自己持续前进所以最近开始在论坛和搜索引擎上开始学习和总结相关知识,其中有一些题目时论坛里面一些前辈分享过的题目或者答案,还有一部分时自己最近面试遇到的问题,基于自己的理解和前辈们的分享归档了一部分,所以分享出来,希望对其他的小伙伴们也有帮助,同时也希望能收到大佬们对于理解有误的地方的指导,最近一段时间会持续更新
一、PHP 数组底层实现原理
1、底层实现是通过散列表(hash table) + 双向链表(解决hash冲突)
hashtable:将不同的关键字(key)通过映射函数计算得到散列值(Bucket->h) 从而直接索引到对应的Bucket
hash表保存当前循环的指针,所以foreach 比for更快
Bucket:保存数组元素的key和value,以及散列值h
2、如何保证有序性
2. 用于存储元素在实际存储数组中的下标
3. 元素按照映射表的先后顺序插入实际存储数组中
4. 映射表只是原理上的思路,实际上并不会有实际的映射表,而是初始化的时候分配Bucket内存的同时,还会分配相同数量的 uint32_t 大小的空间,然后将 arData 偏移到存储元素数组的位置。
1. 链表法:不同关键字指向同一个单元时,使用链表保存关键字(遍历链表匹配key)
2. 开放寻址法:当关键字指向已经存在数据的单元的时候,继续寻找其他单元,直到找到可用单元(占用其他单元位置,更容易出现hash冲突,性能下降)
4、基础知识
链表:队列、栈、双向链表、
链表 :元素 + 指向下一元素的指针
双向链表:指向上一元素的指针 + 元素 + 指向下一元素的指针
参考:
二、冒泡排序的时间复杂度和空间复杂度
1、代码实现
$arr = [2, 4, 1, 5, 3, 6]; for ($i = 0; $i < (count($arr)); $i++) { for ($j = $i + 1; $j < (count($arr)); $j++) { if ($arr[$i] <= $arr[$j]) { $temp = $arr[$i]; $arr[$i] = $arr[$j]; $arr[$j] = $temp; } } } result : [6,5,4,3,2,1]
2、计算原理
第一轮:将数组的第一个元素和其他所有的元素进行比较,哪个元素更大,就换顺序,从而冒泡出第一大(最大)的元素
第一轮:将数组的第二个元素和其他所有的元素进行比较(第一大已经筛选出来不用继续比较了),哪个元素更大,就换顺序,从而冒泡出第二大的元素
... 依次类推,冒泡从大到小排序的数组
平均时间复杂度:O(n^2)
;
最优时间复杂度:O(n)
,需要加判断,第一次循环如果一次都没有交换就直接跳出循环
空间复杂度:O(1)
,交换元素的时候的临时变量占用的空间
最优空间复杂度:O(1)
,排好序,不需要交换位置
3、时间复杂度和空间复杂度
时间复杂度:全程为渐进时间复杂度,估算对处理器的使用效率(描述算法的效率趋势,并不是指算法具体使用的时间,因为不同机器的性能不一致,只是一种效率计算的通用方法)
表示方法:大O符号表示法
复杂度量级:
常数阶O(1)
线性阶O(n)
平方阶O(n²)
立方阶O(n³)
K次方阶O(n^k)
指数阶(2^n)
对数阶O(logN)
线性对数阶O(nlogN)
时间复制类型:
最好时间复杂度
最坏时间复杂度
平均时间复杂度
均摊时间复杂度
空间复杂度:全程渐进空间复杂度,估算对计算机内存的使用程度(描述算法占用的存储空间的趋势,不是实际占用空间,同上)
参考:
三、网络七层协议及 TCP 和 TCP
应用层、表示层、会话层、传输层、网络层、(数据)链路层、物理层
记忆套路:
首字:应表会传(物链网)
前四个正向:应表 - 会传
后三个反向:物联网谐音比网链物更好记
四、TCP 和 UDP 的特点和区别
1、都是属于传输层协议
2、TCP
面向连接,所以只能一对一
面向字节流传输
数据可靠,不丢失
全双工通信
3、UDP(根据TCP特点反记)
无连接,支持一对一,一对多,多对多
面向保温传输
首部开销小,数据不一定可靠但是速度更快
五、TCP 的三次握手和四次挥手
1、三次握手:
1)第一次:客户端发送SYN = 1,seq = client_isn
作用:
客户端:无
2)第二次:服务端发送SYN = 1,seq = server_isn,ACK =client_isn +1
作用:
客户端:确认自己发送和接收都正常,确认服务端的接收和发送正常
服务端:确认自己的接收正常,确认服务端的发送正常(这时候服务端还不能确认客户端接收是否正常)
3)第三次:客户端发送SYN = 0, ACK = server_isn+1,seq =client_isn+1
作用:双方确认互相的接收和发送正常,建立连接
2、四次挥手
1)第一次:客户端发送FIN
作用:告诉服务端我没有数据发送了(但是还能接收数据)
2)第二次:服务端发送ACK
作用:告诉客户端收到请求了,可能服务端可能还有数据需要发送,所以客户端收到进入FIN_WAIT状态,等服务端数据传输完之后发送FIN
3)第三次:服务端发送FIN
作用:服务端告诉客户端我发送完了,可以关闭连接了。
4)第四次:客户端发送ACK
作用:客户端收到FIN之后,担心服务端不知道要关闭,所以发送一个ACK,进入TIME_WAIT,等待2MSL之后如果没有收到回复,证明服务端已经关闭了,这时候客户端也关闭连接。
注意:
当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据
最后需要等待2MSL是因为网络是不可靠的,如果服务端没有收到最后一次ACK,服务端会重新放FIN包然后等客户端再次发送ACK包然后关闭(所以客户端最后发送ACK之后不能立即关闭连接)
六、HTTP 状态码
1、状态码分类
2、常用状态码
200:请求成功
301:永久重定向
302:临时移动
400 bad request:客户端请求语法错误
401 unauthorized:客户端没有权限
403 forbidden:服务器拒绝客户端请求
404 not found:客户端请求资源不存在
500 Internal Server Eerro:服务器内部错误
502 bad gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
503 Service Unavailable 超载或系统维护
504 Gateway timeout:网关超时
原因:Nginx将请求提交给网关(PHP-fpm)处理异常导致
1)fastcgi 缓冲区设置过小
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
2)php-cgi的进程数设置过少
查看FastCgi进程数:netstat -anpo | grep php-cgi| wc -l
调整参数最大子进程数:max_children
一般按照单个进程20M计算需要需要设置的子进程数
3)max_requests(内存溢出或频繁重启)
参数指明每个children最多能处理的请求数量,到达最大值之后会重启children。
设置过小可能导致频繁重启children:
PHP将请求轮询给每个children,在大流量的场景下,每一个children 到达最大值的时间差不多,如果设置过小可能多个children 在同一时间关闭,Nginx无法将请求转发给PHP-fpm,cpu降低,负载变高。
设置过大可能导致内存泄露
fastcgi_connect_timeout
fastcgi_send_timeout
fastcgi_read_timeout
5)fastcgi执行时间
max_execution_time
参考:
七、http 和 HTTPS 的区别
1、端口:http 80; https :443
2、http无状态,https是有http + ssl构建的可进行加密传输的协议
3、http明文传输,https加密传输
4、http更快,三次握手三个包,https 需要12个包(3个tcp包+9个ssl握手包)
八、redis 分布式锁及问题
1、实现:
加锁:setnx
解锁:del
锁超时:expire
2、可能出现的问题
1)setnx 和expire非原子性问题(加锁之后还没来得及设置超时就挂了)
解决方案:
Redis 2.6.12以上版本为set指令增加了可选参数,伪代码如下:set(key,1,30,NX),这样就可以取代setnx指令
2)超时误删其他进程锁。(A进程执行超时,导致锁释放,这时候B进程获取锁开始处理请求,这时候A进程处理完成,会误删B进程的锁)
3)并发场景,A进程执行超时导致锁释放,这时候B进程获取到锁。
解决方案:开启守护进程,给当前进程要过期的锁延时。
4)单点实例安全问题
单机宕机之后导致所有客户端无法获取锁
解决:
主从复制,因为是异步完成的所以无法完全实现解决
参考:
九、redis 为什么是单线程?为什么快?
推荐阅读:/redis/475918.html
十、redis 的数据类型及应用场景
1、string :
普通的key/value存储
2、hash:
hashmap:键值队集合,存储对象信息
3、list:
双向链表:消息队列
4、set:
value永远为null的hashMap:无序集合且不重复:计算交集、并集、差集、去重值
5、zset:
有序集合且不重复:hashMap(去重) + skiplist跳跃表(保证有序):排行榜
参考:
十一、redis 实现持久化的方式及原理、特点
1、RDB持久化(快照):指定时间间隔内的内存数据集快照写入磁盘
1)fork一个子进程,将快照内容写入临时RDB文件中(dump.rdb),当子进程写完快照内容之后新的文件替换老的文件
3)性能最大化,只需要fork子进程完成持久化工作,减少磁盘IO
4)持久化之前宕机可能会导致数据丢失
2、AOF持久化 :以日志的形式记录服务器的所有的写、删除操作
1)每接收到一个写的命令用write函数追加到文件appendonly.aof
2)持久化的文件会越来越大,存在大量多余的日志(0 自增100次到100,会产生100条日志记录)
3)可以设置不同的fsync策略
appendfsync everysec :1s一次,最多丢失1s的数据(默认)
appendfsync always :每次变动都会执行一次
appendfsync no :不处理
参考:
十二、秒杀设计流程及难点
1、静态缓存
三种方式:DNS轮询、IP负债均衡、CDN
3、限流机制
方式:ip限流、接口令牌限流、用户限流、header动态token(前端加密,后端解密)
4、分布式锁
方式:
setnx + expire (非原子性,redis2.6 之后set保证原子性)
释放锁超时 (开启守护进程自动续时间)
过期锁误删其他线程(requestId验证或者lua脚本保证查 + 删的原子性)
5、缓存数据
方式:
缓存击穿:缓存数据预热 + 布隆过滤器/空缓存
缓存雪崩:缓存设置随机过期时间,防止同一时间过期
6、库存及订单
扣库存
创建订单
前端建立websocket连接或者轮询监听订单状态
消费验证记录状态,防止重复消费
回仓
创建订单之后发送延时消息,验证订单支付状态及库存是否需要回仓
十三、防 sql 注入
1、过滤特殊字符
2、过滤数据库关键字
3、验证数据类型及格式
4、使用预编译模式,绑定变量
十四、事务隔离级别
1、标准的sql隔离级别实现原理
未提交读:其他事务可以直接读到没有提交的:脏读
事务对当前被读取的数据不加锁
在更新的瞬间加行级共享锁到事务结束释放
提交读:事务开始和结束之间读取的数据可能不一致,事务中其他事务修改了数据:不可重复度
事务对当前读取的数据(被读到的时候)行级共享锁,读完释放
在更新的瞬间加行级排他锁到事务结束释放
可重复读:事务开始和结束之前读取的数据保持一致,事务中其他事务不能修改数据:可重复读
串行化
事务读取数据时加表级共享锁
事务更新数据时加表级排他锁
2、innodb的事务隔离级别及实现原理(!!和上面的不一样,区分理解一个是隔离级别 一个是!!事务!!隔离级别)
1)基本概念
mvcc:多版本并发控制:依赖于undo log 和read view
当前读:读取的是最新版本
快照读:读取的是历史版本
间隙锁:间隙锁会锁住一个范围内的索引
update id between 10 and 20
无论是否范围内是否存在数据,都会锁住整个范围:insert id = 15,将被防止
只有可重复读隔离级别才有间隙锁
next-key lock:
2)事务隔离级别
未提交读
事务对当前读取的数据不加锁,都是当前读
在更新的瞬间加行级共享锁到事务结束释放
提交读
事务对当前读取的数据不加锁,都是快照读
在更新的瞬间加行级排他锁到事务结束释放
可重复读
串行化
事务读取数据时加表级,当前读
事务更新数据时加表级排他锁
参考:
十五、索引原理
索引就是帮助数据库高效查找数据的存储结构,存储再磁盘中,需要消耗磁盘IO
1、存储引擎
2、索引类型
hash索引
b-tree、b+tree
聚簇索引和非聚簇索引
概念
聚簇索引 :索引和数据存储在一个节点
非聚簇索引:索引和数据分开存储,通过索引找到数据实际存储的地址
详解:
innodb 使用的聚簇索引,且默认主键索引为聚簇索引(没有主键索引的时候,选择一个非空索引,还没有则隐式的主键索引),辅助索引指向聚簇索引位置,然后在找到实际存储地址
myisam 使用非聚簇索引,所有的索引都只需要查询一次就能找到数据
聚簇索引的优势和略势
1. 索引和数据在一起,同一页的数据会被缓存到(buffer)内存中,所以查看同一页数据的时候只需要从内存中取出,
2. 数据更新之后之只需要维护主键索引即可,辅助索引不受影响
3. 辅助索引存的是主键索引的值,占用更多的物理空间。所以会受到影响
4. 使用随机的UUID,数据分布不均匀,导致聚簇索引可能扫全表,降低效率,所以尽量使用自增主键id
十六、分表 (分库) 的策略
1、流程
评估容量和分表数量-> 根据业务选定分表key->分表规则(hash、取余、range)->执行->考虑扩容问题
2、水平拆分
根据字段水平拆分为多个表
每个表的结构相同
所有分表的合集是全量数量
3、垂直拆分
4、问题
跨库join问题
全局表:需要关联部分系统表的场景
冗余法:常用字段进行冗余
组装法:多次查询的结果进行组装
事务一致性
全局主键id
使用uuid -> 会降低聚簇索引效率
使用分布式自增id
扩容问题
十七、select 和 update 的执行流程
1、MysqL 构成
server层:连接器->缓存器->分析器(预处理器)->优化器->执行器
引擎层 : 查询和存储数据
2、select 执行过程
3、update执行过程
基础概念
buffer pool(缓存池),在内存中,下次读取同一页的数据的时候可以直接从buffer pool中返回(innodb的聚簇索引)
更新数据的时候先更新buffer pool,然后在更新磁盘
脏页:内存中的缓存池更新了,但是没有更新磁盘
redo log 和 binlog
WAL(write-ahead-logging)先写日志方案
redo log 刷盘机制,check point
redo log大小固定,循环写入
redo log 就像一个圆圈,前面是check point (到这个point就开始覆盖老的日志),后面是write point (当前写到的位置)
write point 和check point 重叠的时候就证明redo log 满了,需要开始同步redo log 到磁盘中了
执行步骤(两阶段提交 - 分布式事务,保证两个日志的一致性)
宕机后数据崩溃恢复规则
如果redo log 状态为commit ,则直接提交
如果redo log 状态为prepare,则判断binlog 中的事务是否commit,是则提交,否则回滚
如果不使用两次提交的错误案例(update table_x set value = 10 where value = 9)
undo log
在更新写入buffer pool之前记录
如果更新过程中出错,直接回滚到undo log 的状态
十八、binlog 的作用和三种格式
作用:
1. 数据恢复
2. 主从复制
格式(二进制文件):
1)statement
1. 记录每次sql语句的原文
3. 可能出现主从不一致(存储过程、函数等)
4. RC隔离级别(读提交),因为binlog 记录顺序是按照事务commit 顺序记录的,所以可能导致主从复制不一致。通过可重复读级别的间隙锁的引入,可以解决。
2)row
3)mixed
十九、主从同步(主从复制)的原理和问题及读写分离
1、解决的问题
基于sql语句的复制
基于行的复制
混合型复制
3、原理
1)基础概念
2)流程(主节点必须开启bin log功能,)
1. 从节点开启start slave 命令之后,创建一个IO进程连接到主节点
4. 主节点dump log 线程读取bin-log 的内容时会对主节点的bin-log加锁,读取完成在发送给从节点之前释放锁
6. 主从节点通过binlog文件+position偏移量定位主从同步的位置,从节点会保存接收到的position偏移量,如果从节点发生宕机重启,自动从postion位置发起同步
4、主从复制的模式
1)异步模式(默认方式)
1. 可能导致主从不一致(主从延时)
2. 主节点接收到客户端提交的事务之后直接提交事务并返回给客户端
3. 如果主节点事务提交之后,log dump还没来得及写入就宕机就会导致主从数据不一致
4. 不用关心主从的同步操作,性能最好
2)全同步模式
1. 可靠更高,但是会影响主库相应时间
2. 主节点接收到客户端提交的事务之后,必须等待binlog 发送给从库,并且所有从库全部执行完事务之后才返回给客户端
3)半同步模式
4)server-id的配置和server-uuid
2. server-id默认值为0,对于主机来说依然会记录二进制日志,但是会拒绝所有的从机连接。
2. server-id = 0 对于从机来说会拒绝连接其他实例
4. 主库和从库的server-id重复时
两个从库(B、C)server-id重复会导致主从连接异常,时断时连
主库(A)发现相同的server-id会断开之前的连接,重新注册新的连接
B、C从库的连接会周而复始的重连
5、读写分离
1)基于代码实现,减少硬件开支
2)基于中间代理实现
3)主从延时
二十、死锁
1、产生的四个必要条件
1. 互斥条件
2. 请求与保持条件:一次性分配全部资源,否则一个都不分配
3. 非剥夺条件:当进程获得一部分资源等待其他资源的时候释放占有的资源
4. 循环等待条件:
理解:一个资源只能被一个进程占用,进程获取资源资源还能申请新的资源,并且已经获得的资源不能被剥夺,同时多个进程相互等待其他进程被占用的资源
2、解除死锁
1. 终止进程(全部干掉)
2. 逐个种植(杀一个看一下有没有解除)
二十一、MysqL 优化大分页查询 limit 100000 (offset),10 (page_sie)
1、原因
mySQL查询分页数据时不是直接跳过offset(100000),而是取offset + page_size = 100000 + 10 = 100010条数据,然后放弃其掉前面的100000条数据,所以效率地下
2、优化方案
延时关联:使用覆盖索引
主键阈值法:主键是自增的情况下,通过条件推算出符合条件的主键最大值&最小值(使用覆盖索引)
记录上一页的结果位置,避免使用 OFFSET
二十二、redis 缓存和 MysqL 数据一致性
方式:
场景:update set value = 10 where value = 9
3)数据不一致
场景: A进程update set value = 10 where value = 9 ;B进程 update set value = 11 where value = 9;
1)A 进程先更新数据库,还未写入缓存:MysqL value = 10 ;redis value = 9
2)B 进程更新数据库并且提交事务,写入缓存:MysqL value = 11;redis value = 11;
3)A 进程处理完请求提交事务,写入缓存:redis value = 10;
4)最终 MysqL value = 11; redis value = 10
场景:A进程update set value = 10 where value = 9 ;B进程查询value;
2)B 进程开始查询,没有命中缓存,所以查库并写入缓存 redis value = 9
3)A 进程更新数据库完成 MysqL value = 10
4)最终 MysqL value = 10;redis value = 9
解决方案:
1、延时双删除
场景:A进程update set value = 10 where value = 9 ;B进程查询value;
2)B 进程开始查询,没有命中缓存,所以查库并写入缓存 redis value = 9
3)A 进程更新数据库完成 MysqL value = 10
4)A 进程估算延时时间,sleep之后再次删除缓存
5)最终MysqL value = 10;redis value 为空(下次查询直接查库)
6)延时的原因时防止B进程在A进程更新完之后B进程还没来得及写入缓存
2、请求串行化
1)创建两个队列 :更新队列和查询队列
2)当缓存不存在需要查库的时候将key存入更新队列
3)如果查询未完成之前有新的请求进来,并且发现更新队列中还存在key则将key放入查询队列,则等待;不存在则重复第二步
5)数据更新完成之后rpop更新队列,同时rpop查询队列,释放查询请求
6)查询请求可以使用while + sleep 查询缓存并且设置最大延迟时间,还没有完成则返回空
二十三、redis 中的 connect 和 pconnect
1、connect :脚本结束之后释放连接
1. close :释放连接
2、pconnect(长连接) :脚本结束连接不释放,连接保持在PHP-fpm进程中,生命周期随着PHP-fpm进程的生命周期
3、pconnect 的连接复用问题
二十四、redis zset 有序集合使用 skiplist 的原理
1、基本概念
1. skiplist是一个随机的数据,以有序的方式在层次化的链表中保存元素(只能用于元素有序的情况)
2. skiplist实在有序链表和多层链表的基础上演变的
3. 允许重复值,所以对比检查除了要对比key 还要对比value
4. 每个节点都带有一个高度为1的后退指针,用于表头方向到表尾方向的迭代
5. 时间复杂度O(logn)、空间复杂度O(n)
2、跳跃表和平衡树的对比
1)范围查询效率
2)内存占用
skiplist 每个节点的指针数量为1/(1-p)
平衡树的每个节点指针数都为2
3)插入和删除操作
skiplist只需要修改相邻节点的指针
平衡树变更会引起子树的调整
二十五、redis 的过期删除和淘汰机制
1、常规过期删除策略
1)定时删除
2)惰性删除
3)定期删除
3、淘汰策略(内存不足以写入新数据的时候执行)
volatile-lru :设置了过期时间且最近使用越少越优先淘汰
volatile-ttl :设置了过期时间且过期时间越早越优先淘汰
allkeys-lru :所有键中过期时间越早越优先淘汰
allkeys-random :所有键中过期随机淘汰
no-enviction :不允许淘汰,内存不足报错
二十六、redis 常见问题及解决方案
1、缓存雪崩:同一时间大量缓存失效,导致请求直接查询数据库,数据库内存和cpu压力增加甚至宕机
解决:
2、缓存穿透:缓存和数据库都没有数据,大量请求下,所有请求直接击穿到数据库,导致宕机。
解决:
布隆过滤器:长度为m的位向量或者位列表组成(仅包含0或1位值的列表)
空缓存(短时效)
业务层参数过滤
3、缓存击穿:数据库中有数据,但是缓存突然失效之后发生大量请求导致数据库压力增加甚至打垮宕机
解决:
热点数据永不过期
互斥锁:获取锁之后不管成功还是失败都要释放锁
二十七、PHP-fpm 详解及生命周期
1、基础知识
1)CGI协议
2)CGI程序 = php-cgi
3)FastCGI协议
4)FastCGI程序 = PHP-fpm
FastCGI程序对CGI程序的管理模式
重启类型
优雅重启
强制重启
2、PHP-fpm生命周期:待更新
PHP-FPM 生命周期:https://www.abelzhou.com/PHP/PHP-fpm-lifespan/#
参考:
二十八、Nginx 和 PHP 之间的通信
1、通信方式:fastcgi_pass
1)tcp socket
2)unix socket
不需要网络协议栈、打包拆包等
减少tcp 开销,效率比tcp socket 更高
高并发时候不稳定,连接数暴增产生大量的长时缓存,大数据包可能直接返回异常
参考:
二十九、web 漏洞及问题
1、sql注入
2、XSS攻击
推荐阅读(很详细的案例来分析XSS攻击的不同类型及解决方案):[前端安全系列(一):如何防止XSS攻击?](https://tech.meituan.com/2018/09/27/fe-security.html)
3、CSRF攻击:
推荐阅读 :[前端安全系列(二):如何防止CSRF攻击?](https://tech.meituan.com/2018/10/11/fe-security-csrf.html)
推荐阅读 :[浅析文件上传漏洞](https://xz.aliyun.com/t/7365)
5、跨域问题:
1)jsonp
2)cors
3)Nginx代理
推荐学习:《PHP教程》
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。