关于 PHP 程序员解决问题的能力

这个话题老生长谈了,在面试中必然考核的能力中,我个人认为解决问题能力是排第一位的,比学习能力优先级更高。解决问题的能力既能看出程序员的思维能力,应变能力,探索能力等,又可以看出他的经验。如果解决问题能力不佳是无法通过面试的。

这里举个例子,假如我执行了一个PHP的脚本,如php test.php,预期是可以返回一个字符串。但执行后没有任何信息输出,这时候通过什么方法能知道程序错在哪里?这里可以将解决问题能力分为8个等级,越到后面的表示能力越强。

Lv0 查看PHP错误信息

程序没有达到预期效果,证明代码出错了,看PHP的错误信息是第一步。如果直接忽略错误信息,表明这个人不适合担任专业的程序员岗位。有些情况下php.ini配置中关闭了错误显示,需要修改php.ini打开错误信息,或者错误信息被导出到了日志文件,这种情况可以直接tailf php_error.log来看错误信息。

拿到错误信息后直接定位到程序代码问题,或者到Google/百度搜索,即可解决问题。

注:打开错误显示的方法是

php.ini中display_errors / display_startup_errors 设置为On
php.ini中error_reporting 设置为E_ALL
PHP代码中设置error_reporting(E_ALL)

Lv1 存在多个版本的php或php-cli与php-fpm加载不同的配置

存在多个版本的php,懂得通过which php来看是哪个PHP,或者加绝对路径制定php版本。表示此PHPer通过了此层级的50%考验。

另外一个情况就是php-cli与php-fpm得到的执行情况不一样,如在web浏览器中执行是对的,cli下执行是错的。这时候可能是2个环境加载的php.ini不同所致。cli下通过php -i |grep php.ini得到加载了哪个php.ini。而fpm下通过phpinfo()函数可以得到php.ini的绝对路径。

Lv2 var_dump/die打印变量值信息单步调试

这是惯用的程序调试手段,也是最简单粗暴有效的解决问题方法。高级一点的手段是使用PHP的Trace类/日志类,花哨一点的可以借助phpstorm+xdebug在IDE工具里进行Debug。

Trace工具还可以分析脚本的耗时,进行PHP程序的性能优化。

这3个考验全部通过,表明此程序员已经具备了专业PHP程序员应该有的解决问题能力了。PHP程序员只要过了这个等级,就足以应多大部分情况,在中小型网站中毫无压力。

Lv3 使用strace工具跟踪程序执行

strace可以用来查看系统调用的执行,使用strace php test.php,或者strace -p 进程ID。strace就可以帮助你透过现象看本质,掌握程序执行的过程。这个手段是在大型网站,大公司里最常用的。如果没掌握strace,这里只能说抱歉了,我们不接受不会strace的PHPer。

strace其实也是对程序员基础的考验,如果不懂操作操作系统,完全不懂底层,肯定也达不到会用strace的程度。当然strace对于PHP代码里的死循环是解决不了的。比如你发现一个php-fpm进程CPU100%了,strace恐怕是解决不了的。因为strace是看系统调用,一般都是IO类操作,既然是IO密集,那CPU一定不可能是100%。

Lv4 使用tcpdump工具分析网络通信过程

tcpdump可以抓到网卡的数据通信过程,甚至数据内容也可以抓到。使用tcpdump可以看到网络通信过程是什么样的,如何时发起了TCP SYN3次握手,何时发送FIN包,何时发送RST包。这是一个基本功,如果不懂tcpdump,证明不具备网络问题解决能力。

Lv5 统计函数调用的耗时和成功率

使用xhporf/xdebug导出PHP请求的调用过程,然后分析每个函数调用的过程和耗时。能够分析PHP程序的性能瓶颈,找出可以优化的点。

另外一个对于网络服务的调用,如mysql查询,curl,其他API调用等,通过记录起始和结束时microtime,返回的是不是false,可以得到调用是否成功,耗时多少。如果可以汇总数据,整理出调用的成功率,失败率,平均延时,证明此程序员对接口质量敏感,有大型网站项目经验。

