zshrc 启动速度分析和优化

由于常年不科学的使用和随便塞东西,我的 .zshrc 里有太多太多的各类语言、SDK 的启动逻辑,因而它逐渐变得不堪重负起来。今天终于受不了了,我决定对它进行整理,移除部分太慢的代码,并且将部分不需要实时加载的东西懒加载。

速度优化的前期准备

要开始优化,首先需要有科学的评估速度的方法,这将使我们能够找到速度的瓶颈。

测算总启动速度

使用 time 命令,可以测算 zsh 的启动时间:

$ \time zsh -i -c exit
        6.62 real         3.23 user         2.29 sys

可以看到,我的 zsh 启动时间需要 6.62 秒,可以说是十分糟糕的速度了。作为对比,不加载任何启动命令脚本的 zsh 启动速度飞快,甚至只需要 0.01 秒:

$ \time zsh --no-rcs -i -c exit
        0.01 real         0.00 user         0.00 sys

设定优化目标

一个合理而可以达到的目标对速度优化也至关重要。今天,我的目标是不修改第三方代码的情况下,将加载时间优化到一秒以内,而尽量不损失任何功能。我们评估时间所用的方法就如同上文所述,以 time 测算的 real 时间为准。

找到速度瓶颈

找到速度瓶颈的方法通常是运行“性能评估”,也就是 Profile。由于 zshrc 是一个 bash 脚本,我需要寻找一些 zsh 脚本的 profile 方法。

一番查找之后,找到了 Profiling zsh shell scripts 这篇文章。按照文章中描述的方法,我们在 .zshrc 的最前面加入:

PS4=$'\\\011%D{%s%6.}\011%x\011%I\011%N\011%e\011'
exec 3>&2 2>/tmp/zshstart.$$.log
setopt xtrace prompt_subst

在最后面加入:

unsetopt xtrace
exec 2>&3 3>&-

然后执行 zsh -i -c exit 让 zsh 运行一遍初始化。执行完成后,你应该可以在 /tmp 下看到输出的结果:

$ ls -l /tmp/zshstart*.log
-rw-r--r-- 1 oott123 wheel 470745 Jun  9 16:26 /tmp/zshstart.8854.log

这个日志文件已经可以看了,只不过,人类似乎很难阅读……万幸的是,文章的作者也给出了一个工具,用于把输出的文件转换为 KCachegrind 可读的 callgrind 文件。然而,它是 OCaml 写的,也没有提供预编译二进制文件,我们必须先安装 OCaml 来编译运行它。为了速度,那就装吧!

由于我们在 macOS 下工作,通过 brew 可以很轻松的安装 OCaml、opam和 QCacheGrind(这里使用 QCacheGrind,效果和 KCachegrind 应该一样):

brew install ocaml
brew install opam
brew install qcachegrind --with-graphviz

然后使用作者提供的工具 zshprof 将日志文件生成 callgrind 文件,并使用 QCacheGrind 打开它:

git clone https://github.com/raboof/zshprof.git
cd zshprof
opam init # 完了,我们还没开始优化,又有一个程序往 .zshrc 里写东西了……这就是为什么它越来越卡
eval `opam config env`
opam install ocamlfind

# ocamlfind ocamlopt -linkpkg -thread -package str ZshXtraceToCallgrind.ml
ocamlfind ocamlopt -linkpkg Callgrind.ml -linkpkg ZshXtrace.ml -thread -package str ZshXtraceToCallgrind.ml
./a.out < /tmp/zshstart.8854.log > zsh.callgrind
qcachegrind zsh.callgrind

记住这个艰辛的过程,我们在优化途中会经常运行它,以检查优化效果。

打开 QCacheGrind,找到你的 zshrc 文件的源码,点击按 micros 排序:

QCacheGrind 主界面,其中显示了每行源码的耗时。

(由于我个人将自己的所有 rc 代码都写到了一个叫 .shellrc 的文件里,因此这里的截图是 .shellrc 这个文件的。)

很显然,大量类似 dvmphp-version 这样的版本管理工具初始化命令和他们的补全函数消耗了绝大多数时间。我们就从这些函数入手,优化我们的 shell 加载速度。

移除不必要的进程创建

不看不知道,一看吓一跳。就拿下面两句来看:

