0%

背景介绍

  • 我的代码是交换机监控系统,大约有27个线程,每个线程都会不断地使用subprocess.Popen去调用snmpwalk命令来收集交换机状态数据。
  • 监控部署在一台树莓派2B上,CPU是4核1GHz。
  • 现在收集数据速度不是很理想,因为交换机数量有700多台,大约6分钟才能刷新一次数据。当我想提高snmpwalk的并发量来提高收集速度的时候,我发现CPU是限制我的监控系统的瓶颈。
  • 系统状态数据如下,我们可以看到,sy一项特别的大。
监控系统状态ussyidloadavgsnmpwalk平均进程数python进程占用CPU
未启动1.20.6981.48不存在不存在
启动后1629493.422650%~70%
  • 今天面试腾讯运维开发的时候,和面试官讨论到这个问题,给了我一些启发,所以面完就马上试验一下我的想法。

分析与排查

  • 我觉得可能的原因有:
    • 线程调度开销
    • Popen开销
    • snmpwalk开销
    • 进程调度开销
  • 下面来逐个排查

线程调度开销

  • 使用如下代码进行测试
import threading
import time
import subprocess

def snmp(a):
    time.sleep(5)
    print(a)

for a in range(1,249):
    threading.Thread(target=snmp,args=(a,), name=a).start()
    print("Thread Start:",a)
  • 测试结果:只有在创建线程的时候,us有明显升高,sy仅升高约2%创建完成后随即下降。
  • 结论:sy升高与线程调度无关。

Popen开销

  • 使用如下代码进行测试
import threading
import time
import subprocess

def snmp(a):
    time.sleep(5)
    subprocess.Popen(["echo",str(a),], bufsize=0, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE)

for a in range(1,101):
    threading.Thread(target=snmp,args=(a,), name=a).start()
    print("Thread Start:",a)
  • 测试结果:到subprocess.Popen执行时,sy瞬间飙高,us约16%,sy约65%。
  • 结论:sy升高与同时间大量使用Popen有关。

snmpwalk开销

  • 通过上面的代码,我们发现应该是Popen的开销,所以snmpwalk就不需要测试了。

进程调度开销

  • 系统的平均负载小于CPU核心数,说明运行队列是有空余的,所以应该不是进程调度开销导致的。(对这方面不是很懂,我猜的,不知道对不对)

解决思路

  • 先google一下Linux系统sys高的原因。 google链接
  • 我找到这个: 高sys的CPU排查
  • 我尝试使用strace来统计系统调用。对于上面的Popen开销测试代码,我先运行,然后在sleep(5)的时候迅速地把python的pid记下,运行strace命令,等python代码运行结束后,就会给出结果。这里的结果如下:
strace: Process 28984 attached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    2.033028       16942       120        51 futex
  0.00    0.000000           0         6           close
  0.00    0.000000           0         1           brk
  0.00    0.000000           0         1           rt_sigaction
  0.00    0.000000           0         1           madvise
------ ----------- ----------- --------- --------- ----------------
100.00    2.033028                   129        51 total
  • 我们可以看到,futex这项占了超多。
  • 先不着急,我们再用同样的方法调试一下我的监控程序。运行了6分钟后终止,得到的结果如下:
strace: Process 29205 attached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.92  374.189481       33959     11019      3144 futex
  0.02    0.082074           9      8745           pread64
  0.02    0.072160           7     11008           fcntl64
  0.02    0.058213           6      9592           fstat64
  0.01    0.030947           4      7494      5498 stat64
  0.00    0.012808         512        25           wait4
  0.00    0.009980         322        31           read
  0.00    0.007934          12       665           lstat64
  0.00    0.005470         260        21           _llseek
  0.00    0.001788          81        22           write
  0.00    0.001008           1      1338       666 open
  0.00    0.000507           9        57           mmap2
  0.00    0.000379           1       665           getcwd
  0.00    0.000300           0       666           getpid
  0.00    0.000282           0       729           close
  0.00    0.000000           0         7         7 ioctl
  0.00    0.000000           0         2           munmap
  0.00    0.000000           0         1         1 sigreturn
  0.00    0.000000           0        56           clone
  0.00    0.000000           0        56           mprotect
  0.00    0.000000           0         1           rt_sigaction