Lv6 gdb使用

gdb是C/C++调试程序的利器,需要具备一定C/C++功底的程序员才会能熟练使用gdb。上面说的strace无法跟踪php程序CPU100%,而gdb是可以跟踪的。另外gdb也可以解决php程序core dump的问题。

通过gdb -p 进程ID,再配合php-src的.gdbinit zbacktrace等工具,可以很方便地跟踪PHP程序的执行。像上面的CPU100%往往是PHP程序中发生死循环了,gdb进行多次查看,就大致可以得到死循环的位置。具备gdb解决问题能力的PHP程序员少之又少。如果能使用gdb解决PHP问题,这个PHPer百分之百可以通过面试,并且可以拿到较高的技术评级。

Lv7 查看PHP内核和扩展源码

如果能熟悉PHP内核和扩展的源码,遇到PHP程序中最复杂的内存错误,也可以有解决的能力。这类PHP程序员就是凤毛麟角了。配合gdb工具和对PHP源码的熟悉,可以查看opcode的信息,execute_data的内存,全局变量的状态等。

Mac OS X 10.9 安装 CocoaPods

$ sudo gem install cocoapods

如果很慢,请更换源:
$ gem sources --remove https://rubygems.org/
//等有反应之后再敲入以下命令
$ gem sources -a http://ruby.taobao.org/

为了验证你的Ruby镜像是并且仅是taobao,可以用以下命令查看:
$ gem sources -l

只有在终端中出现下面文字才表明你上面的命令是成功的:
*** CURRENT SOURCES ***
http://ruby.taobao.org/

这时候,你再次在终端中运行:
$ sudo gem install cocoapods

Xcode 6.0.1 安装 iOS 7 模拟器

下载地址:
http://devimages.apple.com/downloads/xcode/simulators/ios_7_0_simulator.dmg

安装完毕后,将 /Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk 目录移动到 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs 即可

VMware 10.0.3 安装 Mac OS X 10.9

安装 VMware
如果内存足够大,可以进行这样设置:
sudo vmware
Edit -> Preferences -> Memory -> Fit all virtual machine memory into reserved host RAM
这样所有虚拟机在运行的时候将立刻从主机分配其设置的内存大小

下载安装 unlock-all-v130,地址:
http://pan.baidu.com/s/1jG42who
提取密码:4yph

下载 VMsvga2_v1.2.5_OS_10.9.pkg 和 EnsoniqAudioPCI_v1.0.3_Lion.pkg 和 guestd_patches.pkg,地址:
http://sourceforge.net/projects/vmsvga2/files/

创建虚拟机,类型为 Mac OS X 10.9
硬盘 40GB 即可,CPU 数量2,内存 3GB 即可
Virtual Machine Settings -> Options -> Advanced -> Gather debugging information:None
加载安装盘 iso 镜像,启动
将硬盘抹掉,名称为 Mavericks,安装

如果内存足够大,应该让虚拟机直接使用物理内存,方法:
Virtual Machine Settings -> Oprions -> Advanced -> Disable memory page trimming(需先进行文章开头处的 Fit all virtual machine memory into reserved host RAM 设置操作)
编辑虚拟机安装目录下的 Mac OS X 10.9.vmx,在最后添加(如果已有则修改):
mainMem.useNamedFile="FALSE"

安装完毕之后强制重启,将虚拟光驱指向 unlock-all-v130/tools/darwin.iso
加载虚拟光盘,安装VMware Tools,关机

设置文件共享,将之前下载的 VMsvga2_v1.2.5_OS_10.9.pkg 共享到虚拟机,方法:
Virtual Machine Settings -> Options -> Shared Folders -> Always enabled
点击 Add 选定目录并设置名称 -> Ok -> Save
启动,Finder -> 偏好设置 -> 勾选“已连接的服务器”,桌面上就会出现“VMware Shared Folders”
找到 VMsvga2_v1.2.5_OS_10.9.pkg,拷贝到本地并安装,安装完毕关机
Virtual Machine Settings -> Hardware -> Display -> 勾选Accelerate 3D graphics
启动

