深度解析Tengine的调试与资源监控方法论

摘要: Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,提供更强大的流量负载均衡能力、全站HTTPS服务、安全防攻击、链路追踪等众多高级特性。团队的核心成员来自于淘宝、搜狗等互联网企业,从2011年12月开始,Tengine成为一个开源项目,团队在积极地开发和维护着它,最终目标是打造一个高效、稳定、安全、易用的Web平台。html

Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,提供更强大的流量负载均衡能力、全站HTTPS服务、安全防攻击、链路追踪等众多高级特性。团队的核心成员来自于淘宝、搜狗等互联网企业,从2011年12月开始,Tengine成为一个开源项目,团队在积极地开发和维护着它,最终目标是打造一个高效、稳定、安全、易用的Web平台。前端

阿里云CDN如今服务超过24万家客户,Tengine做为接入层提供高性能Web Server服务,是CDN系统最核心的组件之一。不管是来自阿里集团内部仍是外部客户的流量服务,几乎都是由Tengine承载。能够绝不夸张地说,Tengine的服务质量直接影响着国内外无数大中小型Web站点的服务质量和业务存活,因此,维护Tengine的稳定性一直是咱们团队的最高优先级目标之一。通过了多年淘宝、天猫等大型网站双十一活动的洗礼,Tengine的性能和稳定性已经获得了很好的验证。python

有一句俗语:“上帝说要有光,因而便有了光。“阿里云高级开发工程师墨飏说,“Tengine在作工具化的时候,也基本沿袭了这样的思路。在作开发以前,咱们会系统性地思考:咱们须要面对什么样的场景,会碰到什么样的问题,须要怎样的调试技巧和工具,是否能够解决更多此类问题,因而,咱们的工具便会在这样的思路下逐渐成型和完善。同时,在服务客户的过程当中,咱们也会遇到各类新场景新问题,为了定位和解决问题,咱们也会针对性地提出解决方案,沉淀出更多调试技巧和工具。做为一线开发团队,咱们一路走来积累了很是多调试技巧、工具化的经验。”nginx

本文由阿里云CDN团队的研发同窗笑臣和墨飏带来,从Tengine的内存调试、核心结构、upstream、coredump四个部分展开,为你们整理和分享一些实践经验。git

内存调试——精准定位问题
Tengine做为C语言开发的应用,在内存的使用中会碰到一些问题,第一部分将重点介绍内存调试方面的相关内容。github

从下图能够清晰的看出,Tengine内存分布能够从三个维度来理解:底层实现、抽象层、应用层。后端

图片描述
1、底层实现
Tengine底层实现依赖操做系统的内存分配机制,常见的内存分配器包括jemalloc(FreeBSD)、ptmalloc(glic)、tcmalloc(Google),luajit则使用内置的dlmalloc库。Tengine在每一个链接accept后会malloc一块内存,做为整个链接生命周期内的内存池, 当HTTP请求到达的时候,又会malloc一块当前请求阶段的内存池, 所以对malloc的分配速度有必定的依赖关系。jemalloc的性能是ptmalloc的两倍以上,咱们在使用Tengine的时候默认采用jemalloc。jemalloc在追踪实际内存分配时可使用“malloc_stats_print”来查看内部细节,帮助定位内存泄露等问题。
图片描述
2、nginx pool调试
在底层内存分配工具没法定位问题时,咱们须要从抽象层分析出了什么问题。
Tengine做为nginx 的fork,在使用nginx pool方面与官方nginx基本没什么区别,它的内存池管理机制在HTTP请求的任一阶段均可能被调用来分配内存,咱们能够从内存分配的真实函数调用来统计内存分配的占用量、历史数量、当前数量、large alloc等。mod_debug_pool已经在Tengine社区开源,有兴趣能够自行查阅文档,它的原理是经过hook ngx_create_pool函数来记录__func__/__LINE__,在须要排查问题时能够展现历史数据,从抽象层定位内存泄露等问题。
图片描述安全

3、lua内存统计
lua/luajit是另外一个很是成熟的开源项目,在nginx生态系统中,lua-nginx-module容许lua/luajit以虚拟机形式内嵌到nginx提供强大的脚本能力,咱们在阿里云CDN海量业务开发中大量使用到lua/luajit。在调试luajit内存占用时,能够经过collectgarbage来展现总内存占用量,经过扫描gc object,能够统计global_State中全部gc对象,OpenResty社区也提供了GDB工具来输出gc对象的数量和内存占用。
图片描述服务器