[[ -s "$(brew --prefix dvm)/dvm.sh" ]] && source "$(brew --prefix dvm)/dvm.sh"
[[ -s "$(brew --prefix dvm)/bash_completion" ]] && source "$(brew --prefix dvm)/bash_completion"

光这两句,就执行了 4 次 brew --prefix dvm,而这个命令在我的机器上执行一次需要 0.7 秒:

$ \time brew --prefix dvm > /dev/null
        0.71 real         0.37 user         0.19 sys

为了加载 dvm 而执行的 brew 就用掉了 4*0.71=2.84 秒,这还不算 dvm 本身的加载时间!而对于我而言,brew --prefix dvm 的结果执行一万次也不会改变,一定是 /usr/local/opt/dvm ——因为我知道我不会移动默认的 prefix 位置。那么,将 brew --prefix 命令都替换为字符串:

[[ -s "/usr/local/opt/dvm/dvm.sh" ]] && source "/usr/local/opt/dvm/dvm.sh"
[[ -s "/usr/local/opt/dvm/bash_completion" ]] && source "/usr/local/opt/dvm/bash_completion"

再跑个分试试:

$ \time zsh -i -c exit
        1.62 real         0.93 user         0.69 sys

哇,真是成效显著,一下就从 6.62 秒缩短到了 1.62 秒,整整减少了 5 秒钟!

移除 brew 之后 QCacheGrind 的样子

重新跑一次 profile,也可以看到,那些明显拖慢启动速度的项目已经没有了。顺便吐槽下:Ruby 和 brew 启动也太慢了吧……

现在,我们还剩下一些别的命令初始化和命令补全的耗时。这些命令看起来很好,只是他们的内部耗时相对较大。

懒加载命令和补全功能

为了不影响功能,又不修改第三方代码内部实现,我们无法很好的优化那些非常缓慢的命令初始化过程。但是,大多数情况下,我不会用到其中大多数命令。因此,我希望实现“懒加载”功能,在我首次使用这些功能的时候,zsh 帮我初始化他们。

懒加载的好处是启动快,缺点是运行的时候会比较慢。但这个慢是分散的,不会全部都卡在启动的那几秒钟里,所以我认为效果损失是可以接受的。那么,怎样懒加载他们呢?

一个很简单的方法是,把命令换成一个占位函数,然后在这个函数中再去执行真正的二进制文件。比如:

dvm() {
    # 移除占位
    unfunction "dvm"
    # 加载真正的 dvm
    source "$(brew --prefix dvm)/dvm.sh"
    # 加载 dvm 的补全
    source "$(brew --prefix dvm)/bash_completion"
    # 执行真正的 dvm 命令
    dvm "$@"
}

(上述代码实现参考自:Speed up initial zsh startup with lazy-loading,基于 CC-BY-SA 使用)

但这样,第一次使用 dvm 的时候,就没有命令补全了!于是,命令补全也用同样的方法做懒加载:

__lazycomp_dvm() {
    # 移除占位
    compdef -d dvm
    unfunction dvm
    source "$(brew --prefix dvm)/bash_completion"
}
compdef __lazycomp_dvm dvm

这样,在第一次按 Tab 的时候,就会加载 dvm 的补全。由于不同命令的补全逻辑不一样,所以还是没法第一次执行补全命令,但多按一次 Tab 键至少比多执行一次命令好多了。

把这两个懒加载方法写成函数:

my_lazyload_add_command() {
    local command_name=$1
    eval "${command_name}() { \
        unfunction ${command_name}; \
        _my_lazyload_command_${command_name}; \
        return ${command_name} \"\$@\"; \
    }"
}
my_lazyload_add_comp() {
    local command_name=$1
    local comp_name="_my_lazyload__compfunc_${command_name}"
    eval "${comp_name}() { \
        compdef -d ${comp_name}; \
        unfunction ${comp_name}; \
        _my_lazyload_comp_${command_name}; \
    }"
    compdef $comp_name $command_name
}

再把 dvm 等不太常用的命令初始化逻辑用这两个函数写出来,比如说这样:

_my_lazyload_command_dvm() {
    source "/usr/local/opt/dvm/dvm.sh"
}
_my_lazyload_comp_dvm() {
    source "/usr/local/opt/dvm/bash_completion"
}
my_lazyload_add_command dvm
my_lazyload_add_comp dvm

搞完之后再跑个分试试:

