三連問
經常有社區的同學問: “我的PHP程式有沒有阻塞,我的PHP程式有沒有開啟協程(對自己寫好的代碼表示不自信),我的PHP程式有沒有問題”.然后貼出了自己的程式,然后進入了愉快的灌水環節,隨著時間的流逝,我們并沒有形成系統的解決方法.求人不如求己,我們自己來解決這些問題.
方法論
最近接觸到一個牛X的名詞-方法論.
參考了百度百科一段名詞解釋
方法論,就是關于人們認識世界、改造世界的方法的理論,
它是人們用什么樣的方式、方法來觀察事物和處理問題,概括地說,世界觀主要解決世界“是什么”的問題,方法論主要解決“怎么辦”的問題,
方法論是一種以解決問題為目標的理論體系或系統,通常涉及對問題階段、任務、工具、方法技巧的論述,方法論會對一系列具體的方法進行分析研究、系統總結并最終提出較為一般性的原則,
方法論也是一個哲學概念,人們關于“世界是什么、怎么樣”的根本觀點是世界觀,用這種觀點作指導去認識世界和改造世界,就成了方法論, 方法論是普遍適用于各門具體社會科學并起指導作用的范疇、原則、理論、方法和手段的總和,歷史唯物主義的著作中經常提到方法論這個概念,
我們PHP程式員是不是也可以有一個方法,解決一些很容易碰到的基礎問題.借這個平臺拋磚引玉,希望大家多多指教.
還是參考一個牛X的命題,我是誰,從哪來,到哪去?我們也來捫心三連問
- 我是誰
- 我要干啥
- 干的咋樣
我是誰
這是一個幾千年來無數人探索的問題.
PHP是一個解釋型的語言,我們首先要上來看看自己的執行的PHP在哪里,是什么,以免在陌生的機器上一頓操作猛如虎才發現沒對上號,比如筆者的機器上裝了好多個版本的PHP.不要笑話這個問題,真的有不少人作業幾年忽略了這個問題.直到他某一天在這個上面浪費了兩小時….(逃
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對應到自己的目錄的一個軟連上,自由切換不同版本
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
加載什么配置
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 查看更多的用法.
比如大家常用的
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程式員的巨大作用.
- 很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那里入手去提升,對此我整理了一些資料,包括但不限于:分布式架構、高可擴展、高性能、高并發、服務器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階干貨需要的可以免費分享給大家,需要的加群(點擊→)677079770
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/110770.html
標籤:PHP
