流量低峰也烦人-lighttpd耗时长问题追查

简介:

结论

如果你用lighttpd1.5(以下lighttpd均指1.5)做静态文件服务器,或者你虽然用lighttpd处理php请求,但是用到$PHYSICAL作为mod_proxy_core的条件, 且某个时候你的单机流量很低(几个/s), 或许你也有类似的问题,但是影响程度或许不会引起你的注意!
1.Lighttpd的mod_proxy_core不建议用$PHYSICAL作为条件;
2.Lighttpd的stat cache机制没有节省任何开销;
3.Lighttpd子线程和主线程通过管道+epoll的通信机制,存在event丢失问题;

 

现象

用户反馈凌晨的时候访问百度某页面,某些模块的数据出不来;

其它依赖于我们的前端接口的产品线反馈访问时间有时候超过1s;

我们自己的QA环境偶尔也会出现请求超过1s的问题;

因此我们打开lighttpd的日志的%D配置,打印ms级别的处理时间,发现晚上1点到凌晨8点有很多处理时间超过1s的请求,500ms以上的也有很多,并且流量越低, 比例越大;

1点-8点是流量低峰时期,流量越低,性能越差

这个现象每到高峰时期就正常了,因为是流量低峰才会出现这样的慢请求,占总比例非常之少,对整体的性能和稳定性影响极小,所以性能和稳定性监控报表中没有发现这个问题。

追查过程

由于这个页面对性能要求相对比较严格,虽然性能和稳定性衡量数据已经非常好, 但是这个问题一直是一个阴影,不解决终归不爽,所以开始了下面的追查过程:

由于处理路径是lighttpd->php-cgi->框架+逻辑, 首先的怀疑是框架+逻辑问题,但是通过查看php的处理时间,流量低峰高峰都非常正常,极少超过100ms,所以排除是框架+逻辑问题;那究竟是php-cgi的问题还是lighttpd本身的问题呢?为了排除php-cgi的问题,我们尝试了从线下复现这个问题,看访问静态文件是否也有类似的问题。但是悲剧的是线下就是复现不了这个问题。那再对比和线上环境的不同,会不会是先要经过一段时间的大流量,然后再小流量才会出现这个问题呢?于是用ab 30qps压了2个小时后停止,然后再手动访问试了一下,果然如此!通过访问静态文件,发现静态文件也是如此,处理时间超过1s, 因此基本排除php-cgi本身的原因,问题应该是出在lighttpd本身

通过这个线下实验,我们还发现了如下规律:

1.前期用ab压的时间不定,有时候压2个小时后然后低流量访问还不会出现这个问题,有一定的随机性;

2.手动低流量访问的时候,并不是每次都慢,对同一个url, 紧接着的两次访问(访问第一次后马上访问第二次),第一次会慢,但第二次会很快,然后再过个1-2s钟再访问第三次,又会很慢;

3.手动请求的时候,如果慢,总是慢1s, 但是线上有慢1s的,也有不是慢1s的,最多1s;

4.重启lighttpd后,所有请求会恢复正常,需要重新压;

于是产生两个最大的疑问, lighttpd在公司使用这么广,处理静态资源和php请求的都有用到, 别的产品线为什么不报? 为什么是1s? 对于第一个疑问,觉得可能是因为这种情况影响的平均性能非常少,可能其他产品线不会这么敏感,或者是流量低峰的单机请求量也很高,没有频繁的触发这个问题,这个时候还对比了其它产品线的lighttpd.conf, 这个时候是没有发现有什么问题的。于是就从第二个问题开始着手追查:为什么是1s?

带着问题,开始读lighttpd的源代码了。。。

该页面lighttpd event-handler用的是linux-sysepoll;

首先发现lighttpd代码中有各个地方和1s有关的代码:

源文件server.c