------ ----------- ----------- --------- --------- ----------------
100.00  374.473331                 52200      9316 total
  • 很明显,是futex这项造成的,占了99.92%!
  • 那futex是什么?我们用man futex看一下:
       The  futex()  system call provides a method for waiting until a certain
       condition becomes true.  It is typically used as a  blocking  construct
       in  the  context of shared-memory synchronization.  When using futexes,
       the majority of the synchronization operations are  performed  in  user
       space.   A user-space program employs the futex() system call only when
       it is likely that the program has to block for a longer time until  the
       condition  becomes  true.  Other futex() operations can be used to wake
       any processes or threads waiting for a particular condition.
  • 感觉是锁之类的东西,没啥头绪。
  • 那就换个方向。既然是Popen导致的,那我不用Popen或者少用不就行了。今天面试腾讯运维开发的时候,面试官给了一个建议,把net-snmp的代码改了,让它可以一直运行不退出。我觉得ok,然后我下了net-snmp的源码,打算看看从哪下手,突然看到一个叫python的文件夹……!然后我点进去看了下,觉得不错,然后上网找了找资料,发现已经有人做好一个包了,用pip3 install netsnmp-py3进行安装。项目地址:https://github.com/xstaticxgpx/netsnmp-py3
  • 安装完之后我就把调用snmp的代码修改了,现在一个Popen都没有,直接用函数来调用snmp。
  • 我们来对比一下优化前后的结果。感觉不错,sy有明显的下降,同时扫描速度快了很多。
数据ussyidloadavgpython占用CPU扫描一栋楼所需时间
优化前2040323.5150%~90%约6分5秒
优化后149741.0970%约3分23秒

总结

  • 我们的目的是降低sy的占用,现在目标已经达成了。
  • 解决方法是尽量不调用Popen,在我这里,就是用netsnmp-py3提供的函数来代替Popen执行snmp命令。
  • 后面的目标是,既然CPU使用率这么低,就想办法再加快数据的获取速度。

RPM

查询

  • rpm -q 软件名称 查询已安装的软件的包名
  • rpm -qa 查询所有已安装的RPM包
  • rpm -qf 文件路径 查询文件所在的包名
  • rpm -ql 软件名称 查询软件的RPM包中文件的安装路径(如rpm -ql wireshark| grep bin 查找wireshark安装的可执行文件的路径)
  • rpm -qi 软件名称 查询已安装的软件包的信息
  • rpm -qpi RPM包路径 查询(未安装的)RPM包的信息

安装

  • rpm -i RPM包路径 安装RPM包
  • -i install,安装
  • -v verbose,显示安装过程的详细信息
  • -h hash,用#显示进度条(100%为20个#
  • rpm -ivh RPM包路径 安装RPM包,显示详细信息,显示进度条

升级

  • rpm -U RPM包路径 升级
  • rpm -Uvh RPM包路径 升级,显示详细信息,显示进度条

卸载

  • rpm -e 软件名称 卸载软件

YUM

查询

  • yum list installed 列出安装的软件
  • yum list all 列出所有软件
  • yum list 软件名称 列出软件
  • yum info 软件名称 查询软件信息
  • yum grouplist 列出包组
  • yum groupinfo 包组名称 查询包组信息

安装

  • yum install 软件名称 安装软件
  • yum -y install 软件名称 安装软件,所有选项默认yes
  • yum groupinstall 包组名称 安装包组

升级

  • yum update 软件名称 升级软件
  • yum groupupdate 包组名称 升级包组

卸载

  • yum remove 软件名称 卸载软件
  • yum groupremove 包组名称 卸载包组

