Tengine 是由淘宝网发起的 Web 服务器项目。它在 Nginx 的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine 的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的 Web 平台。
从 2011 年 12 月开始,Tengine 成为一个开源项目,Tengine 团队在积极地开发和维护着它。Tengine 团队的核心成员来自于淘宝、搜狗等互联网企业。Tengine 是社区合作的成果,我们欢迎大家参与其中,贡献自己的力量。
以下沿引项目主页上的特性介绍:
• 继承 Nginx-1.2.8 的所有特性,100% 兼容 Nginx 的配置;
• 动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个 Tengine;
• 更多负载均衡算法支持。如会话保持,一致性 hash 等;
• 输入过滤器机制支持。通过使用这种机制 Web 应用防火墙的编写更为方便;
• 动态脚本语言 Lua 支持。扩展功能非常高效简单;
• 支持管道(pipe)和 syslog(本地和远端)形式的日志以及日志抽样;
• 组合多个 CSS、JavaScript 文件的访问请求变成一个请求;
• 可以对后端的服务器进行主动健康检查,根据服务器状态自动上线下线;
• 自动根据 CPU 数目设置进程个数和绑定 CPU 亲缘性;
• 监控系统的负载和资源占用从而对系统进行保护;
• 显示对运维人员更友好的出错信息,便于定位出错机器;
• 更强大的防攻击(访问速度限制)模块;
• 更方便的命令行参数,如列出编译的模块列表、支持的指令等;
• 可以根据访问文件类型设置过期时间;
1.下载Tengine安装包
官网:http://tengine.taobao.org/
下载地址:http://tengine.taobao.org/download.html
2.服务器上传、安装
将Tengine安装包上传到服务器(一般安装在 /usr/local 目录下)
使用命令解压安装包:
tar -zxvf ./tengine-2.3.3.tar.gz
使用命令:
cd ./tengine-2.3.3
进入到Tengine解压包中
编译安装:
./configure --prefix=/安装路径
安装路径就是想将Tengine安装在的路径,例:./ configure --prefix=/usr/local/tengine
如果出现错误,则缺少系统依赖组件:
安装依赖命令:
yum install gcc openssl-devel pcre-devel zlib-devel
没问题之后使用安装命令安装Tengine:
make && make install
3、基本命令使用
安装好之后先进入到Tengine目录:
cd /usr/local/Tengine/sbin
运行Tengine(在sbin目录下):./nginx
停止Tengine:
1.先使用命令:ps -ef | grep nginx
查看进程
2.使用:kill -9 端口号
终止掉这两个进程(端口号写上面图片框选出来的端口,需要关闭两个进程,所以需要执行两次)
4.脚本方式启动Tengine
1.使用命令:/etc/init.d
进入到此目录下
2.再使用:vi nginx
然后将脚本内容粘贴到文件中,wq保存退出
3.没有权限,需要使用:chmod 777 ./nginx
给文件权限(777是最高权限)
启动服务命令:
- 启动服务:service nginx start
- 停止:service nginx stop
- 状态:service nginx status
- 动态重载配置文件:service nginx reload
#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig: - 85 15
# description: Nginx is an HTTP(S) server, HTTP(S) reverse \
# proxy and IMAP/POP3 proxy server
# processname: nginx
# config: /etc/nginx/nginx.conf
# config: /etc/sysconfig/nginx
# pidfile: /var/run/nginx.pid
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0
# "Tengine安装目录/sbin/nginx" 如果你的Tengine不是安装在 /usr/local/tengine 下,需要将 /usr/local/tengine 该为你Tengine所安装的目录
nginx="/usr/local/tengine/sbin/nginx"
prog=$(basename $nginx)
# "Tengine安装目录/conf/nginx.conf" 如果你的Tengine不是安装在 /usr/local/tengine 下,需要将 /usr/local/tengine 该为你Tengine所安装的目录
NGINX_CONF_FILE="/usr/local/tengine/conf/nginx.conf"
[ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx
lockfile=/var/lock/subsys/nginx
make_dirs() {
# make required directories
user=`nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
options=`$nginx -V 2>&1 | grep 'configure arguments:'`
for opt in $options; do
if [ `echo $opt | grep '.*-temp-path'` ]; then
value=`echo $opt | cut -d "=" -f 2`
if [ ! -d "$value" ]; then
# echo "creating" $value
mkdir -p $value && chown -R $user $value
fi
fi
done
}
start() {
[ -x $nginx ] || exit 5
[ -f $NGINX_CONF_FILE ] || exit 6
make_dirs
echo -n $"Starting $prog: "
daemon $nginx -c $NGINX_CONF_FILE
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
}
stop() {
echo -n $"Stopping $prog: "
killproc $prog -QUIT
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}
restart() {
configtest || return $?
stop
sleep 1
start
}
reload() {
configtest || return $?
echo -n $"Reloading $prog: "
killproc $nginx -HUP
RETVAL=$?
echo
}
force_reload() {
restart
}
configtest() {
$nginx -t -c $NGINX_CONF_FILE
}
rh_status() {
status $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart|configtest)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
exit 2
esac
Tengine问题排查必备
Tengine打access_log 时机
在接着往下介绍之前,先看下Tengine打access_log的时机,清楚了这个后,再接着往下看会清晰很多。
Tengine的access_log是在Tengine“认为”这个请求结束后才打的,对于正常请求,Tengine会在请求最后一个字节发出后认为请求结束;对于异常请求,当Tengine判断连接超时或者异常断开,无法再发送和接收数据的时候。通常情况下可以认为Tengine在请求结束后随即会打出日志。
如何理解Tengine的"请求最后一个字节发出"
Tengine认为请求最后一个字节发出后,该请求就结束了,其实最后一个字节发出可以理解为最后一串数据发出,这里是“发出”而不是用户收到,指的是将最后一串数据填到协议栈中,只要send 成功返回,Tengine就认为结束了,至于数据是否被客户端收到那就是协议栈和网络上的事情了,Tengine不会去关心。
为什么服务端看到的延时同客户端不一致
1 服务端request_time_msec的含义
要搞清楚这个问题,首先我们要明确Tengine access_log中的“request_time_msec” 字段到底表达了什么含义。
我们先看下官方文档是怎么说的:
$request_timerequest processing time in seconds with a milliseconds resolution; time elapsed between the first bytes were read from the client and the log write after the last bytes were sent to the client.
这个字段表示的是从请求的第一个字节开始到请求最后一个字节发出后所经历的时间。
这里其实包含如下几点信息:
建连的时间是不会被算进去的。如果是HTTPS请求,建连及HTTPS握手的时间都不会被算进去。最后一个字节发出后Tengine认为请求结束,数据仅仅是填在协议栈中,从协议栈Buffer中的数据发送给用户的这段时间是不被算进去的。连接挥手的过程是不会被算进去。
注:从长连接的角度去看,上述1、2、4的时间不被算进去还是好理解的。
2 客户端看到的E2E时间
1节中分析的request_time_msec从服务器端看到的请求E2E时间,而用户看到的时间,假设用户用curl去测试:
time curl https://bucket.oss-cn-hangzhou.aliyuncs.com/object
那么上面4.1节提到的几点不会算到服务器端时间的计算逻辑里的,除了4都会被客户端计算进去。
针对延时不一致,下面我们从HTTP的上传下载,具体分析一下这个延时区别,是否差,差多少。
3 上传类请求延时差异
针对于上传来说,服务器端和客户端看到的延时差异不大,相差一个握手/和最后返回的Header发送回去的时间。
握手到服务器端收到请求首字节2rtt,请求完成后返回的HEADER数据一般不会很大可以塞在1个cwnd内发完,需要一个0.5个rtt,一共是2.5个rtt。如果是长连接,忽略三次握手的话,那么看到的差异为1个rtt。
因此针对上传类请求,客户端和服务器端看到的延时差距为2.5个RT,如果是长连接(非连接上首个请求)的话差异为1个rtt。
4 下载类请求延时差异
关于下载请求的延时差异会稍微复杂一些。上传的情况下,服务器只会有HTTP状态码和一些HTTP Header,通常一个rtt就可以发完。而下载,通常服务器会有较多的数据发送给客户端,Tengine把最后一串数据填在协议栈的Buffer里,如果在Buffer中的数据能在一个rtt内发完,那么同上传类请求一致,否则就会比上传类请求的差异大。至于协议栈Buffer中最后一串数据花多长时间能发送到客户端,这个就不太好估计了,取决于当时的网络状况及当时的拥塞窗口大小,需要具体情况具体分析。
在网络情况不错并且服务器端Buffer配置较小情况下,通常差距不大,但是如果客户端网络差,而服务器端Buffer配置较大的情况下,差距会比较大。比如此时客户端网络比较差,只达到100KB/s, 而服务器端协议栈Buffer配置的较大,为1M,Tengine最后一串数据把1M Buffer填满后Tengine认为请求已经结束了,而实际上客户端在10s之后才完整的收到请求应答数据,才认为结束。大家可以用wget测试一下,分别观察下服务器端和客户端看到的请求时间:
wget your-bucket.oss-cn-hangzhou.aliyuncs.com/tmp/1m-file --limit-rate=10k --debug
注:wget这个限速是在应用层面做的,测试看到的时间差异除了服务端Buffer的原因,还有客户端Buffer的原因,数据到达客户端协议栈,而应用因限速而迟迟不读。
服务器端看到的请求成功和客户端看到的请求成功
接下来分析的都是小概率事件,正常情况下通常不会遇到,主要针对出问题时的分析。
服务器端看到的成功,是服务器端正确处理这个请求,并把数据发送到协议栈后,服务器就会认为请求已经成功。
客户端看到请求成功,是收到服务器端返回的状态码及完整的body后才认为请求成功。
1 access_log看到的“200 OK”
access_log里的状态码,只要请求的header已经发出去,那么状态码就确定了,access_log里面打出来的状态码也是确定的。
如果是上传类请求,access_log里打印出状态码为200,那么请求一定是成功了(但是客户端不一定能感知到这个成功)。
如果是下载类请求,access_log里打印出来的状态码是200,那么请求不一定成功,可能body并未发完请求就异常结束了。
2 写到协议栈里的数据不一定能发送出去
Tengine把数据写到协议栈的Buffer中后,从Tengine的角度来说,可以认为数据已经发往客户端了,但从实际角度来看,数据写到协议栈仅仅是写到协议栈,至于写到协议栈的数据是否能否真正被发送出去,是不一定的。在协议栈数据还没发出去之前可能网络中断了,或者连接被reset 了,都会可能发生。这是造成客户端和服务器端看到有差异的一个主要原因。
access_log中的400、408及499
1 产生原因
400是很普通的错误码,但是在Tengine里也有不是普通“400”的时候,在这里我们只介绍非普通400的情况。
408及499在Tengine中是不会作为错误码返回给用户的(除非upstream返回了),只是Tengine利用了这两个状态码标识请求的一种完成状态。这两种错误码都是和时间相关,但是是不同场景下产生,都是在服务端才能看到的状态,客户端是感知不到的。
400,如果用户请求数据还未发完之前,客户端主动断开或者连接异常断开(如被reset 掉),在Tengine的access_log 中计为400。
499,客户端关闭请求,在proxy 场景下确切的说是客户端先于proxy upstream 返回前断开,Tengine在做proxy 的情况下(fastcgi_pass/proxy_pass 等),同一请求链路上,客户端与Tengine的连接先于Tengine后端返回前断开,此时在Tengine的access_log中计为499的日志。
408,客户端请求超时,确切说客户端发送数据超时,客户端向服务器发送请求数据时中间因某种原因中断了一会,引起服务器端读数据超时,此时在Tengine的access_log中会记为408。注意,发送header和发送body可能会有不同的超时时间。
2 如何复现
400请求数据发完之前提前断开连接,nc建立连接后输入完成Host头部后Ctrl + c断掉,或者发送PUT请求在body没有发送完成之前Ctrl + c掉。
408客户端发送超时,nc建立连接后输入完成Host 头部后等待连接超时,或者在Body 发送完成之前等待连接超时。
499客户端在服务器返回之前提前关闭连接 直接Curl,在服务器返回之前Ctrl + c掉, Tengine在等待upstream返回,此时客户端连接已经断开。可能你的手速没服务端处理的快,可以找一些服务器处理相对耗时的请求来复现,比如OSS的大图片处理。
注:用public-read-write权限的bucket进行测试。
3 是否异常
一般正常情况下,400、408、499这三个状态码出现的会比较少,日志中偶尔零星出现一些也不是什么大问题,如果大量出现,那就可能出问题了。
假如日志中大量出现400,如果请求的request_time_msec很小,优先排查是否是客户端问题,如果这个时间很大,请检查服务器压力是否过大,是否有hang住情况。如果服务器端hang 住,请求在发送的时候数据堆在Tengine里,服务器端长时间不读,造成客户端超时断开连接,此时Tengine会产生大量因客户端发送超时而提前断连造成的400。
如果日志中大量出现499,如果请求的request_time_msec很小(ms级别),需要排查是否是客户端问题,如果这个时间很大,需要从两个方向排查:
检查用户请求,是否后端处理确实需要很长时间,而客户端设置的超时时间又很短,此时需要客户端调整超时时间,否则客户端的重试可能会导致雪崩(如果没有限流的话)。
检查服务器是否压力过大,是否有hang住的情况,如果后端持续不返回客户端提前断开的话就会造成大量499。
这三个状态码出现,多多少少都是有些异常的,通常情况下,我们需要快速判断是服务器端的异常还是客户端的异常,从而快速定位问题。