开场白
年前最后几天,准备了一场面试。是PHP开发岗位。面试题都还算是蛮基础,也是常被问到的问题。这里总结出来几道蛮不错的问题。其他的问题,我也做了一些整理,有兴趣的可以看一看,都是一些经典的面试题。
说说php-fpm启动进程相关方面的设置?
针对php-fpm进程的管理,需要在php-fpm.conf配置文件中进行修改。进程运行的模式就只有动态(dynamic)和静态(static)。
- 首先,我们关注一个前提设置:pm = static/dynamic,标识fpm子进程的产生模式。
- static(静态) :表示在fpm运行时直接fork出pm.max_chindren个worker进程。
- dynamic(动态):表示运行时fork出start_servers个进程,随着负载的情况,动态的调整,最多不超过max_children个进程。
❝一般推荐用static,优点是不用动态的判断负载情况,提升性能,缺点是多占用些系统内存资源。 ❞
max_children
- 这个值原则上是越大越好,php-cgi的进程多了就会处理的很快,排队的请求就会很少。
- 设置”max_children”也需要根据服务器的性能进行设定。
- 一般来说一台服务器正常情况下每一个php-cgi所耗费的内存在20M左右。
- 假设“max_children”设置成100个,20M*100=2000M。
- 也就是说在峰值的时候所有PHP-CGI所耗内存在2000M以内。
- 假设“max_children”设置的较小,比如5-10个,那么php-cgi就会“很累”,处理速度也很慢,等待的时间也较长。
- 如果长时间没有得到处理的请求就会出现504 Gateway Time-out这个错误,而正在处理的很累的那几个php-cgi如果遇到了问题就会出现502 Bad gateway这个错误。
start_servers
- pm.start_servers的默认值为2。并且php-fpm中给的计算方式也为:
❝{(cpu空闲时等待连接的php的最小子进程数) (cpu空闲时等待连接的php的最大子进程数 - cpu空闲时等待连接的php的最小子进程数)/ 2};用配置表示就是:min_spare_servers (max_spare_servers - min_spare_servers) / 2;一般而言,设置成10-20之间的数据足够满足需求了。 ❞
说说PHP的生命周期是怎么样的?以及每个阶段分别都做了什么操作?
php的运行模式有两种:web模式和cli模式。无论是哪种公众模式,php的工作原理都是一样的,都是作为一种SAPI运行。首先,认识下SAPI,它是什么。Sapi全称是Server Application Programming Interface,也就是服务端应用编程接口,Sapi通过一系列钩子函数,使得PHP可以和外围交互数据,这是PHP非常优雅和成功的一个设计,通过sapi成功的将PHP本身和上层应用解耦隔离,PHP可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。SAPI运行PHP都经过下面几个阶段: 1、模块初始化阶段(module init) 这个阶段主要进行php框架、zend引擎的初始化操作。这个阶段一般是在SAPI启动时执行一次,对于FPM而言,就是在fpm的master进行启动时执行的。php加载每个扩展的代码并调用其模块初始化例程(MINIT),进行一些模块所需变量的申请,内存分配等。
2、请求初始化阶段(request init) 当一个页面请求发生时,在请求处理前都会经历的一个阶段。对于fpm而言,是在worker进程accept一个请求并读取、解析完请求数据后的一个阶段。在这个阶段内,SAPI层将控制权交给PHP层,PHP初始化本次请求执行脚本所需的环境变量。比如接收客户端发送的post请求数据信息、http请求报文信息等。
3、php脚本执行阶段 php代码解析执行的过程。Zend引擎接管控制权,将php脚本代码编译成opcodes并顺次执行。这也我们的代码真正执行的阶段。
4、请求结束阶段(request shutdown) 请求处理完后就进入了结束阶段,PHP就会启动清理程序。这个阶段,将flush输出内容、发送http响应内容等,然后它会按顺序调用各个模块的RSHUTDOWN方法。RSHUTDOWN用以清除程序运行时产生的符号表,也就是对每个变量调用unset函数。比如清除请求初始化阶段获取到的post请求参数、一些代码变量等。
5、模块关闭阶段(module shutdown) 该阶段在SAPI关闭时执行,与模块初始化阶段对应,这个阶段主要是进行资源的清理、php各模块的关闭操作,同时,将回调各扩展的module shutdown钩子函数。这是发生在所有请求都已经结束之后,例如关闭fpm的操作。(这个是对于CGI和CLI等SAPI,没有“下一个请求”,所以SAPI立刻开始关闭。)
说说fastcgi与cgi之间的区别是什么?
定义 CGI:通用网关接口协议(CGI)是一种对接应用程序和网络服务器的接口协议
。CGI使外部程序与Web服务器之间交互成为可能。CGI程序运行在独立的进程中,并对每个Web请求创建一个进程,这种方法非常容易实现,但效率较差,难以扩展。CGI程序运行在独立的进程中,并对每个Web请求创建一个进程,在结束时销毁
。这种“每个请求一个新进程”的模型使得CGI程序非常容易实现,但效率较差,难以扩展。在高负载情况下,进程创建和销毁进程的开销变得很大
。此外,由于地址空间无法共享,CGI进程模型限制了资源重用方法,如重用数据库连接、内存缓存等。
FastCGI:快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。FastCGI致力于减少网页服务器与CGI程序之间交互的开销
,从而使服务器可以同时处理更多的网页请求。
区别
- CGI每一次请求都会创建一个进程,在请求结束之后进程会销毁。每一个请求,都重复执行这样的逻辑。FastCGI与最大的区别在于,使用持续的进程来处理一连串的请求,不会在请求结束之后关闭进程,而是下一个请求来了之后继续使用。这些进程由FastCGI服务器管理,而不是web服务器。FastCGI具体的进程数量可以通过php-fpm.conf中的pm配置项进行操作。
- 当进来一个请求时,web服务器把环境变量和这个页面请求通过一个socket比如FastCGI进程与web服务器(都位于本地)或者一个TCP 请求(FastCGI进程在远端的server farm)传递给FastCGI进程。服务传入请求时,网络服务器通过Unix域套接字、命名管道或TCP连接向FastCGI进程发送环境变量信息和页面请求。响应通过相同的连接从进程返回到网络服务器,然后网络服务器将该响应传递给最终用户。连接可能在响应结束时关闭,但是web服务器和FastCGI服务进程都将持续,不会被销毁。
- 每个单独的FastCGI进程在其生命周期内可以处理许多请求,从而避免了每个请求进程创建和终止的开销。并发处理多个请求可以通过几种方式来完成:通过内部多路复用使用一个连接(即一个连接上的多个请求);通过使用多个连接;或者通过这些方法的混合。可以配置多个FastCGI服务器,提高稳定性和可扩展性。
能简单的描述一下Nginx与PHP通信的基本流程吗?
- WebServer在启动时,载入FastCGI管理器。
- FastCGI会完成初始化,启动多个CGI解释器。并等待客户端的连接 当客户端连接的时候,FastCGI管理器会连接其中的一个CGI解释器。webserver 将标准输入和cgi配置发送给cgi。
- FastCGI将标准输出和错误输出返回给WebServer。当FastCGI关闭时,意味着本次请求完成。然后FastCGI等待着FastCGI管理器给他提供的下一次请求。
- PHP请求过来的时候,NGINX会将请求发送给FastCGI的Master,发送给Worker。将编译后的结果发送个Nginx,然后返回给客户端。
能说一下PHP的垃圾回收机制是如何实现的吗?
- 在创建一个PHP变量时,会将这个变量存在zavl变量容器中。这个容器存储的是这个变量的类型和值,初次之外还会存储is_ref和refcount两个额外的字段。
- refcount表示指向变量的元素个数,is_ref表示变量是否有别名(是否被引用)。
- 如果refcount为0时,就回收该变量容器。如果一个zval的refcount减1之后大于0,它就会进入垃圾缓冲区。
- 当缓冲区达到最大值后,回收算法会循环遍历zval,判断其是否为垃圾,并进行释放处理。
- 当前请求结束之后,PHP执行脚本结束,也会清楚所有的变量信息。
官网文档:
引用计数基本知识
每个php变量存在一个叫"zval"的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope),那些主脚本(比如:通过浏览器请求的的脚本)和每个函数或者方法也都有作用域。
引用计数基本知识
每个php变量存在一个叫"zval"的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope),那些主脚本(比如:通过浏览器请求的的脚本)和每个函数或者方法也都有作用域。
说说php的同步模式与swoole的携程之间的区别?
- 首先,Swoole 只能运行在命令行(Cli)模式下,所以我们开发调试都是使用命令行,而不是 php-fpm/apache 等。在 Swoole 中,我们可以使用
SwooleCoroutine::create()
创建协程,或者你也可以使用简写go()
。 - 我们一直在说 Swoole 协程适合用于 I/O 密集场景,在同样的硬件配置环境下,它会比传统的同步模式承载更多的访问量。我们熟悉的文件读写、网络通讯请求(MySQL、Redis、Http等)都是属于 I/O 密集型场景。假设一次 SQL 查询为 100ms,在传统同步模式下,当前进程在这 100ms 的时间里,是不能做其它操作的。如果要执行十次这个 SQL,可能需要耗费 1s 以上。而如果用协程,虽然不同协程之间也是按顺序执行,但是在前一个等待 100ms 期间,底层会调度 CPU,去执行其它协程的操作。也就是说,可能第一个查询还没返回结果,其它几个查询就已经发送给了 MySQL 并正在执行中了。如果开启十个协程,分别执行这个 SQL,可能只需要耗费 100 ms 即可完成。
php-fpm与swoole之间有什么区别?
php-fpm与swoole介绍:
- 早期版本的 PHP 并没有内置的 WEB 服务器,而是提供了 SAPI(Server API)给第三方做对接。现在非常流行的 php-fpm 就是通过 FastCGI 协议来处理 PHP 与第三方 WEB 服务器之间的通信。
- Swoole 采用的也是 Master/Worker 模式,不同的是 Master 进程有多个 Reactor 线程,Master 只是一个事件发生器,负责监听 Socket 句柄的事件变化。Worker 以多进程的方式运行,接收来自 Reactor 线程的请求,并执行回调函数(PHP 编写的)。启动 Master 进程的流程大致是:初始化模块。初始化请求。因为 swoole 需要通过 cli 的方式运行,所以初始化请求时,不会初始化 PHP 的全局变量,如
_POST, $_GET 等。执行 PHP 脚本。包括词法、语法分析,变量、函数、类的初始化等,Master 进入监听状态,并不会结束进程。
Swoole 加速的原理
由 Reactor(epoll 的 IO 复用方式)负责监听 Socket 句柄的事件变化,解决高并发问题。通过内存常驻的方式节省 PHP 代码初始化的时间,在使用笨重的框架时,用 swoole 加速效果是非常明显的。
php-fpm与swoole区别
- PHP-FPM是Master 主进程 / Worker 多进程模式。
- 启动 Master,通过 FastCGI 协议监听来自 Nginx 传输的请求。
- 每个 Worker 进程只对应一个连接,用于执行完整的 PHP 代码。
- PHP 代码执行完毕,占用的内存会全部销毁,下一次请求需要重新再进行初始化等各种繁琐的操作。
- 比较适用于HTTP Server。
- Swoole是Master 主进程(由多个 Reactor 线程组成)/ Worker 多进程(或多线程)模式。
- 启动 Master,初始化 PHP 模块,由 Reactor 监听 Socket 句柄的事件变化。
- Reactor 主线程负责子多线程的均衡问题,Manager 进程管理 Worker 多进程,包括 TaskWorker 的进程。
- 每个 Worker 接受来自 Reactor 的请求,只需要执行回调函数部分的 PHP 代码。
- 只在 Master 启动时执行一遍 PHP 初始化代码,Master 进入监听状态,并不会结束进程。
- 不仅可以用于 HTTP Server,还可以建立 TCP 连接、WebSocket 连接。
索引有哪些优缺点?
索引的优点
- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
- 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
索引的缺点
- 时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;
- 空间方面:索引需要占物理空间。
MySQL有哪几种索引类型?
- 从数据结构上来划分:BTree索引(B-Tree或B Tree索引)、Hash索引、full-index(全文索引)、R-Tree索引(空间索引)。这里所描述的是索引存储时保存的形式。
- 从应用层次来分:普通索引,唯一索引,复合索引。普通索引:即一个索引只包含单个列,一个表可以有多个单列索引。唯一索引:索引列的值必须唯一,但允许有空值。复合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。聚簇索引(聚集索引):并不是一种单独的索引类型,而是一种数据存储方式。具体细节取决于不同的实现,InnoDB的聚簇索引其实就是在同一个结构中保存了B-Tree索引(技术上来说是B Tree)和数据行。非聚簇索引:不是聚簇索引,就是非聚簇索引。
- 根据中数据的物理顺序与键值的逻辑(索引)顺序关系:聚集索引,非聚集索引。
讲一讲聚簇索引与非聚簇索引?
在 InnoDB 里,索引B Tree的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引,即将数据存储与索引放到了一块,找到索引也就找到了数据。而索引B Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引、二级索引。聚簇索引与非聚簇索引的区别:
- 非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键(行号)
- 对于InnoDB来说,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。第一次索引一般是顺序IO,回表的操作属于随机IO。需要回表的次数越多,即随机IO次数越多,我们就越倾向于使用全表扫描 。
- 通常情况下, 主键索引(聚簇索引)查询只会查一次,而非主键索引(非聚簇索引)需要回表查询多次。当然,如果是覆盖索引的话,查一次即可
- 注意:MyISAM无论主键索引还是二级索引都是非聚簇索引,而InnoDB的主键索引是聚簇索引,二级索引是非聚簇索引。我们自己建的索引基本都是非聚簇索引。
非聚簇索引一定会回表查询吗?
不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。一个索引包含(覆盖)所有需要查询字段的值,被称之为"覆盖索引"。换而言之,如果查找的字段在索引中就能够找到,就不需要在进行回表查询。
举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select score from student where score > 90
的查询时,在索引的叶子节点上,已经包含了score 信息,不会再次进行回表查询。