这篇文章主要介绍“PHP面试题基础知识有哪些”,在日常操作中,相信很多人在PHP面试题基础知识有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”PHP面试题基础知识有哪些”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一、php 数组底层实现原理
1、底层实现是通过散列表(hash table) + 双向链表(解决hash冲突)
hashtable:将不同的关键字(key)通过映射函数计算得到散列值(Bucket->h) 从而直接索引到对应的Bucket
hash表保存当前循环的指针,所以foreach 比for更快
Bucket:保存数组元素的key和value,以及散列值h
2、如何保证有序性
3、解决hash重复(php使用的链表法):
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:网关超时
3、502 的原因及解决方法
原因: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降低,负载变高。
设置过大可能导致内存泄露
4)php执行时间超过nginx等待时间
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握手包)
1、实现:
加锁:setnx
解锁:del
锁超时:expire
2、可能出现的问题
1)setnx 和expire非原子性问题(加锁之后还没来得及设置超时就挂了)
解决方案:
Redis 2.6.12以上版本为set指令增加了可选参数,伪代码如下:set(key,1,30,NX),这样就可以取代setnx指令
2)超时误删其他进程锁。(A进程执行超时,导致锁释放,这时候B进程获取锁开始处理请求,这时候A进程处理完成,会误删B进程的锁)
解决方案:只能删除自己进程的锁 (lua脚本防止B进程获取过期锁之后误删A进程的锁)
3)并发场景,A进程执行超时导致锁释放,这时候B进程获取到锁。
解决方案:开启守护进程,给当前进程要过期的锁延时。
4)单点实例安全问题
单机宕机之后导致所有客户端无法获取锁
解决:
主从复制,因为是异步完成的所以无法完全实现解决
九、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),当子进程写完快照内容之后新的文件替换老的文件
2)整个redis数据库只包含一个备份文件
3)性能最大化,只需要fork子进程完成持久化工作,减少磁盘IO
4)持久化之前宕机可能会导致数据丢失
2、AOF持久化 :以日志的形式记录服务器的所有的写、删除操作
1)每接收到一个写的命令用write函数追加到文件appendonly.aof
2)持久化的文件会越来越大,存在大量多余的日志(0 自增100次到100,会产生100条日志记录)
3)可以设置不同的fsync策略
4)AOF文件太大之后会进行重写:压缩AOF文件大小
fork一个子进程,将redis内地数据对象的最新状态写入AOF临时文件(类似rdb快照)
主进程收到的变动会先写入内存中,然后同步到老的AOF文件中(重写失败之后也能保证数据完整性)
子进程完成重写之后会将内存中的新变动同步追加到AOF的临时文件中
父进程将临时AOF文件替换成新的AOF文件,并重命名。之后收到的新命令写入到新的文件中
十一、秒杀设计流程及难点
1、静态缓存
2、nginx 负载均衡
三种方式:DNS轮询、IP负债均衡、CDN
3、限流机制
方式:ip限流、接口令牌限流、用户限流、header动态token(前端加密,后端解密)
4、分布式锁
方式:
5、缓存数据
方式:
缓存击穿:缓存数据预热 + 布隆过滤器/空缓存
缓存雪崩:缓存设置随机过期时间,防止同一时间过期
6、库存及订单
扣库存
redis 自减库存,并发场景下可能导致负数,影响库存回仓:使用lua脚本保证原子性
redis预扣库存之后,然后使用异步消息创建订单并更新库存变动
数据库更新库存使用乐观锁:where stock_num - sell_num > 0
添加消息发送记录表及重试机制,防止异步消息丢失
创建订单
回仓
十二、防 sql 注入
1、过滤特殊字符
2、过滤数据库关键字
3、验证数据类型及格式
4、使用预编译模式,绑定变量
十三、事务隔离级别
1、标准的sql隔离级别实现原理
2、innodb的事务隔离级别及实现原理(!!和上面的不一样,区分理解一个是隔离级别 一个是!!事务!!隔离级别)
1)基本概念
2)事务隔离级别
未提交读
事务对当前读取的数据不加锁,都是当前读
在更新的瞬间加行级共享锁到事务结束释放
提交读
事务对当前读取的数据不加锁,都是快照读
在更新的瞬间加行级排他锁到事务结束释放
可重复读
主从复制的情况下 ,如果没有间隙锁,master库的A、B进程
A进程 delete id < 6 ;然后还没有commit
B进程insert id = 3,commit
A进程提交commit
该场景下,主库会存在一条id =3 的记录,但是binlog里面是先删除再新增就会导致从库没有数据,导致主从的数据不一致
事务对当前读取的数据不加锁,都是快照读
事务再更新某数据的瞬间,必须加行级排他锁(Record 记录锁、GAP间隙锁、next-key 锁),事务结束释放
间隙锁解决的是幻读问题
MVCC的快照解决的是不可重复读问题
串行化
事务读取数据时加表级,当前读
事务更新数据时加表级排他锁
十四、索引原理
索引就是帮助数据库高效查找数据的存储结构,存储再磁盘中,需要消耗磁盘IO
1、存储引擎
2、索引类型
hash索引
b-tree、b+tree
b+tree 的数据全部存储在叶子节点,内部节点只存key,一次磁盘IO能获取到更多的节点
b-tree 的内部节点和叶子节点都存储key和数据,查找数据不需要找到叶子节点,内部节点可以直接返回数据
b+tree 增加了叶子节点到相邻节点的指针,方便返回查询遍历
b-tree 和b+tree的去区别
聚簇索引和非聚簇索引
十五、分表 (分库) 的策略
1、流程
评估容量和分表数量-> 根据业务选定分表key->分表规则(hash、取余、range)->执行->考虑扩容问题
2、水平拆分
根据字段水平拆分为多个表
每个表的结构相同
所有分表的合集是全量数量
3、垂直拆分
4、问题
跨库join问题
全局表:需要关联部分系统表的场景
冗余法:常用字段进行冗余
组装法:多次查询的结果进行组装
跨节点的分页、排序、函数问题
事务一致性
全局主键id
使用uuid -> 会降低聚簇索引效率
使用分布式自增id
扩容问题
新数据进行双写,同时写进新老数据库
旧数据复制到新数据库
以老数据库为准,验证数据一致性之后删除冗余数据
从库升级为主库,数据一致,只需要删除冗余数据即可
成倍扩容:需要在加一倍从库
升级从库
双写迁移:
十六、select 和 update 的执行流程
1、mysql 构成
2、select 执行过程
3、update执行过程
十七、binlog 的作用和三种格式
作用:
1. 数据恢复
2. 主从复制
格式(二进制文件):
1)statement
2)row
3)mixed
1. 前两个格式的混合版
2. 根据语句自动选择使用哪一种:
十八、主从同步(主从复制)的原理和问题及读写分离
1、解决的问题
数据分布
负载均衡
数据备份,高可用,避免单点失败
实现读写分离,缓解数据库压力
升级测试(使用高版本mysql当从库)
2、支持的复制类型(binlog 的三种格式)
3、原理
1)基础概念
2)流程(主节点必须开启bin log功能,)
1. 从节点开启start slave 命令之后,创建一个IO进程连接到主节点
2. 连接成功之后,主节点创建一个 log dump线程(主节点会为每一个从节点创一个log dump线程)
3. 当binlog发生变化时,主节点的dump log线程会读取bin-log内容并发送给从节点
4. 主节点dump log 线程读取bin-log 的内容时会对主节点的bin-log加锁,读取完成在发送给从节点之前释放锁
5. 从节点的IO线程接收主节点发送的binlog内容,并将其写入本地relay log 文件中
6. 主从节点通过binlog文件+position偏移量定位主从同步的位置,从节点会保存接收到的position偏移量,如果从节点发生宕机重启,自动从postion位置发起同步
7. 从节点的SQL线程复制读取本地relay log的内容,解析成具体的操作并执行,保证主从数据一致性
4、主从复制的模式
1)异步模式(默认方式)
2)全同步模式
3)半同步模式
4)server-id的配置和server-uuid
1. server-id用于标识数据库实例,防止在链式主从、多主多从拓扑中导致SQL语句的无限循环
2. server-id默认值为0,对于主机来说依然会记录二进制日志,但是会拒绝所有的从机连接。
2. server-id = 0 对于从机来说会拒绝连接其他实例
3. server-id是一个全局变量,修改之hi偶必须重启服务
4. 主库和从库的server-id重复时
两个从库(B、C)server-id重复会导致主从连接异常,时断时连
MySQL服务会自动创建并生成server-uuid配置
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、优化方案
二十一、redis 缓存和 mysql 数据一致性
方式:
1、先更新redis 再更新数据库
场景:update set value = 10 where value = 9
1) redis更新成功:redis value = 10
2)数据库更新失败:mysql value = 9
3)数据不一致
2、先更新数据库,再更新redis
场景: 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
3、先删除缓存再更新数据库
场景:A进程update set value = 10 where value = 9 ;B进程查询value;
1)A 进程先删除缓存 还没来得及修改数据或者事务未提交
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;
1)A 进程先删除缓存 还没来得及修改数据或者事务未提交
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放入查询队列,则等待;不存在则重复第二步
4)如果查询的数据发现查询队列已经存在则不需要再次写入队列
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)内存占用
3)插入和删除操作
skiplist只需要修改相邻节点的指针
平衡树变更会引起子树的调整
二十四、redis 的过期删除和淘汰机制
1、常规过期删除策略
1)定时删除
2)惰性删除
3)定期删除
每隔一段时间检查,删除过期的键
删除多少和检查多少有算法决定
2、redis采用的 惰性删除 + 定期删除
周期性随机测试一些设置了过期时间的键进行检查,到期则删除
每次清理的时间不超过CPU的25%,达到时间则退出检查
定期没有删除到的键,且以后不会使用的键还是会存在内存中,所以需要配合淘汰策略
3、淘汰策略(内存不足以写入新数据的时候执行)
volatile-lru :设置了过期时间且最近使用越少越优先淘汰
volatile-ttl :设置了过期时间且过期时间越早越优先淘汰
volatile-random :设置了过期时间中随机删除
allkeys-lru :所有键中过期时间越早越优先淘汰
allkeys-random :所有键中过期随机淘汰
no-enviction :不允许淘汰,内存不足报错
二十五、redis 常见问题及解决方案
1、缓存雪崩:同一时间大量缓存失效,导致请求直接查询数据库,数据库内存和CPU压力增加甚至宕机
解决:
2、缓存穿透:缓存和数据库都没有数据,大量请求下,所有请求直接击穿到数据库,导致宕机。
解决:
3、缓存击穿:数据库中有数据,但是缓存突然失效之后发生大量请求导致数据库压力增加甚至打垮宕机
解决:
热点数据永不过期
互斥锁:获取锁之后不管成功还是失败都要释放锁
二十六、php-fpm 详解及生命周期
1、基础知识
1)CGI协议
2)CGI程序 = php-cgi
php-cgi就是一个遵守CGI协议的CGI程序
同时也就是PHP解释器
标准的CGI每个请求都会解析php.ini,初始化执行环境等,降低性能
每次修改配置之后需要重新php-cgi才能让php.ini生效
不能动态worker调度,只能一开始指定数量的worker
3)FastCGI协议
4)FastCGI程序 = php-fpm
2、php-fpm生命周期:待更新
二十七、Nginx 和 php 之间的通信
1、通信方式:fastcgi_pass
1)tcp socket
2)unix socket
到此,关于“PHP面试题基础知识有哪些”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注天达云网站,小编会继续努力为大家带来更多实用的文章!