第一个1000ms是lighttpd的epoll的超时时间,也就是如果没有任何句柄有事件发生,epoll最多等待1000ms后即会返回,如果有事件发生,epoll会马上返回有事件发生的所有句柄,然后lighttpd会处理joblist中已经准备好的connection,重新进入状态机;第二个1s是lighttpd有个一个trigger机制,每隔1s会触发一次SIGALRM, 然后lighttpd处理超时的请求,清理stat_cache等;因此,通过修改这两个1s, 发现当改成fdevent_poll(srv->ev, 500);  后,慢请求都变成500了, 所以慢请求是因为的那个connection已经放在joblist, 但是没有成功触发epoll返回, epoll只有等待超时后返回,该connection才会被处理,这也就解释了为什么流量高峰的时候没有这个问题,因为高峰的时候epoll返回得相当频繁,也可以解释为什么线上的慢请求慢100,200ms的都有,但是最多不超过1s了,线下手工访问的时候总是慢1s, 这也是因为每秒的请求量的原因, 这其实也是类似epoll这种异步事件处理模型所带来的通病,用延迟换吞吐量;

问题进一步,那为什么重启lighttpd后,就算流量低也没问题呢,所以进一步看代码,通过把lighttpd所有debug日志打开,发现这个问题和lighttpd­的stat cache机制有关, 为了避免反复的调用stat来获取文件信息,lighttpd用了一个全局的hash表保存了每个物理路径的所对应文件的stat结果,这个机制和server. stat-cache-engine这个配置有关,我们用的默认配置“simple”, cache结果会缓存1s, 如果没有命中或者失效,lighttpd会把这个stat任务放在一个队列里面, 然后告诉状态机HANDLER_WAIT_FOR_EVENT, 暂时退出状态机,由另外几个线程来异步处理这个stat任务,处理完这个任务后,会重新把这个任务关联的connection加到joblist_queue中,然后通过管道通知主线程,让epoll返回, 相关代码如下:

源文件joblist.c

上面的代码可以看出, lighttpd是通过判断一个全局的变量srv->did_wakeup,如果是0,就把它改成1,然后往这个管道发生一个空格字符串,触发主线程的epoll返回,如果这个变量不是0,就不会通知主进程。

下面的代码是主线程epoll返回后,和这个管道句柄对于的处理函数,可以看出主线程又把srv->did_wakeup初始化成0了, 这样下次还会wakeup主线程;

源文件server.c

这就引发一个思考,如果因为某种原因srv->did_wakeup被修改成1了,但是主线程由于某种原因没有收到这个write事件,导致srv->did_wakeup没有被改成0,那不是后面都不会通过管道通知主线程了,为了证明这个假设,我加了下trace代码,发现确实是这样的,设置srv->did_wakeup =1 做了2456次,但是设置srv->did_wakeup = 0只做了2455次,只差一次,并且后续都没有做这个操作了,另外还发现子线程每次write管道都是成功的,但是最后一次主线程没有收到这个事件,至于为什么没有收到,就没有继续查了。

但是,还是有个疑问,我访问的php请求,lighttpd应该把请求路径发给php-fpm,自己应该不关心物理路径的啊,就不用搞什么stat cache吧,这个时候想起了当时为了解决某扩展能够正确获取到PATH_INFO的问题,把mod_proxy_core的条件配置从$HTTP["url"] =~ “\.php$”改成了$PHYSICAL["existing-path"] =~ “\.php$”。马上修改配置,再测试,问题果然没有了, 通过查看lighttpd代码,发现如果配置成$PHYSICAL这种形式,会导致lighttpd去stat这个物理文件,这个操作在mod_proxy_core之前执行,如果用$HTTP[“url”]就不会引发这个问题,到此,一切都清楚了,我看到的其它老的产品线都是配的$HTTP[“url”], 只有少数的几个产品线不是用的$HTTP[“url”],也只是单机流量非常低的情况才会出现这个问题,很难会让人觉察到!

另外,在追查问题的过程中还发现lighttpd stat_cache机制的两个问题,第一个问题就是处理stat任务的子线程,在stat之后,并没有更新这个stat cache的状态为FINISHED, 下次来查的时候还是没有命中cache, 等于是白干了。如下代码所示:

源文件stat_cache.c

第二个问题是就是命中了stat cache, 其实还是需要调用stat判断改cache有没有过期, 所以觉得stat cache本身这个机制也是白搞了,比较没有节省stat的开销,还多搞了,如下代码所示:

源文件stat_cache.c

遗留问题

子线程和主线程通过管道+epoll的机制来通信,为什么会有一定的概率失败呢?write管道其实是成功的,由于精力有限,这个问题没有继续追查;