完全关闭虚拟机和 VMware,找到虚拟机安装目录下的 Mac OS X 10.9.vmx,在最后添加(如果已有则修改):
svga.autodetect = "FALSE"
svga.vramSize = "134217728"
保存重启

安装自动调整客户机分辨率的补丁 guestd_patches.pkg,安装完毕重启,拉伸窗体时客户机就会自动调整分辨率(需要勾选 View -> Autosize -> Autofit Guest)

如若没有声音,则需安装 EnsoniqAudioPCI_v1.0.3_Lion.pkg 并编辑虚拟机安装目录下的 Mac OS X 10.9.vmx,修改如下内容:
sound.autodetect = "FALSE"
sound.virtualDev = "es1371"

如若声音断断续续,有两种解决方法,推荐第2种:
1)添加:
sound.bufferTime = "400"
pciSound.playBuffer = "400"
上面的数字400要根据情况而定,请依次尝试100-4000,选择一个合适的最小值
2)添加:
sound.highPriority = "TRUE"
pciSound.DAC1InterruptsPerSec = 0
pciSound.DAC2InterruptsPerSec = "16"
pciSound.priorityBoost = "TRUE"

显示隐藏文件:
defaults write com.apple.finder AppleShowAllFiles -bool YES
重启 Finder 应用修改:
killall Finder 

Xcode 6.0.1 下载地址:
http://pan.baidu.com/s/1dDvjhW9
提取密码:0e6r

VirtualBox 4.3.16 安装 Mac OS X 10.9

安装 Virtual Box 和 Extension Pack

下载 HackBoot_Mav.iso 和系统盘镜像,地址:
http://pan.baidu.com/s/1c0IlO7Q
提取密码:20hs

创建虚拟机,系统类行为 Max OS X(64 bit),去掉启用 EFI 选项,所有光驱和硬盘都为 SATA 类型,硬盘 30GB 即可
虚拟光驱指向 HackBoot_Mav.iso,启动,出现变色龙界面
将虚拟光驱指向 Mac OS X iso 镜像,按 F5,出现新引导项,按 Enter

可能会停在 [IOBluetoothHCIController][start] -- completed 一段时间


如果出现:
summary table not allowed on fs with block size of 2048
hfs: could not initializc summary table forOSX Base System
解决方法(Linux):
vboxmanage modifyvm "虚拟机名称" --cpuidset 1 000306a9 00100800 3d9ae3bf bfebfbff
解决方法(Windows):
C:Program FilesOracleVirtualBox>VBoxManage.exe modifyvm "虚拟机名称" --cpuidset 1 000306a9 00100800 3d9ae3bf bfebfbff


将硬盘抹掉,名称必须为 Mavericks,安装
安装完毕之后将光驱指向 HackBoot_Mav.iso,强制重启


出现引导界面,再将虚拟光驱指向 Mac OS X iso 镜像,按 F5
出现新引导项,按 Enter,再次进入安装程序,不要再次安装,选择实用工具->终端
依次输入命令安装内核扩展:
umount /Volumes/Mavericks
hdiutil attach /dev/disk0s2 -mountpoint /Volumes/mnt
cp -rp /Backup/Kexts/ElliottForceLegacyRTC.kext /Volumes/mnt/System/Library/Extensions
cp -rp /Backup/Kexts/FakeSMC.kext /Volumes/mnt/System/Library/Extensions
cp -rp /Backup/Kexts/NullCPUPowerManagement.kext /Volumes/mnt/System/Library/Extensions
chmod -R 0755 /Volumes/mnt/System/Library/Extensions/ElliottForceLegacyRTC.kext
chmod -R 0755 /Volumes/mnt/System/Library/Extensions/FakeSMC.kext 
chmod -R 0755 /Volumes/mnt/System/Library/Extensions/NullCPUPowerManagement.kext
chown -R root:wheel /Volumes/mnt/System/Library/Extensions/ElliottForceLegacyRTC.kext
chown -R root:wheel /Volumes/mnt/System/Library/Extensions/FakeSMC.kext 
chown -R root:wheel /Volumes/mnt/System/Library/Extensions/NullCPUPowerManagement.kext
hdiutil detach /Volumes/mnt
最后一句可能需要重复执行,直到出现 disk0 unmounted. disk0 ejected.
退出终端后, 等待虚拟机的硬盘指示灯熄灭,将光驱指向 HackBoot_Mav.iso,强制重启
选择 Mavericks,Enter,即可正常使用,以后都需要用 HackBoot_Mav.iso 引导启动