4、共享内存调试
Tengine是多进程服务模型,其进程间的通讯主要依赖操做系统共享内存机制,随着业务的发展共享内存的使用频率愈来愈高,如何去定位、调试共享内存,是一个比较严峻的问题,且业内缺乏相关工具。咱们在调试共享内存问题时,沉淀和开源了mod_slab_stat工具,在Tengine社区能够查阅详细的文档,该工具统计和展现了每一个zone的内存分配细节,包括page、slab和alloc num等,突出业务通讯内容块分布状况等。该工具适用于ngx shm,基于ngx slab的stub stats模块是个例外。
图片描述网络

以上,从底层实现、抽象层、应用层三个维度,能够定位到绝大部分Tengine系统和业务的内存问题,包括一些内存的调试技巧。

核心结构——调试与解决故障
Tengine做为高性能服务器被普遍应用,它的核心框架和核心数据结构决定了其承载服务的质量。针对所遇到的CDN用户上报故障的工单,好比请求异常、访问慢等,如何从Tengine核心结构去调试、定位和解决问题,是本部分要介绍的内容。

Tengine主要的核心结构包括链接、请求和计时器等。链接承载着请求,以链接池的形式提供前端、后端网络服务,其异步事件驱动的特性是Tengine高性能网络服务的基石,而其事件模型又和计时器牢牢关联,这些核心结构组成了Tengine的核心框架。

要调试和定位Tengine网络问题,须要从链接、请求、计时器等多角度思考,从读写请求状态、解析请求阶段、应答输出阶段、建连等多维度采集,从而获得更详细的全局监控数据。

图片描述

Tengine在nginx stub status工具基础上,开发了tsar工具(已开源),来展现更细粒度的历史资源监控,包括应用层QPS、HTTPS和网络层的accept、读写等待等。基于reqstatus的域名级监控,能够针对任意粒度域名级来统计请求数、链接数和5xx、4xx等状态码数量,还包括链接的复用状况等。

图片描述

有了tsar和reqstatus工具,咱们能够从全局来分析Tengine系统的服务和性能状态,可是,这够了么?CDN系统中,咱们会遇到一些问题,好比:为啥tengine/nginx访问慢?是由于客户端慢、后端慢、仍是nginx自己hang住?如何去衡量“慢“?咱们须要对请求时间进一步细分。

Tengine新增了responsefirstbytetime、upstream_response_time、writewaittime、upstream_read_wait_time等变量来记录从请求进入到响应结束整个请求流程中的包括不限于应答首字节、后端应答首字节、socket读写等待时间总和等关键指标。

图片描述

Tengine在衡量和监控请求卡顿时,将events cycle内event平均处理时间和events cycle内timer平均处理时间综合来定位单cycle平均耗时长的异常,常见的问题如同步IO(磁盘负载高时写log卡顿)、CPU密集型执行流(lua实现的CPU密集型逻辑)。

有些时候,咱们没法从全局资源监控角度定位问题,这时候能够从ngx_cycles->connections[]来查询当前执行的请求或链接结构信息,经过gdb脚原本扫描worker内当前链接信息,查看和调试是否有请求堆积或链接泄露等问题。mod_debug_conn(待开源)工具是上述调试技巧的沉淀,它还能够帮助咱们查看请求/链接的内存占用、分析链接和请求上的各种信息。

图片描述

同时,Tengine的计时器(timer)在异步业务场景中有重要做用,经过扫描计时器红黑树,分析每一个event timer,咱们能够调试和定位异步操做中的问题。有时候咱们在平滑升级tengine服务时,worker一直处于shutting down状态没法退出,其实即是由于一直存在timer致使的。也能够经过hook ngx.timer.at函数来统计lua-nginx-module中的timer caller次数,解决timer超限的报错等。

回源监控
CDN是内容分发网络的简称,其分发的内容来自用户源站,负责回源的upstream模块是Tengine最重要组成部分之一,使Tengine跨越单机的限制,完成网络数据的接收、处理和转发。这部分主要介绍upsteam的一些调试技巧和回源资源监控的内容,以及相应的实例分享。

在上面的章节中,咱们已经分析过如何依靠Tengine提供的关键指标来衡量和监控请求卡顿,那么,咱们一样能够提出问题:如何去分析请求代理或回源失败?是由于客户端主动断连、后端异常仍是请求超时?如何去分析真实的缘由,从而实时监控回源失败,这也是常常困扰CDN用户和开发人员的难题。

从Tengine的请求处理流程分析,客户端在完成TCP三次握手后,比较常见的错误是:一、客户端异常断开链接;二、客户端主动断开链接;三、等待读写客户端超时。在Tengine读取请求内容后,解析客户端请求失败如请求行/请求头等协议出错,在upstream模块回源时,也可能发生链接后端失败、后端异常断开链接、后端主动断开链接、等待读写后端超时、解析后端应答头失败等错误,咱们能够从Tengine的错误日志查看对应的报错信息,在此基础上,咱们从ngx_http_upstream_connect、ngx_http_upstream_send_request、ngx_http_upstream_process_header、ngx_http_upstream_process_body_in_memory等回源关键函数切入,提供error flag来监控真实的客户端/回源失败缘由,将错误统计输出至访问日志、reqstatus工具等,关联和分析域名/请求级别的回源失败问题,保障CDN服务的稳定性。

