1.应用场景

主要用于学习和使用常用工具进行程序剖析,养成技术人的内功心法,从而达到技术的融会贯通。

2.学习/操作

1.文档阅读

PHP程序员内功心法-常用工具

My favorite (dead simple) PHP debugging tool

Swoole4 文档 - 工具使用 -- 推荐好好利用起来

posix是什么都不知道,还好意思说你懂Linux? - 知乎 -- 里面有分析里了程序系统调用的过程,推荐看下

2.整理输出

原文

PHP程序员内功心法-常用工具

石光启 6363次阅读 发布时间:2019-10-28

三连问

经常有社区的同学问: “我的PHP程序有没有阻塞,我的PHP程序有没有开启协程(对自己写好的代码表示不自信),我的PHP程序有没有问题”.然后贴出了自己的程序,然后进入了愉快的灌水环节,随着时间的流逝,我们并没有形成系统的解决方法.求人不如求己,我们自己来解决这些问题.

方法论

最近接触到一个牛X的名词-方法论.
引用了百度百科一段名词解释

方法论,就是关于人们认识世界、改造世界的方法的理论。
它是人们用什么样的方式、方法来观察事物和处理问题。概括地说,世界观主要解决世界“是什么”的问题,方法论主要解决“怎么办”的问题。
方法论是一种以解决问题为目标的理论体系或系统,通常涉及对问题阶段、任务、工具、方法技巧的论述。方法论会对一系列具体的方法进行分析研究、系统总结并最终提出较为一般性的原则。
方法论也是一个哲学概念。人们关于“世界是什么、怎么样”的根本观点是世界观。用这种观点作指导去认识世界和改造世界,就成了方法论。 方法论是普遍适用于各门具体社会科学并起指导作用的范畴、原则、理论、方法和手段的总和。历史唯物主义的著作中经常提到方法论这个概念。

我们PHP程序员是不是也可以有一个方法,解决一些很容易碰到的基础问题.借这个平台抛砖引玉,希望大家多多指教.
还是引用一个牛X的命题,我是谁,从哪来,到哪去?我们也来扪心三连问

  • 我是谁
  • 我要干啥
  • 干的咋样

我是谁

这是一个几千年来无数人探索的问题.

PHP是一个解释型的语言,我们首先要上来看看自己的执行的PHP在哪里,是什么,以免在陌生的机器上一顿操作猛如虎才发现没对上号,比如笔者的机器上装了好多个版本的PHP.不要笑话这个问题,真的有不少人工作几年忽略了这个问题.直到他某一天在这个上面浪费了两小时….(逃

$ which php

/home/shiguangqi/bin/php

$ ll /home/shiguangqi/bin/php

lrwxrwxrwx 1 shiguangqi shiguangqi 30 10月 25 12:09 /home/shiguangqi/bin/php -> /usr/local/php-7.2.14//bin/php*

PHP安装的版本,加载了什么配置文件,PHP的编译参数是什么,有没有你关注的扩展被加载,加载的配置是什么版本等一系列问题.
比如我这里是把不同版本的PHP对应到自己的目录的一个软连上,自由切换不同版本

$ php -v

PHP 7.2.14 (cli) (built: Jul 4 2019 11:02:01) ( NTS DEBUG )

Copyright (c) 1997-2018 The PHP Group

Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

加载什么配置

$ php --ini

Configuration File (php.ini) Path: /usr/local/php-7.2.14/lib

Loaded Configuration File: /usr/local/php-7.2.14/lib/php.ini

Scan for additional .ini files in: (none)

Additional .ini files parsed: (none)

编译参数, 我这个版本是debug版本,默认静态编译了这些模块.

$ php -i | grep configure

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,然后写了两个最简单的代码

<?php

$redis = new Redis;

$redis->connect("127.0.0.1", 6379);

$var = $redis->get("key");

var_dump($var);

<?php

Swoole\Runtime::enableCoroutine();

go(function () {

$redis = new Redis;

$redis->connect("127.0.0.1", 6379);

$var = $redis->get("key");

var_dump($var);

});

然后我们愉快的得出了同样的结果.

干的咋样

以目标为导向,以结果为标准.

得出结果很容易,问题是我们干的质量怎么样呢,上面的例子只是类比,大家不要耻笑这两个例子太简单.
strace 将是我们有帮手「这个工具非常有用」
我们可以这样,看到核心业务的系统调用

strace -s 1000 php redis.php

socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3

fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)

fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0

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)

poll([{fd=3, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLOUT}])

getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0

fcntl(3, F_SETFL, O_RDWR) = 0

setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0

setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0

poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout)

sendto(3, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, MSG_DONTWAIT, NULL, 0) = 22

poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout)

poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLIN}])

recvfrom(3, "$6\r\nvalue2\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 12

write(1, "string(6) \"", 11string(6) ") = 11

write(1, "value2", 6value2) = 6

我们还可以加上一些参数,得到更详细的数据,当前系统调用的消耗的时间和当前时间戳

$ strace -ttt -T -s 1000 php redis.php

1572266793.054360 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000013>

1572266793.054389 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) <0.000007>

1572266793.054414 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000008>

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>

1572266793.054533 poll([{fd=3, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLOUT}]) <0.000008>

1572266793.054565 getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000008>

1572266793.054594 fcntl(3, F_SETFL, O_RDWR) = 0 <0.000006>

1572266793.054619 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000008>

1572266793.054644 setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 <0.000007>

1572266793.054689 poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) <0.000007>

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>

1572266793.054788 poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=3, revents=POLLIN}]) <0.000007>

1572266793.054816 recvfrom(3, "$", 1, MSG_PEEK, NULL, NULL) = 1 <0.000009>

1572266793.054851 poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=3, revents=POLLIN}]) <0.000007>

1572266793.054878 recvfrom(3, "$6\r\nvalue2\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 12 <0.000009>

1572266793.054923 write(1, "string(6) \"", 11string(6) ") = 11 <0.000010>

1572266793.054951 write(1, "value2", 6value2) = 6 <0.000009>

1572266793.054977 write(1, "\"\n", 2"

我们就可以逐行查看程序系统调用的详情.很容易发现,这个程序是阻塞的,虽然每个系统调用的时间极短.但换个业务,换个依赖可就不是这样.但万变不离其宗.

我们再来看第二个程序,也就是开启协程后的系统调用

1572266985.463011 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 4 <0.000016>

1572266985.463048 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR) <0.000006>

1572266985.463070 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000006>

1572266985.463092 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000007>

1572266985.463127 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000005>

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>

1572266985.463296 epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLOUT, {u32=4, u64=38654705668}}) = 0 <0.000011>

1572266985.463353 epoll_wait(3, [{EPOLLOUT, {u32=4, u64=38654705668}}], 4096, 60000) = 1 <0.000007>

1572266985.463383 epoll_ctl(3, EPOLL_CTL_DEL, 4, NULL) = 0 <0.000008>