Fedora 20 配置 Android + OpenCV 开发环境

Fedora 20 + OpenCV 2.4.9 + android-ndk-r10b + Eclipse 4.4 + CDT 8.4

sudo yum install gcc gcc-c++ cmake python-devel ffmpeg ffmpeg-devel zlib-devel
sudo yum install libtiff-devel jasper jasper-devel libpng-devel gtk2-devel
sudo yum install gstreamer-devel gstreamer-plugins-base-devel libdc1394-devel
sudo yum install libv4l-devel
sudo ln -s /usr/include/libv4l1-videodev.h /usr/include/linux/videodev.h

cd ~/opencv
mkdir release
cd release
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..
make
sudo make install

vi ~/.bash_profile
在最后添加环境变量:
export ANDROID_SDK_ROOT=/home/li/android-sdk-linux
export PATH=${PATH}:${ANDROID_SDK_ROOT}/platform-tools:${ANDROID_SDK_ROOT}/tools
export ANDROID_NDK_ROOT=/home/li/android-ndk
export PATH=${PATH}:${ANDROID_NDK_ROOT}

$ source ~/.bash_profile

使用 ndk-build -v 测试配置:
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
This program built for x86_64-pc-linux-gnu

下载并安装 Eclipse + CDT + ADT
修改 Preferences -> Android -> NDK,指向 NDK 目录
导入 Android NDK 项目并测试编译:
Import -> Android -> Existing Android Code Into Workspace: /android-ndk-r10b/samples/hello-jni
在 HelloJni 项目上点右键,选择 Android Tools -> Add Native Support,
在项目的 Java Build Path 中添加 HelloJni 项目,用 ARM 模式的模拟器运行

导入 OpenCV for Android 中的全部项目(将根目录导入)
在 OpenCV Library 项目上点右键,选择 Android Tools -> Add Native Support,
设置项目版本 Android 3.0 or higher,运行 demo 项目

若出现对话框 OpenCV Manager package was not found! Try to install it? 则需安装 Manager,方法:
<Android SDK path>/platform-tools/adb install <OpenCV4Android SDK path>/apk/OpenCV_2.4.9_Manager_2.18_armv7a-neon.apk

Fedora 20 x64 安装NSAM汇编器

到 http://www.nasm.us/ 下载最新代码
然后解压,执行:
./configure
make
sudo make install

sudo yum install glibc.i686
sudo yum install libstdc++.i686
sudo yum install glibc-devel.i686

vi hello.asm
输入:
; hello.asm 
section .data        ; 数据段声明
    msg db "Hello, world!", 0xA     ; 要输出的字符串
    len equ $ - msg  ; 字串长度
section .text        ; 代码段声明
global main          ; 指定入口函数
main:                ; 在屏幕上显示一个字符串
    mov edx, len     ; 参数三:字符串长度
    mov ecx, msg     ; 参数二:要显示的字符串
    mov ebx, 1       ; 参数一:文件描述符(stdout) 
    mov eax, 4       ; 系统调用号(sys_write) 
    int 0x80         ; 调用内核功能
                     ; 退出程序
    mov ebx, 0       ; 参数一:退出代码
    mov eax, 1       ; 系统调用号(sys_exit) 
    int 0x80         ; 调用内核功能