图片描述

如今,咱们有了回源的关键指标变量和error flag,能够依靠一些调试技巧来分享一个upstream问题实例。如图所示,当客户端请求经过Tengine upstream回源时,咱们可能碰到这样的问题:

图片描述

读源站,读满buffer
发送buffer size数据给客户端,全发完了
循环上述两步骤…
继续读源站,读出若干数据,且源站已读完
发送部分给客户端,未发完(客户端卡或者其余缘由)
再次读upstream,读出again(显然,由于以前源站数据读完了)
继续发送数据给客户端,未发完(客户端卡或者其余缘由)
upstream回源先超时了,这个时候$error_flag代表等待读源站超时,可是事实是这样吗?
咱们提供了完整的复现方法,如图所示:
图片描述

站在上帝视角,很明显能够看到实际上是客户端问题,可是回源采集的关键指标:errorflag、upstream_read_wait_time、$write_wait_time,却告诉咱们是源站出错致使的问题,数据不会欺骗咱们,那么问题到底在哪里?

上述调试的一些技巧和回源监控,帮助咱们理解upstream模块的原理:
一、upstream有2个计时器,前端的和后端的;
二、Tengine/Nginx即便写客户端写不进去,只要proxy buf没满也会尝试读后端,若是后端数据读完了,会读出EAGAIN;
三、后端计时器较短先超时了,关闭请求,此时每每认为后端出错,真实状况是客户端出错;

图片描述

这个特殊实例,在某些场景下,甚至在不少的生成环境中,都是比较常见但却每每被忽视的问题,在帮助提高用户体验和服务质量的目标下,即便是nginx核心代码不易发现的bug,在完善的调试工具和回源监控下,同样无所遁形。有兴趣的同窗能够搜索nginx邮件列表来详细了解问题背景,虽然nginx 官方由于一些缘由没有合入该patch,咱们在Tengine中也作了单独的优化策略。

Coredump
coredump是Tengine这类C开发的应用程序比较常见的问题,随着业务的迅猛发展,coredump每每会变得愈来愈大,甚至愈来愈频繁,咱们从空间、数量、自动分析等角度层层递进来优化Tengine的coredump。

“cat /proc/PID/coredump_filter”这行指令帮助咱们了解Linux操做系统coredump机制所支持的内存形态,包括私有内存、共享内存等。咱们在使用Tengine时能够去除共享内存,下降coredump文件大小。同时,也能够限制必定时间内crash写coredump文件的次数,防止磁盘IO太高或磁盘空间被占满,Tengine提供了“worker_core_limit”指令(待开源)来限制至分钟/小时/天级别。

图片描述
如今咱们已经从空间和数量角度优化了coredump,方便了调试。那么是否能够自动化完成分析,提高定位问题的速度?答案是确定的。

咱们经过gdb python脚原本自动分析coredump文件,从中提取获得触发问题的请求的host、URL、headers等,有了这些信息,就能够不断复现来快速调试和定位问题。可是有时候,coredump栈并不能告诉咱们完整的现场,而在crash时,Tengine丢失了日志中的请求信息,咱们将这些信息记录在环形内存中,经过coredump文件将环形缓冲数据输出到文件,环环相扣,完整的现场,真相只有一个。
图片描述
原理:http://nginx.org/en/docs/ngx_...

过后诸葛只能查漏补缺,咱们须要提早发现问题,实时流量拷贝工具应运而生。http_copy是Tengine的扩展模块,能够实时放大转发本机的流量(HTTP请求),能够配置放大流量到指定地址和端口,也可设置放大的倍数和并发量等,更能够经过源站健康检查来自动关停流量,避免过大流量对Tengine和源站的正常服务形成影响。
图片描述

在流量压测/流量拷贝时,不须要真实返回响应的内容,减小带宽消耗,drop_traffic工具添加body filter直接丢弃数据,或者截获c->send_chain/c->send函数直接丢弃数据,在调试时发现问题而不产生问题。

以上就是阿里云CDN团队对于Tengine的内存调试与资源监控方面的一些实践经验,但愿对于正在使用开源Tengine的同窗有一些帮助。

Tengine官网:http://tengine.taobao.org
Tengine Github:https://github.com/alibaba/te...
阿里云CDN:https://www.aliyun.com/produc...
最后,阿里云CDN团队正在招聘志同道合的伙伴,欢迎加入~
图片描述

原文连接

相关文章
相关标签/搜索