$ \time zsh -i -c exit
        0.37 real         0.24 user         0.18 sys

哇,已经只有 0.37 秒了。再看看 Profile,大头已经到了 oh-my-zsh 里:

最后的 Profile 结果

打开 zsh 一看,确实感觉快多了。试试那些被我们 lazy load 的命令,用起来感觉也没有什么延迟。优化初见成效,收工。

对了,完成之后记得把一开始我们添加到 .zshrc 中的几行 Profile 代码干掉。否则,它会创建一堆临时文件,并且它本身也会影响一点点速度。

如何让你的 .zshrc 更高效

这次优化,也让我看到平时写 shell rc 文件的一些常见误区,这些问题可能导致 shell 启动慢到爆表。总结说来,主要是这几点:

  • 尽量避免在初始化脚本中调用外部进程,特别是脚本语言的解释器,比如 node, ruby。他们的冷启动时间非常长,长到你怀疑人生。
  • 避免重复执行语句,特别是调用外部进程的语句。
  • 避免增加不必要的初始化代码。比如调用 nvm 初始化 node.js 版本。应当利用懒加载或者 avn 等方式来做这个事情,或者干脆写死环境变量。
  • 尽量多使用懒加载,避免加载不必要的函数。

其实今天本来还想顺便把 .zshrc 给模块化,并加入版本控制的,但调优它本身花掉了太多时间,这篇文章也足够长了,就下次再弄吧。

User Mode Linux – 一个用户态的 Linux 内核

User Mode Linux 可以在用户态启动一个 Linux。这使我们能在类似 OpenVZ 虚拟化技术的系统上,使用最新的 Linux 内核;甚至可以在非 root 用户下启动。

但有些 OpenVZ VPS 的 TOS 可能不允许你这么做;另外,这样可能会有一定的性能损失。

准备 rootfs 镜像

我们找一台 Ubuntu Server 来准备 rootfs。这个时候需要有 root 权限,因为你需要 mount 一个 loop 文件系统,chroot 改点东西之类的。

# 下载镜像
wget http://uk.images.linuxcontainers.org/images/alpine/3.1/amd64/default/20170305_17:50/rootfs.tar.xz
# 安装解压软件
apt install xz-utils
# 创建镜像,32 M 对于 alpine 是够用的
dd if=/dev/zero of=rootfs.img bs=1M count=32
mkfs.ext4 rootfs.img
# 把镜像挂到 loop 上
mkdir rootfs
sudo mount -o loop rootfs.img rootfs
sudo tar xvf rootfs.tar.xz -C rootfs
# 给 root 设个密码
sudo chroot rootfs /bin/sh
  passwd
  exit
# 清理
sudo umount rootfs
rmdir rootfs

此时 rootfs.img 已经做好了。

编译内核

我们继续用 ubuntu server 来编译 UML 内核。

sudo apt install build-essential kernel-package fakeroot libncurses5-dev libssl-dev ccache
# 抓内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.10.1.tar.xz
tar xvf linux-4.10.1.tar.xz
cd linux-4.10.1
make defconfig ARCH=um
make menuconfig ARCH=um
# 这里会出来一个界面让你配置内核,有茫茫多的参数可以选
# 大概找一些 linux 内核编译的文章可以参考吧
# 主要是 User Mode Linux 有一些特殊选项可以注意下,比如强制静态编译什么的
# 然后就开始正式编译啦
make ARCH=um
strip linux # 移除调试符号

编译过程可能比较慢。有时候会有一些库找不到啥的,apt 装一下就好。

获取 slirp

slirp 是用来在没有 root 的情况下联网的。

apt source slirp

这里获取到的 slirp 放到目标机器上编译,不需要在 ubuntu server 上搞了。

cd slirp-1.0.17/src
./configure
make CFLAGS="-I. -DUSE_PPP -DUSE_MS_DNS -DFULL_BOLT -fno-strict-aliasing -Wno-unused -std=gnu89" clean all

启动 UML

现在万事俱备,只欠东风了。

# 在 UML Host 上
# 把一些二进制文件放好
slirp=/home/oott123/uml/slirp/slirp-1.0.17/bin/slirp
uml=/home/oott123/uml/kernel/linux-4.10.1/linux
rootfs=/home/oott123/uml/alpine/rootfs.img
cow=/home/oott123/uml/machine/demo/data.cow # 放 copy-on-write 数据的地方
export TMPDIR=/tmp
$uml rw ubda=$cow,$rootfs mem=128M eth0=slirp,,$slirp

