這幾天群里來(lái)了安正超大神,一下子群情'波'濤洶涌呀,什么?你不知道超神是誰(shuí),這是他的個(gè)人網(wǎng)站可以去瞧一瞧.
.......對(duì)了,差點(diǎn)忘了,群里也來(lái)了一個(gè)'似乎'沒(méi)有存在感的仁兄 - Drupal獵人(QQ號(hào):打上馬賽克),為什么說(shuō)他呢,因?yàn)槁?tīng)說(shuō)他技術(shù)也很厲害,不過(guò)在某些方面比他的技術(shù)還要那啥,你懂得(嗶嗶,學(xué)生卡)。
名言篇
留下 1/3 的時(shí)間學(xué)習(xí),留下 1/3 的時(shí)間思考,剩下 1/3 才是寫(xiě)代碼 --- Little
干正事吧,好好學(xué)習(xí),也可以跟大神一樣溜 --- Jellybool?
人生如此短暫,我花費(fèi)了打炮的時(shí)間來(lái)學(xué)門(mén)技術(shù),我容易嗎我? --- Drupal獵人
Js事件的冒泡用法 --- Little
js推薦這種把事件綁定在body上,用冒泡來(lái)做,不要直接在每個(gè)元素上都綁事件。
寫(xiě)js,也是寫(xiě)php面向?qū)ο箢?lèi)似。前面定義各種類(lèi)、方法、屬性,最后把事件綁定在body上,通過(guò)冒泡找id或者找元素。這樣你哪怕?lián)Q了模板,只要流程不是有大變化,js也不用大改的。
比如:開(kāi)始音樂(lè)和暫停音樂(lè)。只不過(guò)是click觸發(fā)的,如果加一個(gè) ESC按鍵觸發(fā),只需要改個(gè)觸發(fā)事件即可。
很少在 js 里會(huì)注冊(cè) window. 下的全局變量,一切都是私有變量。內(nèi)部的用 _,注冊(cè)到外面,一般只用 APP. 這樣的大寫(xiě)類(lèi)。$ 開(kāi)頭的表示 jquery selector,閉包?return?出去了。
PHP opcache --- Little
群友問(wèn):php 開(kāi)啟了 opcache, 如果在命令行執(zhí)行 opcache_reset(), 會(huì)重置 包括 php-pfm 的緩存么
答:不會(huì),每個(gè)php-cli進(jìn)程都有自己的opcache空間,所有php-fpm進(jìn)程是一個(gè)opcache空間,
生產(chǎn)環(huán)境opcache的timestamp檢查關(guān)掉,發(fā)布代碼的時(shí)候再更新下opcache,可以避免很多stat系統(tǒng)調(diào)用
※ 深入淺出FastCGI/php-fpm --- Little
這一個(gè)話題包括Little講解php-fpm和PHP與Mysql連接的整合,方方覺(jué)得兩個(gè)丟在一起可以更能理解
先看個(gè)圖,標(biāo)準(zhǔn)的PHP單進(jìn)程CLI和CGI生命周期。php進(jìn)程啟動(dòng),需要zend core、加載擴(kuò)展(MINIT)、接收請(qǐng)求(RINIT) 這樣的。一個(gè)進(jìn)程只服務(wù)一次命令行或HTTP請(qǐng)求,就退出。
而FastCGI/php-fpm 就是改造后的多進(jìn)程的 CGI,類(lèi)似于資源池,預(yù)先啟動(dòng) 100個(gè) php-fpm 進(jìn)程,提前MINT,nginx的請(qǐng)求來(lái)了,直接進(jìn)入 RINIT -> RSHUTDOWN 循環(huán)。請(qǐng)求結(jié)束,進(jìn)程不退出,一個(gè)進(jìn)程至少服務(wù)上萬(wàn)次請(qǐng)求才退出。
為什么一定要退出?怕RINIT->RSHUTDOWN循環(huán),有哪個(gè)代碼寫(xiě)的不好,變量一直沒(méi)釋放,內(nèi)存泄露GC又回收不了。php-fpm里的pm.max_requests配置就是設(shè)置RINT循環(huán)多少次,退出進(jìn)程。
再來(lái)看幾個(gè) TSF、swoole、workerman、php-pm,都是 php 啟動(dòng)cli進(jìn)程,用php管理子進(jìn)程,php解析HTTP協(xié)議。
生命周期連?RINIT?->?MINIT?循環(huán)都省了,沒(méi)寫(xiě)在?類(lèi)屬性里的變量,裸寫(xiě)的變量都是?進(jìn)程級(jí)全局變量,比?php-fpm?下的?$_GET、$_POST、$_SERVER、$_SESSION、$_COOKIE?這些全局變量范圍還大,是進(jìn)程級(jí)的。意味著你?寫(xiě)了個(gè)?a.php,里面定義了?$a?=?1;?賦值之后,下次請(qǐng)求過(guò)來(lái),只要正好分配到了這個(gè)進(jìn)程,依然還能取到普通定義的?$a?變量。
這意味著什么?像?Laravel?里的?$app?這些變量,只要寫(xiě)在最外面,因?yàn)闆](méi)有觸發(fā)?RSHUTDOWN,又沒(méi)有主動(dòng)?unset,GC引用計(jì)數(shù)器一直大于?0,變量不會(huì)消失。
那怎么解決每次請(qǐng)求?$_GET?和?$_POST?不一樣的問(wèn)題?這些?swoole、workerman?進(jìn)程管理器自己實(shí)現(xiàn)了小型化的?INIT?->?SHUTDOWN?過(guò)程,維護(hù)一些引用計(jì)數(shù)唄,自己的?a.php?完成后,這種框架幫你?unset($_GET)。
問(wèn)題來(lái)了,穩(wěn)定不穩(wěn)定?swoole、workman框架本身穩(wěn)定,但因?yàn)橥耆淖兞藀hp生命周期,業(yè)務(wù)開(kāi)發(fā)人員不熟悉,一不小心寫(xiě)了?global、static?這樣的變量,全局用了,內(nèi)存越占越大,崩潰。又或者?寫(xiě)了個(gè)?exit,把整個(gè)進(jìn)程?exit?而不是?request?ext?了。
舉個(gè)例子來(lái)理解,用 swoole、php-pm 這種進(jìn)程模式,假設(shè)只啟動(dòng)一個(gè)進(jìn)程,可能會(huì)出現(xiàn)以下情況。
1、寫(xiě)個(gè)頁(yè)面 http://www.xxx.com/a.php,內(nèi)容是 $a = 1,而到一個(gè)完全不相關(guān)的頁(yè)面 http://www.xxx.com/b.php,內(nèi)容就是var_dump($a); 居然能輸出 int(1); 很神奇
2、寫(xiě)個(gè)頁(yè)面 http://www.xxx.com/c.php,里面執(zhí)行完一段邏輯后,exit('error'),本以為頁(yè)面輸出個(gè) error,是正常的拒絕訪問(wèn)。再刷新,502了,什么情況。
那哪種情況比較適合使用swoole
Little答:
特別繁忙的某個(gè)接口、業(yè)務(wù)邏輯簡(jiǎn)單、依賴較少的情況,可以用。有良好的編碼習(xí)慣,不用全局變量,不寫(xiě) exit 只用 return 不會(huì)有問(wèn)題。但是無(wú)法保證用到的哪個(gè) vendor 不出現(xiàn)這些東西,所以不能大規(guī)模使用。
Abraham因?yàn)樯婕拜^多框架,深切表示很多 vendor 里面 的寫(xiě)法真是千奇百怪,有的根本都沒(méi)見(jiàn)過(guò),
比如解析 var_dump 去判斷對(duì)象
當(dāng)然我只是看了這些進(jìn)程早期版本的實(shí)現(xiàn),現(xiàn)在php不是有某種注入技術(shù),可以改寫(xiě)原生函數(shù)么。說(shuō)不定啟動(dòng)swoole的時(shí)候,已經(jīng)用某種方法把 exit 改寫(xiě)了呢。
這里是Little對(duì)于的一些理解,讀者還是需要與現(xiàn)在版本進(jìn)行比較
對(duì)于swoole、workman等框架本身穩(wěn)定,但因?yàn)橥耆淖兞藀hp生命周期是否意味著更適合做一些微框架,寫(xiě)純Restful API?
Little答:
這種完全改寫(xiě)生命周期的事情,就有點(diǎn)像 HHVM 對(duì) PHP做成預(yù)定義變量類(lèi)型的改變,性能提升很大,但是用起來(lái)要謹(jǐn)慎,并不是全兼容的。
Little在這里并沒(méi)有直接回答此問(wèn)題,因?yàn)樵谥熬徒榻B了,Little對(duì)于項(xiàng)目是超越框架的,不局限于框架之類(lèi)的東西.所以方方感覺(jué)也是蠻正常的
websocket最好不要php直接來(lái)做,目前php的websocket包,都是啟動(dòng)一個(gè)獨(dú)立的進(jìn)程監(jiān)聽(tīng)端口解析協(xié)議,跟 swoole、workerman 這些東西同一種性質(zhì),不能確保100%好用及可用。
推薦 借助 openresty(nginx) + lua + redis 來(lái)做。
openresty(nginx) 通過(guò) redis 插件 監(jiān)聽(tīng)websocket,同時(shí)掛起一個(gè)到 redis 的連接,用 redis 的 sub 方法,訂閱某個(gè) key。php 這邊只需要 調(diào)用 Redis 的 pub 方法,寫(xiě)入 redis 的 key 即可。
一個(gè)由TIME_WAIT 引起的話題 --- Little
TIME_WAIT多了
Abraham回答
fpm?每個(gè)進(jìn)程結(jié)束都銷(xiāo)毀連接,無(wú)法利用持久連接,持久連接指的是請(qǐng)求內(nèi)的吧,你的情況可以減小?mysql?的等待超時(shí)時(shí)間,或者直接更改操作系統(tǒng)維護(hù)的?tcp?連接數(shù)
little最后解答
我剛那個(gè) TIME_WAIT 問(wèn)題改成php-fpm 連接 mysql 用 persistent 是可以解決的。剛剛只是改錯(cuò)了文件,沒(méi)想到有個(gè)老項(xiàng)目的代碼還在這臺(tái)機(jī)器上跑。
雖然php并沒(méi)有mysql連接池,但是 PDO 的persistent參數(shù)是基于單個(gè)php-fpm進(jìn)程,仔細(xì)看了下php-fpm的生命周期,我配置了 204800 個(gè)request請(qǐng)求才會(huì)重啟這個(gè)php-fpm進(jìn)程,所以連接復(fù)用的情況還是很大,單機(jī)400個(gè)php-fpm進(jìn)程,單個(gè)進(jìn)程要跑滿20萬(wàn)次請(qǐng)求等重啟,怎么都要一兩天才行。
Abraham的理解
以前一直以為持久連接是基于 fpm 子進(jìn)程中處理 request 的線程。
為什么上面要放Abraham最開(kāi)始的回復(fù)以及后來(lái)的理解,一來(lái)是Abraham對(duì)于很多人而言其實(shí)已經(jīng)技術(shù)很好了,但是仍然對(duì)某些深層的不能正確理解,拿出來(lái)讓其他人看看是否自己也有這種理解失誤;二來(lái)嘛,嘿嘿嘿,拖出去,彈雞雞彈到死
Little
關(guān)掉持久連接,會(huì)在request shutdown 階段close mysql連接。而打開(kāi)持久連接,并不會(huì)每次請(qǐng)求都關(guān)閉,是同一個(gè)php-fpm進(jìn)程中復(fù)用連接,直到這個(gè)php-fpm進(jìn)程結(jié)束了。
然而php-fpm的mysql并沒(méi)有連接池,比如開(kāi)了400個(gè)php-fpm進(jìn)程,那就會(huì)和mysql保持400個(gè)連接,無(wú)論現(xiàn)在訪問(wèn)量高還是低。如果前端機(jī)器太多,mysql服務(wù)器會(huì)保持太多的連接數(shù),mysql服務(wù)器并不能承受太多個(gè)連接,哪怕這個(gè)連接什么事都沒(méi)干,所以并不是一個(gè)很好的解決辦法,只能再加一層mysql?proxy。而java的實(shí)現(xiàn)就有連接池,好很多,果當(dāng)前并發(fā)請(qǐng)求量只有100,那么只會(huì)有100個(gè)java到mysql的連接。這100個(gè)連接在所有java線程中復(fù)用。
Abraham再提問(wèn)
如果能保持?php-fpm?的進(jìn)程數(shù)那么多個(gè)連接。show?status?like?'%connections%',理論上應(yīng)該不變的對(duì)吧?
Little 回復(fù)
1:15 的時(shí)候,我在一臺(tái)機(jī)器上開(kāi)了持久連接。1:40左右的時(shí)候,我多開(kāi)了一臺(tái)php機(jī)器。
所以我的結(jié)論是 php-fpm 到 mysql,應(yīng)當(dāng)使用 persistent。如果機(jī)器數(shù)、php-fpm進(jìn)程數(shù)很多,后端mysql掛不了這么多空閑連接。那在 php-fpm 與 mysql 之間加一層 mysql proxy。這個(gè) mysql proxy 就是相當(dāng)于多進(jìn)程多機(jī)器的連接池。
多機(jī)器多進(jìn)程?php-fpm?(可能有5000個(gè)連接)?->?mysql?proxy?->?(可能就剩20個(gè)并發(fā)執(zhí)行mysql的活躍連接了)?mysql?server。
默認(rèn)就開(kāi)啟持久連接才好,出現(xiàn)問(wèn)題,再看看是不是考慮關(guān)掉。畢竟?php?連mysql,一個(gè)新連接會(huì)多三次?TCP握手過(guò)程,雖然是內(nèi)網(wǎng),速度感覺(jué)不出來(lái)。我這是給一個(gè)老項(xiàng)目做優(yōu)化,讓他暫時(shí)不掛。我別的項(xiàng)目都是直接上持久連接。
開(kāi)啟持久連接, 可能會(huì)導(dǎo)致那些問(wèn)題
Little答
理論上是沒(méi)有什么問(wèn)題,就是看你的mysql服務(wù)器受不受得住,受不住就要加 proxy。
另外,有空閑連接,如果你的網(wǎng)站并發(fā)量比較小,空閑連接時(shí)間長(zhǎng)了,可能會(huì)被mysql踢掉,會(huì)產(chǎn)生一個(gè)?mysql?has?gone?away?這個(gè)錯(cuò)誤,你要程序中處理,我不知道?laravel?有沒(méi)有自動(dòng)處理,理論上這種全棧框架應(yīng)該都會(huì)有個(gè)配置自動(dòng)處理這個(gè)問(wèn)題。
我全文搜了下?gone?away,發(fā)現(xiàn)?laravel?里有處理gone?away,業(yè)務(wù)層就不用關(guān)心了。
Illuminate\Databasetry\Connection? -> run() 和 tryAgainIfCausedByLostConnection()
我可不可以這樣理解,當(dāng)你的項(xiàng)目已經(jīng)需要利用 php-fpm 持久連接提供的性能福利時(shí),你同時(shí)也需要審視你的數(shù)據(jù)訪問(wèn)層架構(gòu),重新設(shè)計(jì)它
Little答
應(yīng)該這么說(shuō),持久連接應(yīng)該是在每一個(gè)環(huán)節(jié)都是盡量使用的。比如用戶瀏覽器到nginx,用HTTP/1.1,開(kāi)keepalive。nginx到php-fpm,upstream里開(kāi)keepalive,fastcgi conn 還要配個(gè)什么。php到mysql,也開(kāi)持久連接。這樣每個(gè)環(huán)節(jié)都省掉多次不必要的TCP三次握手。而MySQL性能問(wèn)題和這個(gè)連接關(guān)系并不是太大,只是mysql服務(wù)器一直以來(lái)都有個(gè)毛病,連接數(shù)超過(guò)一定的數(shù)量就受不了,哪怕這個(gè)連接是空閑的什么事都沒(méi)干,不像nginx隨便掛上十幾萬(wàn)的連接數(shù)都輕輕松松。
整理者方方小結(jié) --- 還是好好學(xué)習(xí)