执行汇编器:
nasm [-g] -f elf hello.asm
-g 是生产调试信息到目标文件

执行链接器:
gcc -m32 [-g] -o hello hello.o

执行最终输出的可执行程序:
./hello

将输出:
Hello, world!

调试:
gdb hello

用 list 命令查看代码:
(gdb) list
1	; hello.asm 
2	section .data        ; 数据段声明
3	    msg db "Hello, world!", 0xA     ; 要输出的字符串
4	    len equ $ - msg  ; 字串长度
5	section .text        ; 代码段声明
6	global main          ; 指定入口函数
7	main:                ; 在屏幕上显示一个字符串
8	    mov edx, len     ; 参数三:字符串长度
9	    mov ecx, msg     ; 参数二:要显示的字符串
10	    mov ebx, 1       ; 参数一:文件描述符(stdout) 
(gdb) list
11	    mov eax, 4       ; 系统调用号(sys_write) 
12	    int 0x80         ; 调用内核功能
13	                     ; 退出程序
14	    mov ebx, 0       ; 参数一:退出代码
15	    mov eax, 1       ; 系统调用号(sys_exit) 
16	    int 0x80         ; 调用内核功能

将 main 设置为断点:
(gdb) b main
Breakpoint 1 at 0x80483f0

运行程序,然后程序执行到 main 处停下:
(gdb) r
Starting program: /home/li/hello 

Breakpoint 1, 0x080483f0 in main ()
Missing separate debuginfos, use: debuginfo-install glibc-2.18-14.fc20.i686

Docker CentOS7 安装 Apache + PHP + MySQL

去 https://registry.hub.docker.com/ 搜索对应 TAG:
docker pull centos:centos7
以 CentOS 镜像作为基础镜像,启动容器并在其中执行 /bin/bash 命令, -t -i 参数用于创建一个容器
docker run -t -i centos:centos7 /bin/bash

在容器中,执行下面的命令:
yum update
yum install httpd httpd-devel

安装完成后,输入 exit 退出容器的命令行。
执行 docker ps -a,可以看到被终止的容器

把所做的改变提交到一个新的容器:
docker commit [container-id] custom/centos_httpd
容器成功提交后,执行 docker images ,会看到刚才提交的容器

删除旧容器:
docker rm [container-id]

删除旧镜像:
docker rmi [image-id]
docker rmi [image-name][:image-tag]

以刚才提交的容器为基础容器,再来创建一个新的容器:
docker run -t -i custom/centos_httpd /bin/bash
apachectl -k start

查看 Docker 中 CentOS 系统IP地址:
ip addr
查看测试页面(为上一步看到的IP地址):
http://172.17.0.8/


yum install wget tar gcc gcc-c++ make

安装PHP 5.4:
yum install php php-devel
yum install php-bcmath php-gd libjpeg* php-intl php-ldap php-mbstring php-mhash php-mysqlnd php-mysqli php-odbc php-pdo php-pear php-pecl-memcache php-soap php-xml php-xmlrpc

pecl install apc
vi /etc/php.ini
添加:extension=apc.so

安装libmcrypt:
wget http://sourceforge.net/projects/mcrypt/files/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gz
tar -xvzf libmcrypt-2.5.8.tar.gz
cd libmcrypt-2.5.8
./configure
make && make install

安装mhash:
wget http://sourceforge.net/projects/mhash/files/mhash/0.9.9.9/mhash-0.9.9.9.tar.gz
tar -xvzf mhash-0.9.9.9.tar.gz
cd mhash-0.9.9.9
./configure
make && make install

安装mcrypt:
wget http://sourceforge.net/projects/mcrypt/files/MCrypt/2.6.8/mcrypt-2.6.8.tar.gz
tar -xvzf mcrypt-2.6.8.tar.gz
cd mcrypt-2.6.8
LD_LIBRARY_PATH=/usr/local/lib ./configure
make && make install

