年前的最后一次面試題總結(jié)[大廠]
開場白
年前最后幾天,準(zhǔn)備了一場面試。是PHP開發(fā)崗位。面試題都還算是蠻基礎(chǔ),也是常被問到的問題。這里總結(jié)出來幾道蠻不錯的問題。其他的問題,我也做了一些整理,有興趣的可以看一看,都是一些經(jīng)典的面試題。
說說php-fpm啟動進(jìn)程相關(guān)方面的設(shè)置?
針對php-fpm進(jìn)程的管理,需要在php-fpm.conf配置文件中進(jìn)行修改。進(jìn)程運(yùn)行的模式就只有動態(tài)(dynamic)和靜態(tài)(static)。
- 首先,我們關(guān)注一個前提設(shè)置:pm = static/dynamic,標(biāo)識fpm子進(jìn)程的產(chǎn)生模式。
- static(靜態(tài)) :表示在fpm運(yùn)行時直接fork出pm.max_chindren個worker進(jìn)程。
- dynamic(動態(tài)):表示運(yùn)行時fork出start_servers個進(jìn)程,隨著負(fù)載的情況,動態(tài)的調(diào)整,最多不超過max_children個進(jìn)程。
?一般推薦用static,優(yōu)點(diǎn)是不用動態(tài)的判斷負(fù)載情況,提升性能,缺點(diǎn)是多占用些系統(tǒng)內(nèi)存資源。
?
max_children
- 這個值原則上是越大越好,php-cgi的進(jìn)程多了就會處理的很快,排隊(duì)的請求就會很少。
- 設(shè)置”max_children”也需要根據(jù)服務(wù)器的性能進(jìn)行設(shè)定。
- 一般來說一臺服務(wù)器正常情況下每一個php-cgi所耗費(fèi)的內(nèi)存在20M左右。
- 假設(shè)“max_children”設(shè)置成100個,20M*100=2000M。
- 也就是說在峰值的時候所有PHP-CGI所耗內(nèi)存在2000M以內(nèi)。
- 假設(shè)“max_children”設(shè)置的較小,比如5-10個,那么php-cgi就會“很累”,處理速度也很慢,等待的時間也較長。
- 如果長時間沒有得到處理的請求就會出現(xiàn)504 Gateway Time-out這個錯誤,而正在處理的很累的那幾個php-cgi如果遇到了問題就會出現(xiàn)502 Bad gateway這個錯誤。
start_servers
- pm.start_servers的默認(rèn)值為2。并且php-fpm中給的計算方式也為:
?{(cpu空閑時等待連接的php的最小子進(jìn)程數(shù)) + (cpu空閑時等待連接的php的最大子進(jìn)程數(shù) - cpu空閑時等待連接的php的最小子進(jìn)程數(shù))/ 2};用配置表示就是:min_spare_servers + (max_spare_servers - min_spare_servers) / 2;一般而言,設(shè)置成10-20之間的數(shù)據(jù)足夠滿足需求了。
?
說說PHP的生命周期是怎么樣的?以及每個階段分別都做了什么操作?
php的運(yùn)行模式有兩種:web模式和cli模式。無論是哪種公眾模式,php的工作原理都是一樣的,都是作為一種SAPI運(yùn)行。首先,認(rèn)識下SAPI,它是什么。Sapi全稱是Server Application Programming Interface,也就是服務(wù)端應(yīng)用編程接口,Sapi通過一系列鉤子函數(shù),使得PHP可以和外圍交互數(shù)據(jù),這是PHP非常優(yōu)雅和成功的一個設(shè)計,通過sapi成功的將PHP本身和上層應(yīng)用解耦隔離,PHP可以不再考慮如何針對不同應(yīng)用進(jìn)行兼容,而應(yīng)用本身也可以針對自己的特點(diǎn)實(shí)現(xiàn)不同的處理方式。SAPI運(yùn)行PHP都經(jīng)過下面幾個階段: 1、模塊初始化階段(module init) 這個階段主要進(jìn)行php框架、zend引擎的初始化操作。這個階段一般是在SAPI啟動時執(zhí)行一次,對于FPM而言,就是在fpm的master進(jìn)行啟動時執(zhí)行的。php加載每個擴(kuò)展的代碼并調(diào)用其模塊初始化例程(MINIT),進(jìn)行一些模塊所需變量的申請,內(nèi)存分配等。
2、請求初始化階段(request init) 當(dāng)一個頁面請求發(fā)生時,在請求處理前都會經(jīng)歷的一個階段。對于fpm而言,是在worker進(jìn)程accept一個請求并讀取、解析完請求數(shù)據(jù)后的一個階段。在這個階段內(nèi),SAPI層將控制權(quán)交給PHP層,PHP初始化本次請求執(zhí)行腳本所需的環(huán)境變量。比如接收客戶端發(fā)送的post請求數(shù)據(jù)信息、http請求報文信息等。
3、php腳本執(zhí)行階段 php代碼解析執(zhí)行的過程。Zend引擎接管控制權(quán),將php腳本代碼編譯成opcodes并順次執(zhí)行。這也我們的代碼真正執(zhí)行的階段。
4、請求結(jié)束階段(request shutdown) 請求處理完后就進(jìn)入了結(jié)束階段,PHP就會啟動清理程序。這個階段,將flush輸出內(nèi)容、發(fā)送http響應(yīng)內(nèi)容等,然后它會按順序調(diào)用各個模塊的RSHUTDOWN方法。RSHUTDOWN用以清除程序運(yùn)行時產(chǎn)生的符號表,也就是對每個變量調(diào)用unset函數(shù)。比如清除請求初始化階段獲取到的post請求參數(shù)、一些代碼變量等。
5、模塊關(guān)閉階段(module shutdown) 該階段在SAPI關(guān)閉時執(zhí)行,與模塊初始化階段對應(yīng),這個階段主要是進(jìn)行資源的清理、php各模塊的關(guān)閉操作,同時,將回調(diào)各擴(kuò)展的module shutdown鉤子函數(shù)。這是發(fā)生在所有請求都已經(jīng)結(jié)束之后,例如關(guān)閉fpm的操作。(這個是對于CGI和CLI等SAPI,沒有“下一個請求”,所以SAPI立刻開始關(guān)閉。)
說說fastcgi與cgi之間的區(qū)別是什么?
定義
CGI:通用網(wǎng)關(guān)接口協(xié)議(CGI)是一種對接應(yīng)用程序和網(wǎng)絡(luò)服務(wù)器的接口協(xié)議。CGI使外部程序與Web服務(wù)器之間交互成為可能。CGI程序運(yùn)行在獨(dú)立的進(jìn)程中,并對每個Web請求創(chuàng)建一個進(jìn)程,這種方法非常容易實(shí)現(xiàn),但效率較差,難以擴(kuò)展。CGI程序運(yùn)行在獨(dú)立的進(jìn)程中,并對每個Web請求創(chuàng)建一個進(jìn)程,在結(jié)束時銷毀。這種“每個請求一個新進(jìn)程”的模型使得CGI程序非常容易實(shí)現(xiàn),但效率較差,難以擴(kuò)展。在高負(fù)載情況下,進(jìn)程創(chuàng)建和銷毀進(jìn)程的開銷變得很大。此外,由于地址空間無法共享,CGI進(jìn)程模型限制了資源重用方法,如重用數(shù)據(jù)庫連接、內(nèi)存緩存等。
FastCGI:快速通用網(wǎng)關(guān)接口(Fast Common Gateway Interface/FastCGI)是一種讓交互程序與Web服務(wù)器通信的協(xié)議。FastCGI是早期通用網(wǎng)關(guān)接口(CGI)的增強(qiáng)版本。FastCGI致力于減少網(wǎng)頁服務(wù)器與CGI程序之間交互的開銷,從而使服務(wù)器可以同時處理更多的網(wǎng)頁請求。
區(qū)別
CGI每一次請求都會創(chuàng)建一個進(jìn)程,在請求結(jié)束之后進(jìn)程會銷毀。每一個請求,都重復(fù)執(zhí)行這樣的邏輯。FastCGI與最大的區(qū)別在于,使用持續(xù)的進(jìn)程來處理一連串的請求,不會在請求結(jié)束之后關(guān)閉進(jìn)程,而是下一個請求來了之后繼續(xù)使用。這些進(jìn)程由FastCGI服務(wù)器管理,而不是web服務(wù)器。FastCGI具體的進(jìn)程數(shù)量可以通過php-fpm.conf中的pm配置項(xiàng)進(jìn)行操作。
當(dāng)進(jìn)來一個請求時,web服務(wù)器把環(huán)境變量和這個頁面請求通過一個socket比如FastCGI進(jìn)程與web服務(wù)器(都位于本地)或者一個TCP 請求(FastCGI進(jìn)程在遠(yuǎn)端的server farm)傳遞給FastCGI進(jìn)程。服務(wù)傳入請求時,網(wǎng)絡(luò)服務(wù)器通過Unix域套接字、命名管道或TCP連接向FastCGI進(jìn)程發(fā)送環(huán)境變量信息和頁面請求。響應(yīng)通過相同的連接從進(jìn)程返回到網(wǎng)絡(luò)服務(wù)器,然后網(wǎng)絡(luò)服務(wù)器將該響應(yīng)傳遞給最終用戶。連接可能在響應(yīng)結(jié)束時關(guān)閉,但是web服務(wù)器和FastCGI服務(wù)進(jìn)程都將持續(xù),不會被銷毀。
每個單獨(dú)的FastCGI進(jìn)程在其生命周期內(nèi)可以處理許多請求,從而避免了每個請求進(jìn)程創(chuàng)建和終止的開銷。并發(fā)處理多個請求可以通過幾種方式來完成:通過內(nèi)部多路復(fù)用使用一個連接(即一個連接上的多個請求);通過使用多個連接;或者通過這些方法的混合??梢耘渲枚鄠€FastCGI服務(wù)器,提高穩(wěn)定性和可擴(kuò)展性。
能簡單的描述一下Nginx與PHP通信的基本流程嗎?
WebServer在啟動時,載入FastCGI管理器。
FastCGI會完成初始化,啟動多個CGI解釋器。并等待客戶端的連接 當(dāng)客戶端連接的時候,F(xiàn)astCGI管理器會連接其中的一個CGI解釋器。webserver 將標(biāo)準(zhǔn)輸入和cgi配置發(fā)送給cgi。
FastCGI將標(biāo)準(zhǔn)輸出和錯誤輸出返回給WebServer。當(dāng)FastCGI關(guān)閉時,意味著本次請求完成。然后FastCGI等待著FastCGI管理器給他提供的下一次請求。
PHP請求過來的時候,NGINX會將請求發(fā)送給FastCGI的Master,發(fā)送給Worker。將編譯后的結(jié)果發(fā)送個Nginx,然后返回給客戶端。
能說一下PHP的垃圾回收機(jī)制是如何實(shí)現(xiàn)的嗎?
在創(chuàng)建一個PHP變量時,會將這個變量存在zavl變量容器中。這個容器存儲的是這個變量的類型和值,初次之外還會存儲is_ref和refcount兩個額外的字段。
refcount表示指向變量的元素個數(shù),is_ref表示變量是否有別名(是否被引用)。
如果refcount為0時,就回收該變量容器。如果一個zval的refcount減1之后大于0,它就會進(jìn)入垃圾緩沖區(qū)。
當(dāng)緩沖區(qū)達(dá)到最大值后,回收算法會循環(huán)遍歷zval,判斷其是否為垃圾,并進(jìn)行釋放處理。
當(dāng)前請求結(jié)束之后,PHP執(zhí)行腳本結(jié)束,也會清楚所有的變量信息。
官網(wǎng)文檔:
引用計數(shù)基本知識
每個php變量存在一個叫"zval"的變量容器中。一個zval變量容器,除了包含變量的類型和值,還包括兩個字節(jié)的額外信息。第一個是"is_ref",是個bool值,用來標(biāo)識這個變量是否是屬于引用集合(reference set)。通過這個字節(jié),php引擎才能把普通變量和引用變量區(qū)分開來,由于php允許用戶通過使用&來使用自定義引用,zval變量容器中還有一個內(nèi)部引用計數(shù)機(jī)制,來優(yōu)化內(nèi)存使用。第二個額外字節(jié)是"refcount",用以表示指向這個zval變量容器的變量(也稱符號即symbol)個數(shù)。所有的符號存在一個符號表中,其中每個符號都有作用域(scope),那些主腳本(比如:通過瀏覽器請求的的腳本)和每個函數(shù)或者方法也都有作用域。
引用計數(shù)基本知識
每個php變量存在一個叫"zval"的變量容器中。一個zval變量容器,除了包含變量的類型和值,還包括兩個字節(jié)的額外信息。第一個是"is_ref",是個bool值,用來標(biāo)識這個變量是否是屬于引用集合(reference set)。通過這個字節(jié),php引擎才能把普通變量和引用變量區(qū)分開來,由于php允許用戶通過使用&來使用自定義引用,zval變量容器中還有一個內(nèi)部引用計數(shù)機(jī)制,來優(yōu)化內(nèi)存使用。第二個額外字節(jié)是"refcount",用以表示指向這個zval變量容器的變量(也稱符號即symbol)個數(shù)。所有的符號存在一個符號表中,其中每個符號都有作用域(scope),那些主腳本(比如:通過瀏覽器請求的的腳本)和每個函數(shù)或者方法也都有作用域。
說說php的同步模式與swoole的攜程之間的區(qū)別?
- 首先,Swoole 只能運(yùn)行在命令行(Cli)模式下,所以我們開發(fā)調(diào)試都是使用命令行,而不是 php-fpm/apache 等。在 Swoole 中,我們可以使用
\Swoole\Coroutine::create()創(chuàng)建協(xié)程,或者你也可以使用簡寫go()。 - 我們一直在說 Swoole 協(xié)程適合用于 I/O 密集場景,在同樣的硬件配置環(huán)境下,它會比傳統(tǒng)的同步模式承載更多的訪問量。我們熟悉的文件讀寫、網(wǎng)絡(luò)通訊請求(MySQL、Redis、Http等)都是屬于 I/O 密集型場景。假設(shè)一次 SQL 查詢?yōu)?100ms,在傳統(tǒng)同步模式下,當(dāng)前進(jìn)程在這 100ms 的時間里,是不能做其它操作的。如果要執(zhí)行十次這個 SQL,可能需要耗費(fèi) 1s 以上。而如果用協(xié)程,雖然不同協(xié)程之間也是按順序執(zhí)行,但是在前一個等待 100ms 期間,底層會調(diào)度 CPU,去執(zhí)行其它協(xié)程的操作。也就是說,可能第一個查詢還沒返回結(jié)果,其它幾個查詢就已經(jīng)發(fā)送給了 MySQL 并正在執(zhí)行中了。如果開啟十個協(xié)程,分別執(zhí)行這個 SQL,可能只需要耗費(fèi) 100+ms 即可完成。
php-fpm與swoole之間有什么區(qū)別?
php-fpm與swoole介紹:
- 早期版本的 PHP 并沒有內(nèi)置的 WEB 服務(wù)器,而是提供了 SAPI(Server API)給第三方做對接?,F(xiàn)在非常流行的 php-fpm 就是通過 FastCGI 協(xié)議來處理 PHP 與第三方 WEB 服務(wù)器之間的通信。
- Swoole 采用的也是 Master/Worker 模式,不同的是 Master 進(jìn)程有多個 Reactor 線程,Master 只是一個事件發(fā)生器,負(fù)責(zé)監(jiān)聽 Socket 句柄的事件變化。Worker 以多進(jìn)程的方式運(yùn)行,接收來自 Reactor 線程的請求,并執(zhí)行回調(diào)函數(shù)(PHP 編寫的)。啟動 Master 進(jìn)程的流程大致是:初始化模塊。初始化請求。因?yàn)?swoole 需要通過 cli 的方式運(yùn)行,所以初始化請求時,不會初始化 PHP 的全局變量,如 _POST, $_GET 等。執(zhí)行 PHP 腳本。包括詞法、語法分析,變量、函數(shù)、類的初始化等,Master 進(jìn)入監(jiān)聽狀態(tài),并不會結(jié)束進(jìn)程。
Swoole 加速的原理
由 Reactor(epoll 的 IO 復(fù)用方式)負(fù)責(zé)監(jiān)聽 Socket 句柄的事件變化,解決高并發(fā)問題。通過內(nèi)存常駐的方式節(jié)省 PHP 代碼初始化的時間,在使用笨重的框架時,用 swoole 加速效果是非常明顯的。
php-fpm與swoole區(qū)別
- PHP-FPM是Master 主進(jìn)程 / Worker 多進(jìn)程模式。
- 啟動 Master,通過 FastCGI 協(xié)議監(jiān)聽來自 Nginx 傳輸?shù)恼埱蟆?/li>
- 每個 Worker 進(jìn)程只對應(yīng)一個連接,用于執(zhí)行完整的 PHP 代碼。
- PHP 代碼執(zhí)行完畢,占用的內(nèi)存會全部銷毀,下一次請求需要重新再進(jìn)行初始化等各種繁瑣的操作。
- 比較適用于HTTP Server。
- Swoole是Master 主進(jìn)程(由多個 Reactor 線程組成)/ Worker 多進(jìn)程(或多線程)模式。
- 啟動 Master,初始化 PHP 模塊,由 Reactor 監(jiān)聽 Socket 句柄的事件變化。
- Reactor 主線程負(fù)責(zé)子多線程的均衡問題,Manager 進(jìn)程管理 Worker 多進(jìn)程,包括 TaskWorker 的進(jìn)程。
- 每個 Worker 接受來自 Reactor 的請求,只需要執(zhí)行回調(diào)函數(shù)部分的 PHP 代碼。
- 只在 Master 啟動時執(zhí)行一遍 PHP 初始化代碼,Master 進(jìn)入監(jiān)聽狀態(tài),并不會結(jié)束進(jìn)程。
- 不僅可以用于 HTTP Server,還可以建立 TCP 連接、WebSocket 連接。
索引有哪些優(yōu)缺點(diǎn)?
索引的優(yōu)點(diǎn)
- 可以大大加快數(shù)據(jù)的檢索速度,這也是創(chuàng)建索引的最主要的原因。
- 通過使用索引,可以在查詢的過程中,使用優(yōu)化隱藏器,提高系統(tǒng)的性能。
索引的缺點(diǎn)
- 時間方面:創(chuàng)建索引和維護(hù)索引要耗費(fèi)時間,具體地,當(dāng)對表中的數(shù)據(jù)進(jìn)行增加、刪除和修改的時候,索引也要動態(tài)的維護(hù),會降低增/改/刪的執(zhí)行效率;
- 空間方面:索引需要占物理空間。
MySQL有哪幾種索引類型?
- 從數(shù)據(jù)結(jié)構(gòu)上來劃分:BTree索引(B-Tree或B+Tree索引)、Hash索引、full-index(全文索引)、R-Tree索引(空間索引)。這里所描述的是索引存儲時保存的形式。
- 從應(yīng)用層次來分:普通索引,唯一索引,復(fù)合索引。普通索引:即一個索引只包含單個列,一個表可以有多個單列索引。唯一索引:索引列的值必須唯一,但允許有空值。復(fù)合索引:多列值組成一個索引,專門用于組合搜索,其效率大于索引合并。聚簇索引(聚集索引):并不是一種單獨(dú)的索引類型,而是一種數(shù)據(jù)存儲方式。具體細(xì)節(jié)取決于不同的實(shí)現(xiàn),InnoDB的聚簇索引其實(shí)就是在同一個結(jié)構(gòu)中保存了B-Tree索引(技術(shù)上來說是B+Tree)和數(shù)據(jù)行。非聚簇索引:不是聚簇索引,就是非聚簇索引。
- 根據(jù)中數(shù)據(jù)的物理順序與鍵值的邏輯(索引)順序關(guān)系:聚集索引,非聚集索引。
講一講聚簇索引與非聚簇索引?
在 InnoDB 里,索引B+ Tree的葉子節(jié)點(diǎn)存儲了整行數(shù)據(jù)的是主鍵索引,也被稱之為聚簇索引,即將數(shù)據(jù)存儲與索引放到了一塊,找到索引也就找到了數(shù)據(jù)。而索引B+ Tree的葉子節(jié)點(diǎn)存儲了主鍵的值的是非主鍵索引,也被稱之為非聚簇索引、二級索引。聚簇索引與非聚簇索引的區(qū)別:
- 非聚集索引與聚集索引的區(qū)別在于非聚集索引的葉子節(jié)點(diǎn)不存儲表中的數(shù)據(jù),而是存儲該列對應(yīng)的主鍵(行號)
- 對于InnoDB來說,想要查找數(shù)據(jù)我們還需要根據(jù)主鍵再去聚集索引中進(jìn)行查找,這個再根據(jù)聚集索引查找數(shù)據(jù)的過程,我們稱為回表。第一次索引一般是順序IO,回表的操作屬于隨機(jī)IO。需要回表的次數(shù)越多,即隨機(jī)IO次數(shù)越多,我們就越傾向于使用全表掃描 。
- 通常情況下, 主鍵索引(聚簇索引)查詢只會查一次,而非主鍵索引(非聚簇索引)需要回表查詢多次。當(dāng)然,如果是覆蓋索引的話,查一次即可
- 注意:MyISAM無論主鍵索引還是二級索引都是非聚簇索引,而InnoDB的主鍵索引是聚簇索引,二級索引是非聚簇索引。我們自己建的索引基本都是非聚簇索引。
非聚簇索引一定會回表查詢嗎?
不一定,這涉及到查詢語句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再進(jìn)行回表查詢。一個索引包含(覆蓋)所有需要查詢字段的值,被稱之為"覆蓋索引"。換而言之,如果查找的字段在索引中就能夠找到,就不需要在進(jìn)行回表查詢。
舉個簡單的例子,假設(shè)我們在員工表的年齡上建立了索引,那么當(dāng)進(jìn)行select score from student where score > 90的查詢時,在索引的葉子節(jié)點(diǎn)上,已經(jīng)包含了score 信息,不會再次進(jìn)行回表查詢。
一線互聯(lián)大廠,常見面試真題總結(jié)。點(diǎn)擊查看。
