PHP程序員-經常使用工具

三連問

常常有社區的同窗問: 「個人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程序員的巨大做用.

相關文章
相關標籤/搜索