配置网络

启动之后你可能会发现没有网络。不用担心,改改网络配置:

# 在 UML 里
vi /etc/network/interfaces
#####
auto eth0
iface eth0 inet static
address 10.0.2.15
gateway 10.0.2.2
netmask 255.255.255.0
dns-nameservers 10.0.2.3
hostname $(hostname)
#####
/etc/init.d/networking restart
echo 'nameserver 114.114.114.114' > /etc/resolv.conf
# 测试网络
wget -O- myip.ipip.net
# 安装软件
apk update
apk add curl
curl myip.ipip.net
# 关闭 UML
halt

与主机交互

或许你需要将 UML 内的端口转发到主机上。

# 在主机上
vi ~/.slirprc
redir 2222 22

或许你需要访问主机的文件系统。

# 在 UML 上,把 UML 内的 /mnt/home 映射到 /home
# 注意权限问题、注意两边不要同时读写
mkdir /mnt/home
mount none /mnt/home -t hostfs -o /home

记 VestaCP 的 Let’s Encrypt 自助签发配置

主要原料是 https://github.com/interbrite/letsencrypt-vesta 这个脚本。

然后写一个 python 脚本用来给用户自助签发

#!/bin/env python
import sys
import re
import os
import subprocess

args = ["/usr/local/bin/letsencrypt-vesta", "-u", os.environ['SUDO_USER']]
for i in sys.argv[1:]:
    if re.match('^[a-z0-9][a-z0-9.-]+$', i):
        args.append(i)
subprocess.call(args)

给用户 sudo 权限

%users ALL=NOPASSWD:/usr/local/bin/sign.py

然后让用户执行 sudo /usr/local/bin/sign.py 域名 即可。

我是如何搭建一个低成本的 Minecraft 小型服务器的

注目:本人不接受任何的免费的技术咨询指导,提问请先发红包!

自己有个小型的 Minecraft 服务器,为了节约成本以及管理方便,做了一整套自动化的管理方案。整套方案代码量近万行,同时很多东西都写死在了代码里,想开源但是很多东西目前还不完善,根本没法拿出手……所以写了个思路整理,若有需要的朋友可以参考着搭一个。

一、 服务端的选用

没啥好说的,由于需要 mod ,用的 KCauldron-1.7.10, mod 都是自己打的, Forge + LiteModLoader ,大概十几个 mod 。

插件用了一个非正版验证插件,配合宏与按键绑定(Marcos and key bind)进行自动登录。

二、 ISP 的选择

ISP 使用青云。由于服务器在线人数常年在 5 人以下,故选用青云可以随时开关实例,节约运行成本。

青云关机的话,公网 IP 和硬盘也收费,故使用 API 将 ip 释放,仅保留硬盘扣费。

三、 如何开启服务器

由于公网 IP 变动和服务器常年关闭,在另外一台服务器上搭建了一个 HTTP 服务,并自己写了个启动器。

当启动器启动的时候调用那个服务去开启服务器、下发公网 IP 并写入 servers.dat,避免动态域名解析被缓存的问题。

开启服务器后,利用 Telegram bot API ,在群里进行通知服务器的新 IP。

利用 mc 协议检查十分钟没人登录后,服务器自行关闭。

四、 如何管理 mc 服务

使用 supervisord 进行自启动。

需要控制台时,利用 supervisorctl fg minecraft 来调用控制台。

在 Windows 下使用 boot2docker 搭建 docker 开发环境

很久以前研究过一会儿 docker ,当时觉得只是一个轻量级的虚拟机包装而已,没觉得有多大用。

最近发现这玩意还蛮好玩的,但是又懒得翻以前的虚拟机了,于是发现一个叫 boot2docker国内下载(via DaoCloud) 的东西。官方宣称是“It runs completely from RAM, weighs ~27MB and boots in ~5s (YMMV).”。

下载回来的安装包有 100 多 MB,当时在想说好的 27MB 呢,结果发现包里有个 VirtualBox 和一个 msysgit ……简直坑。

如果你的机器上没有这俩玩意,那就放心大胆的直接安装吧。它会帮你把各种琐事配置好,官方的指导文档里也写的十分详细。

