首页接口优化

问题概述

在项目中写的一个下单程序的首页接口,涉及到查询商品分组、查询各个分组下的商品、查询商品的促销信息等多个环节的操作,最后在项目压测环节发现性能与预期相差巨大…单机TPS只有10,而目标至少100以上…

需要对首页接口进行优化。

问题梳理

通过分析,该接口性能瓶颈主要在访问数据库的次数太多上面,所以本次优化的重点也在于如何降低数据库访问次数。

简单反思了一下为什么当初会写出一个性能这么差的接口( ̄∇ ̄):

  • 先组织数据结构后去数据库查询数据
  • 先组织数据结构后去其他方法/RPC查询数据
  • 一句话概括,“将拼装数据的过程放到了查询数据之前进行”导致了这个问题

举个例子:

第一遍写的接口中,到处都充斥着这种例子。

其实就是党哥说的那句:“不要在foreach中进行findRow、insert操作”,事实上,最好也不要在foreach中进行rpc调用、调用进行数据库操作的方法,这些都会让数据库访问次数增加。

道理都懂,但是没遇到问题之前总是不能感同身受。

代码优化

减少访问数据库次数,组装的操作尽量在内存中进行操作( SQL 能用 in 操作尽量用 in 操作),查询的时候尽量批量操作(包括RPC)

坚决不在 foreach 中进行 insert、findRow 操作。

举个例子:

优化前,单次接口请求访问数据库250次以上(随着分类、商品、促销信息的增加访问数据库还会增加)。

优化后,单次接口请求访问数据库11次(不变)。

在开发环境中的性能对比:

这是优化前的单次访问响应时间:

36ms

这是优化后的单次访问响应时间:

5ms

上线之后,从log日志看,代码时间从1000ms降低到了30ms左右,问题来了,可是QA反馈的结果是响应时间依旧很长,优化有限

那么问题来了,问题出在哪里了?问题出在nginx上了。

nginx 参数调优

在开始讲具体内容之前,先给出一组数据

代码优化前: 代码优化后:
线下:代码时间 36ms 线下:代码时间 5ms 性能提高了7倍
线上:代码时间 1000ms 线上:代码时间 30ms 性能提高了30倍
线上:响应时间 1500ms 线上:响应时间 450ms 性能提高了2~3倍

看到了吗,代码优化后,影响时间只不过从1000多ms降低到了500左右ms,所以QA测的TPS从10个左右上升到了20个左右,所以就是优化效果有限,那么问题出在哪里呢?出在了nginx上,nginx处理时间过长,下面讲讲平哥修改的了哪些nginx参数,以及每个参数的作用。

平哥改了nginx.conf的参数,如下图所示:

fastcgi_buffer_size

Syntax: fastcgi_buffer_size size;
Default: fastcgi_buffer_size 4k 8k;
Context: http, server, location

Sets the size of the buffer used for reading the first part of the response received from the FastCGI server.

This part usually contains a small response header. By default, the buffer size is equal to one memory page.

This is either 4K or 8K, depending on a platform. It can be made smaller, however.

后端服务器的响应头会放到fastcgi_buffer_size当中,这个大小默认等于fastcgi_buffers当中的设置单个缓冲区的大小。

fastcgi_buffer_size只是响应头的缓冲区,没有必要也跟着设置太大。 fastcgi_buffer_size最好单独设置,一般设置个4k就够了。

fastcgi_buffers

Syntax: fastcgi_buffers number size;
Default: fastcgi_buffers 8 4k 8k;
Context: http, server, location

Sets the number and size of the buffers used for reading a response from the FastCGI server,

for a single connection. By default, the buffer size is equal to one memory page.

This is either 4K or 8K, depending on a platform.

fastcgi_buffers的缓冲区大小一般会设置的比较大,以应付大网页。 fastcgi_buffers当中单个缓冲区的大小是由系统的内存页面大小决定的,Linux系统中一般为4k。 fastcgi_buffers由缓冲区数量和缓冲区大小组成的。总的大小为number*size。

若某些请求的响应过大,则超过fastcgi_buffers的部分将被缓冲到硬盘(缓冲目录由_temp_path指令指定), 当然这将会使读取响应的速度减慢, 影响用户体验. 可以使用fastcgi_max_temp_file_size指令关闭磁盘缓冲.