最近想尝试一下使用face_recognition进行人脸识别,需要使用pillow包,但是在安装时出现了一大长串错误。百度不到适合我的解决方法,于是上pillow官网看了下 安装指南 ,原因和解决办法记录如下: 我看到这句:

前言

  • 关于分布式FFMPEG转码的信息可以看我之前的博客: https://blog.csdn.net/imdyf/article/details/80621009
  • 代码托管在GitHub上: https://github.com/chn-lee-yumi/distributed_ffmpeg_transcoding_cluster
  • 事实上这个集群可以使用不同架构的CPU,你可以用几个树莓派,加几台x86的电脑,它们可以一起工作,这个我们在文章末尾再讲。
  • 我们先使用树莓派来搭建集群。这里有一个树莓派2B,一个树莓派3B。我的电脑是win10,用于烧录系统和配置树莓派。
  • 集群节点角色分三种:控制(一个)、计算(至少一个)、存储(一个)。每个节点可以部署一个或多个角色。我们的角色分配如下:
节点角色
树莓派3B控制、计算、存储
树莓派2B计算

准备工作

  • 首先需要上raspberrypi.org下载最新的树莓派镜像,我这里用raspbian。然后用win32diskimager烧录到TF卡,接着在boot-分区创建一个名为ssh的空文件。现在就可以插到树莓派上开机了。
  • 树莓派开机后,在路由器页面看到树莓派的IP地址,然后用putty进行ssh连接,用户名pi,密码raspberry。
  • 我的树莓派2B的IP地址是:10.1.1.3;树莓派3B的IP地址是:10.1.1.172。
  • 不管哪个节点,进去后都先进行下面这一步:
  • 换国内软件源。我这里用清华大学的源。根据使用说明 https://mirrors.tuna.tsinghua.edu.cn/help/raspbian/ ,执行sudo nano /etc/apt/sources.list并把文件修改成如下内容:
deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main non-free contrib
deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main non-free contrib
  • Ctrl+O保存,再按Ctrl+X退出。
  • 然后执行sudo apt-get update进行更新。

树莓派3B配置

存储

  • 集群中,共享存储是十分重要的,所以我们先配置存储
sudo apt-get install nfs-kernel-server
sudo chmod 777 /var/lib/nfs/.etab.lock
sudo chmod 777 /var/lib/nfs
sudo mkdir -p /srv/distributed_ffmpeg_transcoding_shared_files
sudo chmod 777 /srv/distributed_ffmpeg_transcoding_shared_files
mkdir /srv/distributed_ffmpeg_transcoding_shared_files/upload
mkdir /srv/distributed_ffmpeg_transcoding_shared_files/tmp
mkdir /srv/distributed_ffmpeg_transcoding_shared_files/download
  • 运行sudo nano /etc/exports,将文件内容修改成如下:
/srv/distributed_ffmpeg_transcoding_shared_files/upload 10.1.1.3(ro,insecure)
/srv/distributed_ffmpeg_transcoding_shared_files/tmp 10.1.1.3(rw,insecure)
  • 其中10.1.1.3是树莓派2B的IP地址(计算节点)。
  • 假如你有三个计算节点,它们的IP是10.1.1.3-10.1.1.5,可以写成如下形式:
/srv/distributed_ffmpeg_transcoding_shared_files/upload 10.1.1.3(ro,insecure) 10.1.1.4(ro,insecure) 10.1.1.5(ro,insecure)
/srv/distributed_ffmpeg_transcoding_shared_files/tmp 10.1.1.3(rw,insecure) 10.1.1.4(rw,insecure) 10.1.1.5(rw,insecure)
  • 如果存储节点也是计算节点,不需要存储节点写上。因为存储节点不需要挂载NFS。
  • 修改完成后,执行下面这条命令使配置生效:
exportfs -arv

计算

  • 计算角色配置十分简单,只要执行下面一条命令
sudo apt-get install ffmpeg