如果你的系统里有 VirtualBox 了,那也就直接安装,它会自动识别系统上的 vbox 的,不过你最好先测试一下你的 Host-only 网卡是否正常。

那么重点来了:如果你的系统里有 msysgit,并且还有其它的 GNU 工具链,比如我这种装了 Gow 的人,那么其实这个玩意是有坑的。

要说这个坑,首先要谈一谈 boot2docker 的工作原理。 boot2docker 由 VirualBox 里的一个叫 boot2docker 的虚拟机、一个用来管理 Virual Box 里的虚拟机的工具 boot2docker.exe 和 docker 本体 docker.exe 组成。

当你执行 sh start.sh 的时候,它首先调用 boot2docker init 来创建这个虚拟机(如果已经存在则跳过);其次将该虚拟机启动;最后使用 boot2docker ip 来获取虚拟机的 ip 地址并设置环境变量。

boot2docker ip 这个命令,是调用系统中的 ssh 来获取 ip 地址的。它在 init 的时候,将虚拟机的 22 端口转发到宿主机的 2022 端口,并运行 ssh -p 2022 docker@localhost ip addr show dev eth1 来获取 ip 地址(详细的命令可运行 boot2doker -v ip 来显示)。而 Gow 里的 ssh 指令,则是调用了 PuTTY 的 plink.exe

那么问题来了:plink.exe 根本不支持 -p 语法,也不支持它所使用的 -o 等参数。

所以,如果你装了 Gow,请一定把 msys git 的路径加到 PATH 的最前方……起码是在 Gow 的前面,这样就不会被坑了。

处理完这些乱七八糟的事情之后,运行 boot2docker status 即可看到你的虚拟机的当前状态。下次使用时,只需执行 sh start.sh 即可自动配置好环境变量并且开启 git bash。在这个 git bash 里,你就可以执行你的 docker 指令了~

如果不想用 git bash,那么执行 boot2docker shellinit ,也可以看到应设置的环境变量,如:

export DOCKER_HOST=tcp://192.168.111.222:2376
export DOCKER_CERT_PATH='C:\Users\oott123\.boot2docker\certs\boot2docker-vm'
export DOCKER_TLS_VERIFY=1

此时,用 set 将这些环境变量设置好,即可在 cmd 中使用 docker 来管理 docker 容器了,如:

set DOCKER_HOST=tcp://192.168.111.222:2376
set DOCKER_CERT_PATH=C:\Users\oott123\.boot2docker\certs\boot2docker-vm
set DOCKER_TLS_VERIFY=1

试试 docker info 能不能显示出虚拟机的信息来吧。

supervisord 管理多进程程序如 pyspider 时的合适配置

现在的工程中,有一部分是用 pyspider 来抓取数据的,使用 supervisord 来管理它的进程。在使用过程中,遇到一个奇怪的问题:用supervisorctl stop pyspider之后,pyspider 并未全部停止,而是留下了三四个进程在系统中。

查阅官方手册,在 [program:x] Section Settings 中找到了名为 stopasgroup 的选项,解释如下:

If true, the flag causes supervisor to send the stop signal to the whole process group and implies killasgroup is true. This is useful for programs, such as Flask in debug mode, that do not propagate stop signals to their children, leaving them orphaned.

在 [program:pyspider] 小节,开启这个选项。如:

[program:pyspider]
command=/home/oott123/pyspider/run.py
autorestart=true
startsecs=2
user=oott123
directory=/home/oott123/pyspider/
redirect_stderr=true
stopasgroup=true
environment=WEBUI_HOST="127.0.0.1",WEBUI_PORT="5033"

然后重启 supervisord ,再测试停止 pyspider 的服务,它就不会残留那几个进程了。

CentOS 7 下 docker 安装拾穗

如何安装 docker

docker 在 CentOS 7 下已经收录到了 extras 包中,因此我们只需要执行:yum install docker 即可安装 docker。

安装后,使用systemctl start docker来启动 docker 的服务,再用systemctl enable docker来使其开机启动。顺带一提,service docker startchkconfig docker on也能达到同样的效果;但由于 CentOS 7 采用了 systemd 来管理服务和开启启动项,因此我们尽量使用 systemctl 来操作服务。

安装后输入 docker info 来查看 docker 信息。