fastcgi_busy_buffers_size

Syntax: fastcgi_busy_buffers_size size;
Default: fastcgi_busy_buffers_size 8k 16k;
Context: http, server, location

When buffering of responses from the FastCGI server is enabled, limits the total size of buffers that can be busy sending a response to the client while the response is not yet fully read.

In the meantime, the rest of the buffers can be used for reading the response and, if needed, buffering part of the response to a temporary file.

By default, size is limited by the size of two buffers set by the fastcgi_buffer_sizeand fastcgi_buffers directives.

fastcgi_busy_buffers_size不是独立的空间,它是fastcgi_buffers和fastcgi_buffer_size的一部分。nginx会在没有完全读完后端响应的时候就开始向客户端传送数据,

所以它会划出一部分缓冲区来专门向客户端传送数据(这部分的大小是由fastcgi_busy_buffers_size来控制的,建议为fastcgi_buffers中单个缓冲区大小的2倍),

然后它继续从后端取数据,缓冲区满了之后就写到磁盘的临时文件中。

fastcgi_max_temp_file_size和fastcgi_temp_file_write_size

Syntax: fastcgi_max_temp_file_size size;
Default: fastcgi_max_temp_file_size 1024m;
Context: http, server, location

When buffering of responses from the FastCGI server is enabled, and the whole response does not fit into the buffers set by the fastcgi_buffer_size and fastcgi_buffers directives,

a part of the response can be saved to a temporary file. This directive sets the maximum size of the temporary file.

The size of data written to the temporary file at a time is set by the fastcgi_temp_file_write_size directive.

The zero value disables buffering of responses to temporary files.

This restriction does not apply to responses that will be cached or stored on disk.

临时文件由fastcgi_max_temp_file_size和fastcgi_temp_file_write_size这两个指令决定。 fastcgi_temp_file_write_size是一次访问能写入的临时文件的大小,默认是fastcgi_buffer_size和proxy_buffers中设置的缓冲区大小的2倍,Linux下一般是8k。

fastcgi_max_temp_file_size指定当响应内容大于proxy_buffers指定的缓冲区时, 写入硬盘的临时文件的大小. 如果超过了这个值, Nginx将与Proxy服务器同步的传递内容, 而不再缓冲到硬盘. 设置为0时, 则直接关闭硬盘缓冲.

fastcgi_buffering

Syntax: fastcgi_buffering on / off;
Default: fastcgi_buffering on;
Context: http, server, location

Enables or disables buffering of responses from the FastCGI server.

fastcgi_buffering这个参数用来控制是否打开后端响应内容的缓冲区,如果这个设置为off,那么fastcgi_buffers和fastcgi_busy_buffers_size这两个指令将会失效。但是无论fastcgi_buffering是否开启,对fastcgi_buffer_size都是生效的。

fastcgi_buffering开启的情况下,nignx会把后端返回的内容先放到缓冲区当中,然后再返回给客户端(边收边传,不是全部接收完再传给客户端)。 临时文件由fastcgi_max_temp_file_size和fastcgi_temp_file_write_size这两个指令决定的。

如果fastcgi_buffering关闭,那么nginx会立即把从后端收到的响应内容传送给客户端,每次取的大小为fastcgi_buffer_size的大小,这样效率肯定会比较低。

注: fastcgi_buffering启用时,要提防使用的代理缓冲区太大。这可能会吃掉你的内存,限制代理能够支持的最大并发连接数。

总结

性能对比

优化前的接口响应时间:1500ms=1000+500

优化后的接口响应时间:107.20ms=30+70

优化前的接口TPS:10

优化后的接口TPS:124

优化前后在线下数据量比较少的时候后端代码处理时间相差7倍,在线上数据量多的时候后端代码处理时间相差30倍,可见随着数据的增加,代码质量的影响会更大。

心得体会

这是工作以来,第一次遇到性能上的问题,从业务逻辑的角度来说,我一开始那么写一点问题都没有,存在问题的是性能方面,这也不正是在公司里面和在学校写代码的区别吗。

在学校只要把代码写出来就行,不重视细节,不重视性能。但是在公司里面就不一样了,你不光要完成任务,还要完成的漂亮,因为你是直接面向用户的,如果你有bug,体验不好,那用户可不买单,最终吃亏的还是自己。