1572266985.463407 getsockopt(4, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000008>

1572266985.463445 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000007>

1572266985.463467 setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 <0.000007>

1572266985.463525 recvfrom(4, 0x7f29bb9b033d, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000008>

1572266985.463556 sendto(4, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, 0, NULL, 0) = 22 <0.000033>

1572266985.463633 recvfrom(4, 0x7f29bb9b033d, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000006>

1572266985.463662 recvfrom(4, "$6\r\nvalue2\r\n", 8192, 0, NULL, NULL) = 12 <0.000007>

1572266985.463713 write(1, "string(6) \"", 11string(6) ") = 11 <0.000009>

1572266985.463740 write(1, "value2", 6value2) = 6 <0.000008>

可以发现,通过创建socket,然后connect操作,然后通过epoll_ctl加入监听,是通过多路复用的方式,这里是没有阻塞等待的,我们可以通过这种方式明确的确认自己的程序细节.
比如,可以从第八行看到这里超时等待的时间是60s.
然后我们修改代码超时时间为0.5s

<?php

Swoole\Runtime::enableCoroutine();
go(function () {
    $redis = new Redis;
    $redis->connect("127.0.0.1", 6379, 0.5);
    $var = $redis->get("key");
    var_dump($var);
});

然后继续查看

1572267648.851924 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 4 <0.000011>

1572267648.851952 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR) <0.000004>

1572267648.851968 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000004>

1572267648.851986 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000005>

1572267648.852011 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000006>

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>

1572267648.852131 epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLOUT, {u32=4, u64=38654705668}}) = 0 <0.000006>

1572267648.852164 epoll_wait(3, [{EPOLLOUT, {u32=4, u64=38654705668}}], 4096, 500) = 1 <0.000006>

1572267648.852184 epoll_ctl(3, EPOLL_CTL_DEL, 4, NULL) = 0 <0.000005>

1572267648.852201 getsockopt(4, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000006>

1572267648.852225 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000058>

1572267648.852302 setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 <0.000006>

1572267648.852350 recvfrom(4, 0x7f71f91b033d, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000006>

1572267648.852373 sendto(4, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, 0, NULL, 0) = 22 <0.000029>

1572267648.852446 recvfrom(4, "$", 1, MSG_PEEK, NULL, NULL) = 1 <0.000011>

1572267648.852486 recvfrom(4, "$6\r\nvalue2\r\n", 8192, 0, NULL, NULL) = 12 <0.000006>

1572267648.852523 write(1, "string(6) \"", 11string(6) ") = 11 <0.000008>

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

Swoole官方文档上提到学习swoole需要具备的基础知识:

学习 Swoole 需要掌握哪些基础知识

多进程 / 多线程

  • 了解 Linux 操作系统进程和线程的概念
  • 了解 Linux 进程 / 线程切换调度的基本知识
  • 了解进程间通信的基本知识,如管道、UnixSocket、消息队列、共享内存

SOCKET

  • 了解 SOCKET 的基本操作如 accept/connectsend/recvcloselistenbind
  • 了解 SOCKET 的接收缓存区、发送缓存区、阻塞 / 非阻塞、超时等概念

IO 复用

  • 了解 select/poll/epoll
  • 了解基于 select/epoll 实现的事件循环,Reactor 模型
  • 了解可读事件、可写事件

TCP/IP 网络协议

  • 了解 TCP/IP 协议
  • 了解 TCPUDP 传输协议

以及调试工具  -------------- 不单单针对swoole,针对其他编程语言/软件开发是一样的

调试工具

  • 使用 gdb 调试 Linux 程序
  • 使用 strace 跟踪进程的系统调用
  • 使用 tcpdump 跟踪网络通信过程
  • 其他 Linux 系统工具,如 ps、lsof、top、vmstat、netstat、sar、ss 等

个人补充

程序性能分析,可以参考专门的专栏

性能优化高手课

Linux 性能优化实战

网络排查案例课

性能测试实战 30 讲

全链路压测实战 30 讲

高楼的性能工程实战课

性能工程高手课

后续补充

...

3.问题/补充

1. 关于使用PHP实现的服务  --- FYI

Swoole4 文档 -- Swoole的由来

Rango的陈述

...

2011 年底我入职腾讯,负责朋友网的 PHP 平台开发工作。惊奇地发现朋友网的同事不光这样想了,他们直接做到了。朋友网团队已经在生产环境中使用了这套方案。朋友网有三架马车,第一个是 PWS,这是一个纯 PHP 编写的 WebServer,朋友网线上有 600 多台服务器运行在 PWS 上,完全没有使用 Apache、PHP-FPM 之类的程序。第二个是 SAPS,这是使用纯 PHP 开发的一个分布式队列,当时大概由 150 台服务器的集群在跑,很多图片裁剪、头像处理、消息同时、数据同步等逻辑全部使用了 SAPS 做逻辑异步化。第三个是 PSF,这是一个 PHP 实现的 Server 框架,朋友网很多逻辑层的服务器都是基于 PSF 实现的。大概有 300 台左右的集群在运行 PSF 服务器程序。在朋友网的这段时间,我学到了很多 Linux 底层、网络通信的知识,积累了很多大型集群高并发环境的网络通信跟踪、调试经验,为开发 Swoole 打下了一个很好的基础。

个人想法:一直以来,都以为,单单依靠PHP是无法实现高性能的web 服务,需要依靠专门的http server才行,自带的http server只能用于开发/测试当中。

后来才逐渐意识到,自己对PHP这门编程语言其实并没有达到很熟悉。

而且自己越来越坚信,只有一个能将一门编程语言掌握到很熟悉的程度,才能真正掌握其他的编程语言。

底层的东西,都是一样的,都是基于操作系统,网络编程,多进程多线程,数据结构与算法等。

4.参考

参见上面文档列表

后续补充

...

PHP/其他语言 - 程序员内功心法 常用工具 - 学习/实践相关推荐

  1. 四年程序员的常用工具清单

    操作系统:window10. linux:ubuntu/centos window10 dos命令.批处理 linux shell命令 文本: 建议用markdown格式 Typora txt 记事本 ...

  2. 推荐10个实用的程序员开发常用工具

    1. 截图工具(snipaste) 官网地址 snipaste是一款截图+贴图工具,按住F1快捷键就可轻松截图,还可调整窗口大小和移动截图窗口,除此之外,我还经常使用snipaste量尺寸,按c快捷键 ...

  3. .NET 程序员十种常用辅助开发工具

     .NET 程序员十种常用辅助开发工具      一: .NET 程序员十种工具 - Visual Studio Converter(转换器工具) Visual Studio .NET 项目转换器(参 ...

  4. Java 程序员开发常用的工具(全)

    Java 程序员开发常用的工具 作为一名Java程序开发人员,可以的选择集成开发环境IDE(Integrated Development Environment)非常多,得益于Java是一门开源语言. ...

  5. BAT 程序员们常用的开发工具

    阿里篇 一.Java 线上诊断工具 Arthas Arthas 是阿里巴巴 2018 年 9 月开源的一款 Java 线上诊断工具. 使用场景: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 ...

  6. 收藏!!BAT 程序员们常用的开发工具

    阅读本文大概需要 15 分钟. 作者:infoQ来源:https://tinyurl.com/y4wohyqt 工欲善其事必先利其器,一个优秀的程序员除了代码写得好,善于利用各种开发工具同样可以事半功 ...

  7. C程序员最常用哪些工具软件

    C程序员最常用哪些工具软件? 原文地址:http://topic.csdn.net/u/20081211/01/00099275-605f-4a66-a16c-d8fe3eaf7324.html 人常 ...

  8. 【数据结构与算法】程序员们常用的10个关键数据结构,包括它们的原理和C语言实现代码

    [数据结构与算法]程序员们常用的10个关键数据结构,包括它们的原理和C语言实现代码 文章目录 [数据结构与算法]程序员们常用的10个关键数据结构,包括它们的原理和C语言实现代码 1. 数组 (Arra ...

  9. 为什么3年的Java高级程序员薪水仅仅8k-10k,而一个Linux底层C语言程序员两年经验就敢要1...

    为什么80%的码农都做不了架构师?>>>    为什么3年的Java高级程序员薪水仅仅8k-10k,而一个Linux底层C语言程序员两年经验就敢要10k的薪水?   由于目前国内嵌入 ...

最新文章

  1. (十五)java数组
  2. apue.h头文件(UNIX环境高级编程)
  3. 【PAT乙级】1024 科学计数法 (20 分)
  4. 2019年上半年总结__下半年计划
  5. unity调整旋转需要传什么参数?参数在数学上叫做什么?_人脸识别背后,卷积神经网络的数学原理原来是这样的...
  6. pipline中替换tag变量
  7. dotnetcore+vue+elementUI 前后端分离 三(前端篇)
  8. 阿里巴巴云原生混部系统 Koordinator 正式开源
  9. beego 例子_beego框架代码分析
  10. springboot 的 RedisTemplate 的 execute 和 executePipelined 功能的区别redis
  11. 草稿 9206 1128需处理
  12. C++字符读入函数(getchgetchar)
  13. html加拼音注释,满江红岳飞全文带拼音(注释+译文)
  14. 2021中国科学院文献情报中心期刊分区表 计算机(2)
  15. 我手机计算机屏幕是黑色的,教你处理手机或者电脑黑屏的简单方法
  16. vmware 虚拟机安装系统成功,没有虚拟网卡的完美解决方法
  17. 大数据开发常见面试问题总结
  18. 【Java获取国家法定节假日三种工具类其二】
  19. 比 Excel 更强大,Python 的可视化库 Altair 入门
  20. 实战 Java 第8天:开发商品详情查询接口

热门文章

  1. html锚点的使用,html 锚点的使用
  2. 【2021总结】工作时间从865到995,这一年的变化真大
  3. 顶底突破同花顺副图指标 波浪类指标
  4. 正则表达式给内容快速加双引号,单引号
  5. GBase 8c 创建用户示例说明
  6. 调整泊松回归中的过度分散
  7. android 较高版本,页面分割线实现LinearLayoutCompat布局简单事例
  8. python实现半色调技术图像转换
  9. 华为离职副总裁透露年薪千万的工作感悟,值得深思
  10. 修复IE主页被锁定的问题~~~