[root@localhost data]# docker info
Containers: 0
Images: 0
Storage Driver: devicemapper
 Pool Name: docker-253:0-68161190-pool
 Data file: /var/lib/docker/devicemapper/devicemapper/data
 Metadata file: /var/lib/docker/devicemapper/devicemapper/metadata
 Data Space Used: 293.0 Mb
 Data Space Total: 102400.0 Mb
 Metadata Space Used: 0.7 Mb
 Metadata Space Total: 2048.0 Mb
Execution Driver: native-0.2
Kernel Version: 3.10.0-123.el7.x86_64

如何修改 Data file 路径

遇到的第一个问题,则是:docker 的数据都放到哪里了?显然,上面的 docker info 给了我们答案: 在/var/lib/docker/ 下。由于种种神奇的原因,我们希望尝试 修改 docker 的数据存放目录

编辑这个文件:vim /etc/sysconfig/docker,找到OPTIONS=--selinux-enabled -H fd://,修改为:OPTIONS=-g /mnt/docker0/data --selinux-enabled -H fd://。这样我们就成功的将 docker 目录修改成功了。

执行 systemctl restart docker ,再 docker info ,可以看到目录已经修改成功了。

如何使用国内的 docker 镜像源

考虑 DockerPool 这个 docker poll。

例如,我要下载 CentOS 7 的 docker 镜像,那么执行:

docker pull dl.dockerpool.com:5000/centos:centos7

镜像文件略微有点大,下得我心醉。在 DockerPoll Downloads 可以找到其它的镜像列表。

下载完成后,要把第三方 registry 的下载的文件打上 tag

docker tag dl.dockerpool.com:5000/centos:centos7 centos:centos7

如何清理 docker 容器

执行了一些简单的命令,比如docker run -t -i centos:centos7 ping baidu.com之后,再看 docker info ,发现 Containers 居然到了 4 。大惊,用 docker ps -a 查看后才知道,每次我用 docker run 的时候他都会新建一个 Container。试了下 docker rm ,一次只能删除一个。改用 docker rm $(docker ps -aq) ,成功删除它们。

参考:Docker——从入门到实践

还算比较完善的 Linux 服务器自动备份、上传脚本

脚本托管于gist,点击查看

由于内嵌会导致一定程度的网页加载阻塞,故此处内嵌并非实时版本。

#!/bin/bash
Err() { echo -ne "\e[1;31m"; Log "$@" 1>&2; echo -ne "\e[0m";}
Log() { echo $(date +"[%Y-%m-%d %H:%M:%S]") $@; }
Log ====每日备份脚本开始运行====
UPLOADER_BIN="/root/auto-scripts/bpcs_uploader/bpcs" #bpcs 上传脚本路径(见下方bpcs文件)
REMOTE_DIR=$(date +"%Y%m")/$(date +"%Y%m%d") # 远端上传路径
WORKING_DIR="/root/auto-scripts/backup_daily" # 工作路径
CONFIG_DIR=$WORKING_DIR/list.d #配置文件路径
BACKUP_DIR=$WORKING_DIR/backups #本地备份路径
CURRENT_DIR=$BACKUP_DIR/$(date +"%Y%m%d")
OLD_DIR=$BACKUP_DIR/$(date -d -3day +"%Y%m%d")
zipPassword=ChangeThePasswordToYourOwn #备份密码
#准备删除老文件
Log 删除旧备份文件夹:$OLD_DIR
rm -rf $OLD_DIR
#创建新目录
mkdir -p $CURRENT_DIR
#遍历配置目录,进行备份
for i in $(ls $CONFIG_DIR)
do
zipExclude="*/cache/* */tmp/* */temp/* */.npm/* */data/threadcache/*"
. $CONFIG_DIR/$i
Log 备份$backupDir为$i……
backupFile=$i.zip
zip -r -P $zipPassword $CURRENT_DIR/$backupFile $backupDir -x $zipExclude
Log 上传备份……
$UPLOADER_BIN upload $CURRENT_DIR/$backupFile $REMOTE_DIR/$backupFile
done
Log 备份完成!

用法懒的写,自己研究!

依赖:bpcs_uploader,以及 zip 。yum install zip

CentOS 6 + Apache 环境下,PHP 5.2 与 5.4 共存

别问我为什么还要用 5.2 ,我会告诉你是因为 ZendOPT 么。