控制

  • 首先我们要设置免密码SSH。先执行ssh-keygen -t rsa生成key,期间会让你输入一些参数,我们采用默认参数,直接回车。
  • 结果如下图: ssh-keygen 示意图
  • 然后执行下面的命令ssh-copy-id -i ~/.ssh/id_rsa.pub 10.1.1.3,将公钥复制到树莓派2B。(10.1.1.3是树莓派2B的IP地址)
  • 期间会问你一些东西,我们先输入yes,回车,然后输入raspberry(输入这个的时候不会有显示),回车。结果如下图: ssh-copy-id 示意图
  • 因为树莓派3B也是计算节点,我们也要复制公钥。使用命令ssh-copy-id -i ~/.ssh/id_rsa.pub 10.1.1.172,其余操作同上。
  • 如果还有别的节点,修改命令的IP地址为目标节点,再执行这样的命令。
  • 这样免密码SSH就设置好了。接下来我们下载控制脚本,执行如下命令:
wget https://raw.githubusercontent.com/chn-lee-yumi/distributed_ffmpeg_transcoding_cluster/master/dffmpeg.sh
chmod +x dffmpeg.sh
  • 然后我们需要修改控制脚本的配置。执行nano dffmpeg.sh,将配置项部分修改成如下内容: 配置修改结果图
  • 主要要修改的部分:storage_node的值IP改成存储节点的IP地址,这里是10.1.1.172。compute_node的值改成计算节点的IP地址,这里是10.1.1.172和10.1.1.3。compute_node_weight的值改成各计算节点的权重(性能越好权重应该设置得越大)。我这里将树莓派3B设成48,树莓派2B设成36。
  • 最后,我们还需要安装一个小软件,执行命令sudo apt-get install bc进行安装。

树莓派2B配置

  • 它只有一个计算角色,要配置的东西很少,我们只要安装ffmpeg和挂载共享存储。执行如下命令:
sudo apt-get install ffmpeg
sudo mkdir -p /srv/distributed_ffmpeg_transcoding_shared_files
sudo chmod 777 /srv/distributed_ffmpeg_transcoding_shared_files
mkdir /srv/distributed_ffmpeg_transcoding_shared_files/upload
mkdir /srv/distributed_ffmpeg_transcoding_shared_files/tmp
sudo mount 10.1.1.172:/srv/distributed_ffmpeg_transcoding_shared_files/upload /srv/distributed_ffmpeg_transcoding_shared_files/upload
sudo mount 10.1.1.172:/srv/distributed_ffmpeg_transcoding_shared_files/tmp /srv/distributed_ffmpeg_transcoding_shared_files/tmp
  • 其中10.1.1.172是存储节点(也就是树莓派3B)的IP地址。

测试

  • 测试在控制端进行。首先先下载个测试视频。如果你没有合适的视频,可以用我在网上找的。执行下面的命令进行下载:
wget https://github.com/chn-lee-yumi/distributed_ffmpeg_transcoding_cluster/blob/master/test_video.mp4?raw=true
  • 下载后的文件名后缀不对,我们改一下:mv test_video.mp4?raw=true test_video.mp4
  • 然后我们就可以开始正式测试分布式FFMpeg转码了。执行下面的命令:
 ./dffmpeg.sh test_video.mp4 -c:v mpeg4 -b:v 1M
  • 其中mpeg4是编码格式,1M是视频码率。我们把test_video.mp4转码成mpeg4格式,视频码率为1Mbps。
  • 执行效果如下图: 执行效果

结束语

  • 在这个过程中发现一个奇怪的bug:ffmpeg还在运行但已经执行了后面的touch语句。会导致最后视频拼接出错。临时解决办法是修改控制脚本配置项的sync_wait_time。该BUG原因不明,但不是因为使用了NFS的问题,因为在存储节点没有使用NFS也会出现此情况。该问题留待解决。
  • 这个集群是可以加入x86的计算机的,只要按照配置步骤,安装ffmpeg,开启免密码ssh,挂载共享存储,在控制脚本加入节点,就可以了。