云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

亚马逊云_手机服务器软件_企业级

小七 141 0

我们如何每天攀登nginx拯救世界54年

@Cloudflare团队刚刚推动了一项改进,显著提高了我们网络的性能,特别是对于速度特别慢的异常请求。快多少?我们估计我们每天节省了大约54年的互联网时间,否则我们都会等待网站加载。-马修普林斯(@eastdakota)2018年6月28日1000万个网站、应用程序和API使用Cloudflare为用户提供速度提升。在高峰时期,我们在151个数据中心每秒处理超过1000万个请求。多年来,我们对NGINX的版本做了很多修改,以应对我们的增长。这篇博文是关于其中一个的。NGINX的工作原理NGINX是使用事件循环来解决c1k问题的一种流行程序。每当网络事件(一个新的连接、一个请求或一个我们可以发送更多数据的通知等)到来时,NGINX就会唤醒,处理事件,然后返回去做它需要做的任何事情(这可能是在处理其他事件)。当一个事件到达时,与该事件相关的数据已经准备就绪,这使得NGINX无需等待就可以高效地同时处理多个请求。num_events=epoll_wait(epfd,/*returned=*/events,events_len,/*timeout=*/-1);//事件是活动事件的列表//handle事件[0]:传入请求GET event[1]:发送响应以获取例如,以下是从文件描述符读取数据的代码://我们在fd上有一个read事件而(buf_len>0){ssize_t n=读取(fd,buf,buf_len);如果(n0){返回buf_len;}这意味着,如果一个事件处理程序需要从磁盘读取,它将阻塞事件循环,直到整个读取完成,并且后续事件处理程序被延迟。这对于大多数工作负载来说都很好,因为从磁盘读取数据通常足够快,而且比等待数据包从网络到达更容易预测。尤其是现在每个人都有一个SSD,而且我们的缓存磁盘都是SSD。现代固态硬盘的延迟非常低,通常是10微秒。除此之外,我们可以用多个工作进程运行NGINX,这样慢的事件处理程序不会阻塞其他进程中的请求。大多数时候,我们可以依赖NGINX的事件处理来快速高效地服务请求。SSD性能:标签上的内容并不总是如此正如你可能已经猜到的,这些乐观的假设并不总是正确的。如果每次读取总是需要50µs,那么在4KB块中读取0.19MB应该只需要2ms(我们在更大的块中读取)。但我们自己的测量显示,我们到达第一个字节的时间有时要差得多,特别是在第99和第999个百分位。换言之,每100次(或每1000次)读取中最慢的读取通常需要更长的时间。固态硬盘速度很快,但也非常复杂。它们内部是排队和重新排序I/O的计算机,还执行各种后台任务,如垃圾收集和碎片整理。偶尔,一个请求的速度会慢到足以产生影响。我的同事Ivan Babrou运行了一些I/O基准测试,发现读取峰值最多为1秒。此外,我们的一些固态硬盘有更多的性能异常值。未来,我们将考虑购买固态硬盘时的性能一致性,但同时我们需要为现有硬件提供解决方案。使用SO峎REUSEPORT均匀分布负载一个单独的缓慢响应是很难避免的,但是我们真正不想要的是1秒的I/O阻塞1000个我们在同一秒内收到的请求。从概念上讲,NGINX可以并行处理许多请求,但一次只运行一个事件处理程序。所以我增加了一个度量标准:gettimeofday(&start,NULL);num_events=epoll_wait(epfd,/*returned=*/events,events_len,/*timeout=*/-1);//事件是活动事件的列表//handle事件[0]:传入请求GET(&event_start_句柄,NULL);//handle event[1]:发送响应以获取(&event_start_handle,&start,&event_loop_blocked);事件循环阻塞的p99超过了我们TTFB的50%。也就是说,为一个请求提供服务的一半时间是由于事件循环被其他请求阻塞的结果。event_loop_blocked只测量大约一半的阻塞(因为不测量对epoll_wait()的延迟调用),因此阻塞时间的实际比率要高得多。我们的每台机器运行NGINX时有15个工作进程,这意味着一个缓慢的I/O只会阻塞高达6%的请求。然而,事件的分布并不均匀,最高层的工作线程会接收11%的请求(或者是预期的两倍)。从而解决了不均匀分布问题。Marek Majkowski以前曾在其他NGINX实例的上下文中写过这种缺点,但这种缺点在我们的例子中并不适用,因为我们缓存进程中的上游连接是长期的,因此打开连接时稍高的延迟可以忽略不计。这个单一的配置更改使SO峎u重用报告的峰值p99提高了33%。将read()移动到线程池:不是一个好办法解决方法是使read()不阻塞。事实上,这是一个在上游NGINX中实现的特性!使用以下配置时,read()和write()在线程池中完成,不会阻塞事件循环:aio线程;爱你写在上面;然而,当我们测试这一点,而不是33x的响应时间改进,我们实际上看到p99略有增加。差异在误差范围内,但我们对结果非常失望,并在一段时间内停止了这一选择。我们没有看到NGINX所看到的改进水平有几个原因。在他们的测试中,他们使用200个并发连接请求4MB大小的文件,这些文件驻留在旋转的磁盘上。旋转磁盘会增加I/O延迟,因此,有助于延迟的优化将产生更显著的效果。我们还主要关注p99(和p999)的性能。有助于提高平均性能的解决方案不一定有助于处理异常值。最后,在我们的环境中,典型的文件大小要小得多。90%的缓存命中是针对小于60KB的文件。较小的文件意味着更少的机会阻止(我们通常在2次读取中读取整个文件)。如果我们看看缓存命中必须执行的磁盘I/O://我们接到一个请求https://example.com哪个具有缓存键0xCAFEBEEFfd=打开("/cache/prefix/dir/EF/BE/CAFEBEEF",仅限O\RDONLY);//元数据和头的最大读取量为32KB//如果"aio线程"处于打开状态,则在线程池中完成读取(fd,buf,32*1024);32KB不是一个静态数字,如果头很小,我们只需要读取4KB(我们不使用直接IO,所以内核将四舍五入到4KB)。open()看似无害,但实际上不是免费的。内核至少需要检查文件是否存在,以及调用进程是否有打开它的权限。为此,它必须找到/cache/prefix/dir/EF/BE/cafebef的索引节点,而要做到这一点,它必须在/cache/prefix/dir/EF/BE/中查找CAFEBEEF。长话短说,在最坏的情况下,内核必须执行以下查找:/缓存/缓存/前缀/缓存/前缀/目录/缓存/前缀/dir/EF/缓存/前缀/dir/EF/BE/缓存/前缀/dir/EF/BE/cafebef这是open()完成的6次独立读取,而read()只完成了1次读取!幸运的是,大多数时候查找都是由dentry缓存提供服务的,不需要访问ssd。但显然,在线程池中完成read()仅仅是问题的一半。妙招:线程池中的非阻塞open()所以我修改了NGINX,让它在线程池中执行open()的大部分操作,这样它就不会阻塞事件循环。以及结果(非阻塞打开和非阻塞读取):6月26日,我们在5个最繁忙的数据中心部署了我们的更改,第二天在全球范围内推出。p99总峰值TTFB提高了6倍。事实上,把每秒处理800万个请求的时间加起来,我们每天为互联网节省了54年的等待时间。我们已经把工作交给上游了。有兴趣的人可以跟进。我们的事件循环处理仍然不是完全非阻塞的。特别是,当我们第一次缓存一个文件(open(O_CREAT)和rename())或执行重新验证更新时,我们仍然会阻止。然而,与缓存命中相比,这是很少见的。在将来,我们将考虑将其移出事件循环,以进一步改善p99延迟。结论NGINX是一个功能强大的平台,但是在linux上扩展极高的I/O负载是一个挑战。上游NGINX可以在不同的线程中卸载读取,但是在我们的规模下,我们通常需要更进一步。如果您对解决具有挑战性的绩效问题感到兴奋,请申请加入我们在旧金山、伦敦、奥斯汀或香槟的团队。