调试和 Debug 工具
之前介绍了 Sentry 帮我们发现应用中的问题,但是这还远远不够,我们还需要掌握如下内容:
- 了解 Linux 服务器的相关情况,如 CPU、内存、负载、网络、硬盘剩余资源等。
- 掌握性能测试工具。
- 掌握分析 Python 程序性能瓶颈的工具。
了解 Linux 服务器运行情况
free
在 Linux 下,使用 free 命令获取当前内存的使用情况:
还有一个常见的参数是-m,表示以 MB 为单位。不过现在硬件越来越便宜,内存都以 GB 论,建议使用-h 让结果可读。上述结果中纵轴主要是 Mem 和 Swap,横轴表示各类型的值,如表 12.3 所示。
表 12.3 横轴值的类型与含义
类型 | 含义 |
total | 内存总量 |
used | 已经使用的内存量 |
free | 还没有使用的内存量 |
shared | 进程共享的内存量 |
buffers/cached | 磁盘缓存的大小 |
纵轴上还有一行“-/+buffers/cache”,其中“-buffers/cache”表示已用内存,它的值是 used-buffers-cached 的结果,也就是 92G-6.1M-73G 约等于 18 GB;“-buffers/cache”表示可用内存,它的值是 free+buffers+cached 的结果,也就是 2.5G+6.1M+73G 约等于 76 GB。
Swap 是交换空间,也就是虚拟内存,当物理内存不够的时候可以牺牲一些性能,不至于内存溢出而让程序停止运行。当内存比较小的时候,交换空间通常是推荐的做法。
如果经常发现可用内存不到全部内存的 30%或者 Swap 的 used 的值不为 0,说明需要考虑增加物理内存了。
uptime
如果主观上感觉系统运行缓慢,首先可以通过 uptime 命令获知当前系统的平均负载:
> uptime 18:51:20 up 251 days, 6:26, 6 users, load average:4.74, 4.30, 4.21
uptime 命令的输出:4.74、4.30、4.21,分别表示最近 1 分钟、最近 5 分钟和最近 15 分钟内进程队列中的平均进程数,进程数越多表示进程需要等待的时间越长,系统越繁忙。如果这三个值都经常超过逻辑 CPU 的个数,就说明 CPU 很繁忙。获得逻辑 CPU 个数的方式如下:
> grep processor/proc/cpuinfo|wc-l 24
可见上面的系统还是运行良好的。但是我们不一定非得等负载达到逻辑 CPU 的个数时再来解决,通常在当系统负荷持续大于 0.7*逻辑 CPU 的个数时,就应该开始研究问题出在哪里,想办法优化或者更新硬件了。
通过 w 和 top 等命令也可以输出 uptime 的内容。
htop
htop 是一款运行于 Linux 系统上的监控与进程管理软件,用于取代 UNIX 下传统的 top。top 只提供最消耗资源的进程列表,htop 提供所有进程的列表,并且使用颜色标识出处理器、Swap 和内存状态。htop 还提供方便光标控制的界面来杀死进程、过滤进程、搜索进程、根据指标排序等功能。
可以通过如下命令安装它:
> sudo apt-get install htop
在 htop 的默认设置中,提供如表 12.4 所示类型的输出。
默认情况下,这些输出是按照进程占用的 CPU 使用率从高到低排序的,也可以通过 MEM%、RES、USER 等指标排序。
表 12.4 Flask-DebugToolbar 内置的面板及功能
输出类型 | 含义 |
PID | 进程 ID |
USER | 运行此进程的用户 |
PRI | 进程的优先级 |
NI | 进程的优先级别值,默认的为 0 |
VIRT | 进程占用的虚拟内存值 |
RES | 进程占用的物理内存值 |
SHR | 进程占用的共享内存值 |
S | 进程的运行状况。R 表示正在运行;S 表示休眠,等待唤醒;Z 表示僵死状态 |
CPU% | 该进程占用的 CPU 使用率 |
MEM% | 该进程占用的物理内存和总内存的百分比 |
TIME+ | 该进程启动后占用的总的 CPU 时间 |
COMMAND | 进程命令名称 |
dstat
dstat 是一个用 Python 语言实现的多功能系统资源统计生成工具,可以取代 vmstat、iostat、netstat 和 ifstat 等工具,它可以动态收集网络、硬盘、CPU 等资源情况。dstat 可以让你实时地看到所有系统资源,并以不同颜色显示出来。我们能通过观察一定时间段内资源的使用情况来分析问题。
我们先安装它:
> sudo apt-get install dstat
dstat 支持的参数非常多,我们分开演示:
- -c 表示显示 CPU 占用信息,usr/sys/idl 分别表示用户进程执行时间/系统进程执行时间/空闲时间所占用的 CPU。如果 usr 长期超过 50,表示程序需要优化;如果 sys 比较高,且 idl 经常小于 50,通常说明 CPU 负荷很重,应该考虑增加物理 CPU 或者更换服务器了。wai 表示在等待 I/O 设备(例如磁盘或者网络)的响应,如果 wai 长期处于一个比较大的值,说明 IO 等待比较严重,需要进一步研究造成等待的原因。
- -d 表示磁盘的读写操作。这一栏显示磁盘的读、写总数。
- -n 表示网络设备发送和接受的数据。这一栏显示网络收、发的数据总数。
- -g 表示分页活动。一个较大的分页表明系统正在使用大量的交换空间,或者说内存非常分散,最好的结果就是都输出 0。
dstat 还支持其他参数:
> dstat-lym ---load-avg------system--------memory-usage----- 1m 5m 15m | int csw | used buff cach free 5.18 4.43 4.31|5168 65k|19.8G 6224k 68.5G 6397M 5.32 4.47 4.33| 63k 107k|19.8G 6224k 68.5G 6392M 5.32 4.47 4.33| 61k 101k|19.8G 6224k 68.5G 6382M 5.32 4.47 4.33| 63k 106k|19.8G 6224k 68.5G 6389M 5.32 4.47 4.33| 62k 105k|19.8G 6224k 68.5G 6386M
其中-l 和 uptime 命令、-m 和 free 命令的输出内容源是一样,只是添加了定期刷新的特性。-y 是系统统计,包含中断(int)和上下文切换(csw),通常值越大就表示有越多的进程造成了拥塞。
事实上,dstat 的强大不只是因为它聚合了多种工具的系统检测结果,还因为它能通过附带插件而实现一些高级用法,如找出占用资源最高的进程和用户:
> dstat --proc-count --top-cpu --top-mem --top-io proc -most-expensive- --most-expensive- ----most-expensive---- tota| cpu process | memory process | i/o process 675|migration/14 4.2|tmux 1169M|redis-serve1090B 0 675|maelstrom-wor4.2|tmux 1169M|redis-serve2658B 0 675|maelstrom-wor4.1|tmux 1169M|redis-serve2660B 0 675|maelstrom-wor4.2|tmux 1169M|redis-serve2650B 0 675|maelstrom-wor4.1|tmux 1169M|redis-serve2659B 0
插件都存放在/usr/share/dstat 目录下,可以参考它们的实现,并扩展更多的工具。
Glances
Glances 是一个基于 psutil 的跨平台的系统监控工具。它使用 Python 实现了一个比 htop 功能更齐全的监控工具。它支持 Docker 容器启动,支持 C/S 模型、还支持 SNMP 协议通信。
我们先安装它:
> pip install glances
如果你安装了 Bottle 这个 Web 框架,还能访问和终端显示几乎一样的 Glances 的 Web 页面,如图 12.9 所示。
图 12.9 Glances 的 Web 页面
Glances 还支持把采集的数据导入到其他服务,如 InfluxDB、StatsD、ElasticSearch、RabbitMQ 等。
iftop
iftop 是类似于 top 命令的实时流量监控工具:
> sudo apt-get install iftop
iftop 展示了不同地址使用流量的情况。
sysdig
sysdig 是新兴的 Linux 服务器监控和故障排除工具。
> sudo apt-get install sysdig -y
在 Docker 环境中不能使用 sysdig。
在开始介绍 sysdig 之前,先了解如下几个命令。
- strace:追踪某个进程产生和接收的系统调用。
- tcpdump:监控原始网络数据包。
- lsof:追踪打开的文件。
这三个命令之前是系统管理员必备的工具集的一部分。而 sysdig 其实相当于 strace+tcpdump+htop+iftop+lsof,它不仅能分析 Linux 系统的现场状态,还能将该状态保存为转储文件以供离线检查。
公众号文章《超级系统工具 Sysdig,比 strace、tcpdump、lsof 加起来还强大》(http://bit.ly/29JJe65 )介绍了包含网络、应用、容器、硬盘 I/O、进程和 CPU 使用率、安全等多个方面的用法,本书就不展开了。另外,官网的备忘单(http://bit.ly/29S1Dsp )一定要读读,里面对比了 sysdig 和其他工具命令的使用方法,熟悉这些方法可满足日常工作需要。
nethogs
nethogs 用来查看进程占用带宽情况:
> sudo apt-get install nethogs
使用起来也很简单:
> sudo nethogs eth0
使用 nethogs 很容易就找到流量占用最大的进程。
iotop
iotop 和 top 命令类似,只不过它是针对硬盘 I/O 进程的:
> sudo apt-get install iotop
当发现系统平均负载比较高、dstat 的 wai 字段值比较大时,可以使用 iotop 找出是哪些进程出了问题。
iptraf
iptraf 是一个网络流量监控工具:
> sudo apt-get install iptraf
iptraf 可以获取不同网卡的网络状况,甚至能查看到不同协议的进出流量数据包数和字节数等细节。
性能测试
一个网站到底能够承受多大的用户访问量,经常是开发者和系统管理员最关心的问题。开发者对应用程序进行优化以及系统管理员对网站架构进行调整以后,需要验证这些改变带来了多大的收益,除了进行灰度发布(A/B test 就是一种灰度发布),常用的办法就是对网站进行服务器压力测试了。
boom
本节将介绍一个用 Python 实现的压测工具 boom(https://github.com/tarekziade/boom ),它是 Apache Bench 的替代品。
> pip install boom
它的使用也很简单:
> boom http://localhost:9000 -c 10 -n 100 Server Software:Werkzeug/0.11.3 Python/2.7.6 Running GET http://127.0.0.1:9000 Host: localhost Running 100 times per 10 workers. [=================================================================>] 100%Done --------Results-------- Successful calls 100 Total time 0.8056 s Average 0.0753 s Fastest 0.0198 s Slowest 0.1006 s Amplitude 0.0807 s RPS 124 BSI Pretty good --------Status codes-------- Code 200 100 times. --------Legend-------- RPS: Request Per Second BSI: Boom Speed Index
-c 表示模拟并发 10 个用户,一共 100 次请求。
tcpcopy
使用 boom 或者其他压测工具时都有一个问题,就是模拟的请求不够真实,因为这种模拟并不能考虑到实际环境中复杂的网络状况,只能使用同一个客户端,请求单一。这个时候可以使用由网易工程师开发的 tcpcopy 导入线上流量进行功能和压力测试,这样做的好处是测试数据接近真实水平,而且实施起来相对简单。tcpcopy 支持 HTTP、Memcached、Redis、MySQL 等协议。
它有如下优点:
- 实时。
- 效果真实。因为 tcpcopy 是一种 TCP 请求复制工具,它复制的是真实的请求。
- 低负载,基本不影响线上应用。
- 操作简单。
- 分布式。
tcpcopy 架构有三个角色。
1.online server:线上服务器。tcpcopy 运行在这上面,捕捉和复制线上请求给 target server。
2.target server:路由服务器。处理复制过来的请求,然后将响应信息返回给 assistant server。
3.assistant server:辅助服务器。intercept 运行在这上面,主要负责一些辅助的工作,如将回应包信息传回给 tcpcopy。
我们实验的架构如下:
角色 | IP |
online server | 192.168.0.174 |
target server | 192.168.0.132 |
assistant server | 192.168.0.130 |
首先在 target server 上设置默认路由:
> sudo route add -host 192.168.0.241 gw 192.168.0.130
192.168.0.241 是我们最后伪装的源地址。tcpcopy 既支持 IP,也可以使用地址段。
现在确保 target server 上跑着应用:
> python -m SimpleHTTPServer 9000
在 assistant server 上安装 intercept:
> sudo apt-get install libpcap-dev-y > wget https://github.com/session-replay-tools/intercept/archive/1.0.0.tar.gz > tar zxf 1.0.0.tar.gz > cd intercept-1.0.0 > ./configure && sudo make install
启动 intercept:
> sudo /usr/local/intercept/sbin/intercept -i eth1 -F 'tcp and src port 9000'
最后在 online server 上安装 tcpcopy:
> sudo apt-get install libpcap-dev -y > wget https://github.com/session-replay-tools/tcpcopy/archive/1.0.0.tar.gz > tar zxf 1.0.0.tar.gz > cd tcpcopy-1.0.0 > ./configure && sudo make install
为了保持同样的 Python 应用,我们启动 SimpleHTTPServer:
> sudo python-m SimpleHTTPServer 80
启动 tcpcopy:
> sudo/usr/local/tcpcopy/sbin/tcpcopy-l/var/log/tcpcopy.log-x 80-192.168.0.132:9000 -s 192.168.0.130-c 192.168.0.241
上面启动命令表示监控本机 80 端口的流量,把流量转发到 192.168.0.132 的 9000 端口上。现在请求 online server 上 Web 服务:
> http http://192.168.0.174
这时候可以在 target server 上收到了同样的请求了:
192.168.0.241 - - [03/May/2016 08:15:57]"GET/HTTP/1.1"200-
可以看到请求的源地址被改变了。
Python 程序性能分析
cProfile/pstats
profile 和 cProfile 都可以用来收集程序执行的调用链的顺序、每一步所花费的时间、调用的次数、执行的模块名字或者代码文件以及具体的行数等信息。它们的工作原理都一样,唯一的区别是 cProfile 模块是以 C 扩展的方式实现的,一般情况下应该使用 cProfile。
模拟一个性能问题的例子(profile_test.py):
import time def func1(): sum=0 for i in range(100000): sum+=i def func2(): time.sleep(10) func1() func2()
使用如下方式调用 cProfile:
> python -m cProfile -o profile.out chapter12/section3/profile_test.py
上述的命令把收集到的性能分析数据存在 profile.out 中,需要借用可视化的工具查看。我们看一下使用 pstats 的效果:
> python-c"import pstats;p=pstats.Stats('profile.out');p.sort_stats('time'). print_stats()" Tue May 3 16:39:15 2016 profile.out 6 function calls in 10.012 seconds Ordered by:internal time ncalls tottime percall cumtime percall filename:lineno(function) 1 10.003 10.003 10.003 10.003{time.sleep} 1 0.006 0.006 0.008 0.008 chapter12/section3/profile_test.py:5( func1) 1 0.002 0.002 0.002 0.002{range} 1 0.001 0.001 10.012 10.012 chapter12/section3/profile_test.py:2(< module>) 1 0.000 0.000 10.003 10.003 chapter12/section3/profile_test.py:11( func2) 1 0.000 0.000 0.000 0.000{method 'disable' of '_lsprof.Profiler' objects}
可以看出测试程序中最耗费时间的是 time.sleep,竟然花了 10.003 s。sort_stats 支持多种排序类型,如 calls、cumulative、line、pcalls。
之前介绍的 Flask-DebugToolbar 和 ProfilerMiddleware 显示的性能分析的内容都是基于 cPro-file 和 pstats 的。
豆瓣工程师看到的豆瓣页面都被添加了额外的内容,比如页面响应完成后,黑导航上面会显示本次请求的响应时间,如图 12.10 所示。
图 12.10 豆瓣工程师看到的豆瓣页面
右侧有一个显示为黄色的,内容为 216 ms 的可点击的链接,如果页面响应时间小于 200 ms,就是绿色的,表示响应正常;超过 400 ms 就是红色,表示太慢了,该修了。点击后就能看到页面 DOM 中插入的类似的性能分析结果。这样,工程师在使用豆瓣过程中就可以关注到问题,并且能方便快速地了解性能瓶颈在哪里。
还可以使用 gprof2dot.py 和 graphviz 图形化显示调用关系。
ipdb
ipdb 是添加了 IPython 支持的交互调试器,它的优点有支持自动补全、语法高亮、更好的接口自省等。我们先安装它:
> pip install ipdb
set_trace() 是最常用的方法,如果在复杂程序中发现了问题,可以在代码中插入 set_trace() 函数,并运行程序。当执行到 set_trace() 函数时,就会暂停程序的执行并直接跳转到调试器中,这时候我们可以检查相关的上下文。当退出调试器时,调试器会自动恢复程序的执行。下面展示一个在 Flask 视图中添加调试器的例子(app_with_ipdb.py):
import ipdb @app.route('/') def index(): number=request.args.get('number') ipdb.set_trace() # set_trace() 要放在有问题的代码之前 result=100/number return 'Result is:{}'.format(result)
现在请求视图就可以在终端看到请求的处理被暂停了:
> python app_with_ipdb.py * Running on http://0.0.0.0:9000/(Press CTRL+C to quit) > /home/ubuntu/web_develop/chapter12/section3/app_with_ipdb.py(12)index() 10 number=request.args.get('number') 11 ipdb.set_trace() ---> 12 result=100/number 13 return 'Result is:{}'.format(result) 14 ipdb> p number # 打印变量 None
其他常用的参数有 q(退出)、args(列出当前行的参数和变量值)、j(跳到对应行执行)、c(继续执行,直到遇到下一个断点)、r(继续执行,直到当前函数返回)、n(继续执行,直到当前函数的下一行或者返回)等。
line_profiler
line_profiler 是一个按行计时和记录执行频率的工具,能非常直观地看到哪一行有性能问题。我们先安装它:
> pip install line_profiler
最常用的是使用 @profile 装饰器,我们看一个例子(line_test.py):
@profile def fib(n): if n in (1, 2): return 1 return fib(n-1)+fib(n-2) print fib(5)
不用关心装饰器 profile 还没有被定义,kernprof 可以在脚本运行的时候注入它:
> kernprof-l-v chapter12/section3/line_test.py 5 Wrote profile results to line_test.py.lprof Timer unit:1e-06 s Total time:2.2e-05 s File:chapter12/section3/line_test.py Function:fib at line 2 Line # Hits Time Per Hit % Time Line Contents ============================================================== 2 @profile 3 def fib(n): 4 9 8 0.9 36.4 if n in (1, 2): 5 5 4 0.8 18.2 return 1 6 4 10 2.5 45.5 return fib(n-1)+fib(n-2)
-l 这个选项是告诉 kernprof 将 @profile 装饰器注入到你的脚本的内建环境里;-v 是告诉 kernprof 在脚本执行完之后立马显示计时信息;如果不指定-o 参数,默认会把分析结果存在 line_test.py.lprof 中。格式兼容 pstats 模块,但是没有按行的效果:
> python-c"import pstats;p=pstats.Stats('line_test.py.prof');p.sort_stats('calls'). print_stats()"
memory_profiler
memory_profiler 是一个监控 Python 代码内存使用量的工具。我们先安装它:
> pip install memory_profiler
强烈推荐安装 psutil 这个库来提高 memory_profiler 的性能。
跟 line_profiler 类似,memory_profiler 需要用 @profile 装饰器来装饰你感兴趣的函数,就像这样(mem_test.py):
@profile def my_func(): a=[1]*(10**6) b=[2]*(2*10**7) del b return a my_func()
用以下的命令来查看 my_func 在运行时耗费的内存:
> python-m memory_profiler chapter12/section3/mem_test.py Filename:chapter12/section3/mem_test.py Line # Mem usage Increment Line Contents ================================================ 2 18.207 MiB 0.000 MiB @profile 3 def my_func(): 4 25.844 MiB 7.637 MiB a=[1]*(10**6) 5 178.434 MiB 152.590 MiB b=[2]*(2*10**7) 6 25.844 MiB -152.590 MiB del b 7 25.844 MiB 0.000 MiB return a
按行显示使用的内存量,能很直观地看到增加/减少的内存量。
Line_profiler 和 memory_profiler 共有的特性是它们在 IPython 里都有快捷方式。你只需要在 IPython 里输入以下内容:
In : %load_ext memory_profiler In : %load_ext line_profiler
就可以使用%lprun 和%mprun 这两个 Magic 函数了。在 IPython 中使用它们时不能出现 @profile 装饰器(因为这种模式下 profile 未定义),需要使用未添加 @profile 装饰器的函数:
In : from chapter12.section3.ipy_test import my_func In : %mprun-f my_func my_func() In : %lprun-f my_func my_func()
性能调优实践
如果你感觉程序很慢,首先应该分析慢的原因,然后再寻求优化的方法。性能调优一般可以从以下几个方面来着手。
1.优化程序算法本身。通常先使用上述分析工具度量 CPU 时间、内存使用、函数调用次数等方面数据,然后可以选择合适的数据结构,优化循环,去掉不必要的抽象,避免不必要的数据拷贝和计算,以空间换时间(以时间换空间),充分利用系统资源(如多线程、多进程、多核并行计算)等。这些方法比较初级,但能消除明显的编程细节引起的瓶颈。
2.优化运行环境和资源。运行环境与资源包括各种软硬件平台,包含操作系统、数据库、CPU、内存、磁盘、网络等。这是最直接、最简单的调优方法,如购买更好的 CPU,使用 SSD 等。这种方法受制于预算和硬件本身纵向扩展的限制。
3.优化系统架构。单台服务器的处理能力总是会达到上限的,常见的思路是把压力分散到多台机器上,从而使每台机器都能获得可接受的延迟或吞吐量。除此之外,应该使用更合适的存储和缓存策略,如在符合条件的场景下使用 NoSQL。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论