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程序员的巨大做用.