常常有社區的同窗問: 「個人PHP程序有沒有阻塞,個人PHP程序有沒有開啓協程(對本身寫好的代碼表示不自信),個人PHP程序有沒有問題」.而後貼出了本身的程序,而後進入了愉快的灌水環節,隨着時間的流逝,咱們並無造成系統的解決方法.求人不如求己,咱們本身來解決這些問題.php
最近接觸到一個牛X的名詞-方法論.
引用了百度百科一段名詞解釋mysql
方法論,就是關於人們認識世界、改造世界的方法的理論。
它是人們用什麼樣的方式、方法來觀察事物和處理問題。歸納地說,世界觀主要解決世界「是什麼」的問題,方法論主要解決「怎麼辦」的問題。
方法論是一種以解決問題爲目標的理論體系或系統,一般涉及對問題階段、任務、工具、方法技巧的論述。方法論會對一系列具體的方法進行分析研究、系統總結並最終提出較爲通常性的原則。
方法論也是一個哲學概念。人們關於「世界是什麼、怎麼樣」的根本觀點是世界觀。用這種觀點做指導去認識世界和改造世界,就成了方法論。 方法論是廣泛適用於各門具體社會科學並起指導做用的範疇、原則、理論、方法和手段的總和。歷史惟物主義的著做中常常提到方法論這個概念。程序員
咱們PHP程序員是否是也能夠有一個方法,解決一些很容易碰到的基礎問題.借這個平臺拋磚引玉,但願你們多多指教.
仍是引用一個牛X的命題,我是誰,從哪來,到哪去?咱們也來捫心三連問redis
這是一個幾千年來無數人探索的問題.sql
PHP是一個解釋型的語言,咱們首先要上來看看本身的執行的PHP在哪裏,是什麼,以避免在陌生的機器上一頓操做猛如虎才發現沒對上號,好比筆者的機器上裝了好多個版本的PHP.不要笑話這個問題,真的有很多人工做幾年忽略了這個問題.直到他某一天在這個上面浪費了兩小時….(逃swoole
1 $ which php 2 /home/shiguangqi/bin/php 3 $ ll /home/shiguangqi/bin/php 4 lrwxrwxrwx 1 shiguangqi shiguangqi 30 10月 25 12:09 /home/shiguangqi/bin/php -> /usr/local/php-7.2.14//bin/php*
PHP安裝的版本,加載了什麼配置文件,PHP的編譯參數是什麼,有沒有你關注的擴展被加載,加載的配置是什麼版本等一系列問題.
好比我這裏是把不一樣版本的PHP對應到本身的目錄的一個軟連上,自由切換不一樣版本curl
1 $ php -v 2 PHP 7.2.14 (cli) (built: Jul 4 2019 11:02:01) ( NTS DEBUG ) 3 Copyright (c) 1997-2018 The PHP Group 4 Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
加載什麼配置socket
1 $ php --ini 2 Configuration File (php.ini) Path: /usr/local/php-7.2.14/lib 3 Loaded Configuration File: /usr/local/php-7.2.14/lib/php.ini 4 Scan for additional .ini files in: (none) 5 Additional .ini files parsed: (none)
編譯參數, 我這個版本是debug版本,默認靜態編譯了這些模塊.工具
1 $ php -i | grep configure 2 Configure Command => './configure' '--prefix=/usr/local/php-7.2.14' '--with-config-file-path=/usr/local/php-7.2.14/lib' '--with-mysqli=mysqlnd' '--with-jpeg-dir' '--with-png-dir' '--with-iconv-dir' '--with-freetype-dir' '--with-zlib' '--with-mhash' '--with-libxml-dir' '--with-curl' '--with-mcrypt' '--with-gd' '--with-openssl' '--enable-ftp' '--enable-xml' '--enable-bcmath' '--enable-gd-native-ttf' '--enable-mbregex' '--enable-mbstring' '--enable-pcntl' '--enable-sockets' '--enable-fpm' '--enable-soap' '--enable-zip' '--enable-debug'
查看PHP擴展相關, 其實能夠經過php --help
查看更多的用法.
好比你們經常使用的ui
php --ri swoole
以目標爲導向
這一部分對於咱們來講最熟悉不過,咱們天天干的就是這個事情.
設計服務以前要心中要藍圖,最好把90%的時間用來想,10%的時間用來寫.不只犯錯少,並且質量高.
好比我要查詢redis,而後寫了兩個最簡單的代碼
1 <?php 2 $redis = new Redis; 3 $redis->connect("127.0.0.1", 6379); 4 $var = $redis->get("key"); 5 var_dump($var); 6 <?php 7 Swoole\Runtime::enableCoroutine(); 8 go(function () { 9 $redis = new Redis; 10 $redis->connect("127.0.0.1", 6379); 11 $var = $redis->get("key"); 12 var_dump($var); 13 });
而後咱們愉快的得出了一樣的結果.
以目標爲導向,以結果爲標準.
得出結果很容易,問題是咱們乾的質量怎麼樣呢,上面的例子只是類比,你們不要恥笑這兩個例子太簡單.
strace 將是咱們有幫手
咱們能夠這樣,看到核心業務的系統調用
1 strace -s 1000 php redis.php 2 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 3 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) 4 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 5 connect(3, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) 6 poll([{fd=3, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLOUT}]) 7 getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 8 fcntl(3, F_SETFL, O_RDWR) = 0 9 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 10 setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 11 poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) 12 sendto(3, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, MSG_DONTWAIT, NULL, 0) = 22 13 poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) 14 poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLIN}]) 15 recvfrom(3, "$6\r\nvalue2\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 12 16 write(1, "string(6) \"", 11string(6) ") = 11 17 write(1, "value2", 6value2) = 6
咱們還能夠加上一些參數,獲得更詳細的數據,當前系統調用的消耗的時間和當前時間戳
1 $ strace -ttt -T -s 1000 php redis.php 2 1572266793.054360 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000013> 3 1572266793.054389 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) <0.000007> 4 1572266793.054414 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000008> 5 1572266793.054438 connect(3, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000070> 6 1572266793.054533 poll([{fd=3, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLOUT}]) <0.000008> 7 1572266793.054565 getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000008> 8 1572266793.054594 fcntl(3, F_SETFL, O_RDWR) = 0 <0.000006> 9 1572266793.054619 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000008> 10 1572266793.054644 setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 <0.000007> 11 1572266793.054689 poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) <0.000007> 12 1572266793.054717 sendto(3, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, MSG_DONTWAIT, NULL, 0) = 22 <0.000030> 13 1572266793.054788 poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=3, revents=POLLIN}]) <0.000007> 14 1572266793.054816 recvfrom(3, "$", 1, MSG_PEEK, NULL, NULL) = 1 <0.000009> 15 1572266793.054851 poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLIN}]) <0.000007> 16 1572266793.054878 recvfrom(3, "$6\r\nvalue2\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 12 <0.000009> 17 1572266793.054923 write(1, "string(6) \"", 11string(6) ") = 11 <0.000010> 18 1572266793.054951 write(1, "value2", 6value2) = 6 <0.000009> 19 1572266793.054977 write(1, "\"\n", 2"
咱們就能夠逐行查看程序系統調用的詳情.很容易發現,這個程序是阻塞的,雖然每一個系統調用的時間極短.但換個業務,換個依賴可就不是這樣.但萬變不離其宗.
咱們再來看第二個程序,也就是開啓協程後的系統調用
1 1572266985.463011 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 4 <0.000016> 2 1572266985.463048 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR) <0.000006> 3 1572266985.463070 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000006> 4 1572266985.463092 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000007> 5 1572266985.463127 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000005> 6 1572266985.463167 connect(4, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000076> 7 1572266985.463296 epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLOUT, {u32=4, u64=38654705668}}) = 0 <0.000011> 8 1572266985.463353 epoll_wait(3, [{EPOLLOUT, {u32=4, u64=38654705668}}], 4096, 60000) = 1 <0.000007> 9 1572266985.463383 epoll_ctl(3, EPOLL_CTL_DEL, 4, NULL) = 0 <0.000008> 10 1572266985.463407 getsockopt(4, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000008> 11 1572266985.463445 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000007> 12 1572266985.463467 setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 <0.000007> 13 1572266985.463525 recvfrom(4, 0x7f29bb9b033d, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000008> 14 1572266985.463556 sendto(4, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, 0, NULL, 0) = 22 <0.000033> 15 1572266985.463633 recvfrom(4, 0x7f29bb9b033d, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000006> 16 1572266985.463662 recvfrom(4, "$6\r\nvalue2\r\n", 8192, 0, NULL, NULL) = 12 <0.000007> 17 1572266985.463713 write(1, "string(6) \"", 11string(6) ") = 11 <0.000009> 18 1572266985.463740 write(1, "value2", 6value2) = 6 <0.000008>
能夠發現,經過建立socket,而後connect操做,而後經過epoll_ctl加入監聽,是經過多路複用的方式,這裏是沒有阻塞等待的,咱們能夠經過這種方式明確的確認本身的程序細節.
好比,能夠從第八行看到這裏超時等待的時間是60s
.
而後咱們修改代碼超時時間爲0.5s
1 <?php 2 Swoole\Runtime::enableCoroutine(); 3 go(function () { 4 $redis = new Redis; 5 $redis->connect("127.0.0.1", 6379, 0.5); 6 $var = $redis->get("key"); 7 var_dump($var); 8 });
而後繼續查看
1 1572267648.851924 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 4 <0.000011> 2 1572267648.851952 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR) <0.000004> 3 1572267648.851968 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000004> 4 1572267648.851986 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000005> 5 1572267648.852011 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000006> 6 1572267648.852042 connect(4, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000058> 7 1572267648.852131 epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLOUT, {u32=4, u64=38654705668}}) = 0 <0.000006> 8 1572267648.852164 epoll_wait(3, [{EPOLLOUT, {u32=4, u64=38654705668}}], 4096, 500) = 1 <0.000006> 9 1572267648.852184 epoll_ctl(3, EPOLL_CTL_DEL, 4, NULL) = 0 <0.000005> 10 1572267648.852201 getsockopt(4, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000006> 11 1572267648.852225 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000058> 12 1572267648.852302 setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 <0.000006> 13 1572267648.852350 recvfrom(4, 0x7f71f91b033d, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000006> 14 1572267648.852373 sendto(4, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, 0, NULL, 0) = 22 <0.000029> 15 1572267648.852446 recvfrom(4, "$", 1, MSG_PEEK, NULL, NULL) = 1 <0.000011> 16 1572267648.852486 recvfrom(4, "$6\r\nvalue2\r\n", 8192, 0, NULL, NULL) = 12 <0.000006> 17 1572267648.852523 write(1, "string(6) \"", 11string(6) ") = 11 <0.000008> 18 1572267648.852543 write(1, "value2", 6value2) = 6 <0.000006>
而後第8行能夠發現,超時的時間變爲500ms, 這樣能夠發現一些簡單,可是很是重要的細節.一樣第一個例子的超時時間也能夠閱讀到.
運行中的進程能夠經過 strace -p pid
來查看詳情.
關於strace
更詳細的用法能夠 man strace
來查看.
若是咱們的項目很複雜,怎麼定位本身運行時問題,阻塞等待時間太長,死循環等問題.其實也是利用strace
工具,能夠先結合top
來查看系統的負載,以及進程佔用資源.常見的也就是內存和CPU.vmstat
也是很是好的工具.
固然也有全自動化的工具,好比swoole tracker
深刻PHP內核,能夠全自動檢查阻塞,甚至是內存泄漏.固然也能夠本身寫腳原本實現.
經常使用的工具還有gdb
,下一篇咱們一塊兒來實驗gdb
工具對PHP
程序員的巨大做用.