安装php-mcrypt:
wget http://museum.php.net/php5/php-5.4.16.tar.gz
tar -xvzf php-5.4.16.tar.gz
cd php-5.4.16/ext/mcrypt
phpize
php-config
./configure
make && make install
vi /etc/php.ini
添加:extension=mcrypt.so

apachectl -k stop
apachectl -k start

安装 MySQL Client:
rpm -ivh http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
#上面的地址来自 http://dev.mysql.com/downloads/repo/yum/ ,可能会有变动,请自行修改
yum install mysql

ctrl + p  ctrl + q 可退出并使容器继续运行

安装 MySQL:
docker pull mysql:5.6.20
启动:
docker run --name docker_mysql -e MYSQL_ROOT_PASSWORD=mysecretpassword -d -P mysql:5.6.20
查看端口映射:
docker ps -a
0.0.0.0:49154->3306/tcp
此时容器的3306端口会被映射到宿主机器的49154端口,这样我们就可以通过宿主机器的49154端口访问 docker_mysql 了
mysql --host=127.0.0.1 --port=49154 -uroot -pmysecretpassword

安装 OpenSSH:
yum install openssh-server openssh-clients
ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
启动 ssh 服务:
/usr/sbin/sshd
修改密码:
yum install passwd
passwd
登录:
ssh root@localhost

解决 vi 中文乱码:
vi /etc/virc
在最后加上:
set fileencodings=utf-8,gb2312,gbk,gb18030
set termencoding=utf-8
set encoding=prc

在 CentOS 或者 Fedora 上运行 Docker 容器

Fedora 20:
sudo yum -y install docker-io

CentOS 7:
sudo yum install docker

sudo systemctl start docker
sudo systemctl enable docker

在 CentOS 或 Fedora 上安装完成 Docker 后,为了能以非 root 用户运行 docker,你需要把你自己添加进 docker 用户组,使用如下命令:
sudo usermod -a -G docker login_name
注销,以使组改变生效。


Docker 的基础用法:
去 https://registry.hub.docker.com/ 搜索对应 TAG:
docker pull centos:centos7
例如,以交互式模式启动一个 CentOS 容器,运行以下命令,这最后的参数 '/bin/bash' 是用于在容器内部执行的命令。
docker run -i -t centos:centos7 /bin/bash
查看 CentOS 版本:
cat /etc/redhat-release
其他系统:
lsb_release -a

第一次我们运行以上命令,它将通过网络下载可用的 CentOS 的 Docker 镜像,并且使用这个镜像启动一个 Docker 容器,一个 CentOS 容器会立即启动,你将在容器中看到 console 提示,你能通过容器沙箱访问一个完全成熟的 CentOS 操作系统。

如果你在提示处键入 exit ,你将从容器中退出,并且容器将被停止。

获取一个所有容器的列表(包括停止的),运行如下命令:
docker ps -a

后台重启一个已经停止的容器:
docker start [container-id]

移除一个已经停止的容器:
docker rm [container-id]

为了查看或者是与容器交互,进入一个后台运行的容器:
docker attach [container-id]

你可以自由的定制一个正在运行的容器(比如,安装新软件),如果你想保存当前容器的变更,首先是键入 exit 命令退出交互式模式,然后通过使用这个命令来保存这个变更的镜像,存储成一个新的不同的镜像。
docker commit [container-id] [new-image-name]

获取你的容器的容器 ID ,你可以使用 docker ps -a 来查看。

一旦你像这样创建一个新的镜像,你可以使用这个镜像运行一个新的容器。

你也可以下载任何的公共镜像(比如: ubuntu, mysql),并且在本地仓库存储他们(官方索引地址 https://registry.hub.docker.com/):
docker pull [image name]

查看所有的本地已经下载或者保存的镜像:
docker images

你可以选择一个指定的镜像来启动一个容器:
docker run -i -t [image-id] /bin/bash

从本地仓库中删除一个镜像:
docker rmi [image-id]
docker rmi [image-name][:image-tag]

修改镜像名称:
docker tag [image-id] [image-name][:image-tag]