言归正传。在 Linux 下,要装两个不同版本的 php 可不是下载两个 exe 那么简单。这里采用的方法,是手工编译 php 5.2 并使用 epel 源中的 php 5.4 。前者采用 fastcgi 模式运行,后者采用传统的 mod_php 来运行。

现在我们假设,环境中已经装好了 Apache 2.2 + MySQL 5.5 + PHP 5.4 ,并且希望加装一个 PHP 5.2.17 ,并且能够直接使用 MySQL 5.5。

首先需要安装 epel 。

rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm

然后安装依赖:

yum install httpd-devel libxml-delvel openssl-devel bzip2-devel libcurl-devel libjpeg-devel libpng-devel freetype-devel libmcrypt-devel libmhash-devel libtool-ltdl-devel

然后需要自己编译安装 libiconv,如下。

wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
tar -xvf libiconv-1.14.tar.gz
cd libiconv-1.14
./configure
make
make install

然后编译安装 PHP ,记得打开 cgi 选项。PHP 源码包请到官网下载。

./configure --prefix=/usr/local/php52 --with-config-file-path=/usr/local/php52/etc --with-config-file-scan-dir=/usr/local/php52/etc/php.d --with-openssl --with-zlib --with-bz2 --with-gettext --with-mhash --with-mcrypt --with-iconv=/usr/local/libiconv --with-curl --with-gd --with-jpeg-dir --with-png-dir --with-freetype-dir --with-mysql=/usr/bin/mysql --with-pdo-mysql=/usr/bin/mysql --enable-gd-native-ttf --enable-bcmath --enable-mbstring --enable-zip --enable-soap --enable-sockets --enable-ftp --without-pear --enable-fastcgi --enable-force-cgi-redirect
make
make install

这样 PHP 5.2.17 就被安装到了 /usr/local/php52 里了。

/usr/loca/php52/bin/php -v

然后配置 fastcgi 。 找到 ~/public_html/cgi-bin/ 目录,创建 php52.fcgi 文件如下:

#!/bin/sh
export PHP_FCGI_CHILDREN=4
export PHP_FCGI_MAX_REQUESTS=200
export PHPRC="/home/oott123/conf/php.ini"
exec /usr/local/php52/bin/php-cgi

其中 PHPRC 为用户定义的 php.ini 文件。再创建 .htaccess 文件(此处需要 AllowOverrideAll )如下:

<FilesMatch \.php$>
    SetHandler application/x-httpd-php-52
</FilesMatch>
Action application/x-httpd-php-52 /cgi-bin/php52.fcgi

如果需要再加入 php 5.4 的支持,再添加如下代码:

<FilesMatch \.php54$>
    SetHandler application/x-httpd-php
</FilesMatch>

至此已经完成了 PHP 多版本的配置。

在Linux下使用ftp.proxy搭建FTP反向代理/透明代理

由于某些原因,自己的FTP服务器访问起来相当不顺畅。好在手上有速度稍快的资源可以拿来当反代,Google了一大堆,最后终于找到了一个合适的项目,叫 ftp.proxy 。ftp.proxy 是一个提供 FTP 代理的程序,支持透明代理,也支持标准化的 FTP 代理。这里我们把它配置为透明(反向)代理来使用。

首先,到 ftp.proxy 的下载页面 查看最新的 ftp.proxy ,在服务器上下载它。写这篇文章的时候,最新的 ftp.proxy 是1.2.3。由于这个项目已经快10年没有更新了(The current stable version of ftp.proxy is 1.2.3 Released 30. July 2004),为了防止官方出现某些意外的错误,我把它放了一份到了百度网盘上,你可以考虑下载它。

wget http://www.ftpproxy.org/download/ftpproxy-current.tgz

然后就是编译安装。如同官网所说,这是一个“old story”。

tar -xvf ftpproxy-1.2.3.tgz #文件名请自行修改
cd ftpproxy-1.2.3/
make
mkdir /usr/local/man #如果出错是没关系的
sudo make install

安装的时候遇到了一个错误,是因为自己的 CentOS 并没有/usr/local/man/这个目录。修正的方法已经写在上面了。

然后就可以开始使用ftp.proxy了。如下:

ftp.proxy -D 12580 192.168.1.1:10086

其中12580是你要在反代服务器上监听的端口,而10086则是原服务器的端口。

在本地试着连接一下,Enjoy it~