管道其实是成功的,由于精力有限,这个问题没有继续追查;

对lighttpd配置的建议

如果利用mod_proxy_core做php处理,还是用$HTTP[“url”]做条件吧,例如:

$HTTP["url"] =~ “\.php” {

proxy-core.balancer = “static”

proxy-core.protocol = “fastcgi”

proxy-core.allow-x-sendfile = “enable”

proxy-core.backends = ( “unix:/home/super/php/var/php-cgi.sock” )

proxy-core.rewrite-request = (

“_pathinfo” => ( “(/[^\?]*)/index\.php(/[^\?]*)” => “$2″ ),

“_scriptname” => ( “(.*\.php)” => “$1″ )

)

注意,为了让php-cgi取到正确的PATH_IFNO, 请注意添加“_pathinfo” rewrite规则!

对lighttpd代码优化的建议

1.在每隔1s的trigger操作中,新增一个操作:将srv->did_wakeup重置为0,防止这个变量变成1以后永远便不会0的情况发生;
2.stat_cache_thread处理完stat_job之后,要更新源stat_cache_entry的状态为FINISHED, 否则就白搞了;
3.命中stat cache后,不用再通过stat判断该cache是不是最新的,因为最多缓存1s钟;

 

 


关注百度技术沙龙

 


本文转自百度技术51CTO博客,原文链接:http://blog.51cto.com/baidutech/779536,如需转载请自行联系原作者

相关文章
|
网络协议 开发工具
网络编程之 listen()函数的使用与三次握手的理解
listen()函数 在进入我们的函数讲解前大家再回顾一下编写服务器端的流程
381 0
|
11月前
|
安全 算法 API
OpenSSL支持哪些加密算法?
【10月更文挑战第4天】OpenSSL支持哪些加密算法?
814 5
|
8月前
|
Java 关系型数据库 数据库连接
mybatis中的useGeneratedKeys和keyProperty
在 MyBatis 中,`useGeneratedKeys` 和 `keyProperty` 是用于处理数据库自动生成主键的关键配置。通过这些配置,可以方便地获取和使用数据库生成的主键值,提高开发效率和代码可读性。确保正确配置和使用这两个属性,可以在应用程序中高效地进行数据库操作。
312 25
|
10月前
|
安全 算法 网络安全
一张图就把HTTPS工作原理讲明白了!
【10月更文挑战第31天】
783 1
一张图就把HTTPS工作原理讲明白了!
|
Shell Windows
Shell test [] 命令:条件判断的艺术
`test` 命令在Shell脚本中用于条件检测,涉及数值、字符串和文件比较。例如,`test $a -eq $b` 检查两个数是否相等;`-e` 检查文件是否存在;`-w` 检查文件是否可写。数值比较不支持 `>=` 和 `<=`,需用 `-ge` 和 `-le`。字符串比较时注意空值,使用双引号。逻辑运算包括 `-a`(与)、`-o`(或)和 `!`(非)。文件类型和权限检测也是`test`的重要用途。
218 0
|
安全 网络协议 Linux
|
消息中间件 数据安全/隐私保护
MQTT微消息队列服务器连接报错:Error: Connection refused: Not authorized
使用MQTTX工具进行测试时,通过AccessKey创建了Client ID的用户名和密码。配置了公网接入点及端口1883,但尝试连接时出现错误。已附上工具截图:![](https://ucc.alicdn.com/pic/developer-ecology/3byii5uar64gg_36327474e991439da422f38c450ef153.png)。确认过用户名、密码和Client ID无误,问题仍未解决,期待回复!
|
存储 算法 数据可视化
MySQL数据库 -- 索引结构 (B+ tree 与 Hash)
索引(index)是帮助MySQL高效获取数据的数据结构 , 在Mysql中有两个最常用的索引 -- B+tree索引 和 Hash索引 B-Tree(B树)是一种多叉路平衡查找树,相对于二叉树,B树每个节点可以有多个分支 哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中
581 0
|
存储 SQL Cloud Native
离在线一体化云原生数仓发展思考
OLAP是一个很卷的赛道,创业公司也众多。基于笔者10+年的大数据与数据仓库的工作经验,就目前的数据仓库主流趋势:离在线一体化、引擎一体化、云原生化等写一些思考,抛砖引玉。
516 57