bingoohuang / blog

write blogs with issues
MIT License
178 stars 24 forks source link

毁三观到三观回正:简单的echo咋就这么慢呢 #182

Open bingoohuang opened 4 years ago

bingoohuang commented 4 years ago

毁三观的图:

image

三观回正的图:

image

用了这么久HTTP, 你是否了解Content-Length和Transfer-Encoding ?

由Content-Length导致的问题引发的一系列思考:

前段时间开发API网关, 使用postman调试时出现了超时的情况, 经排查确定是请求数据被处理后Content-Length与实际不一致导致的问题, 故有此文.

前言

Content-Length, HTTP消息长度, 用十进制数字表示的八位字节的数目. 一般情况下, 很多工作都被框架完成, 我们很少去关注这部分内容, 但少数情况下发生了Content-Length与实际消息长度不一致, 程序可能会发生比较奇怪的异常, 如:

  1. 无响应直到超时.
  2. 请求被截断, 而且下一个请求解析出现错乱.

什么是Content-Length

Content-Length是HTTP消息长度, 用十进制数字表示的八位字节的数目, 是Headers中常见的一个字段. Content-Length应该是精确的, 否则就会导致异常 (特别地, HTTP1.0中这个字段可有可无).

Content-Length首部指示出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的, 比如, 对文本文件进行了gzip压缩的话, Content-Length首部指的就是压缩后的大小而不是原始大小.

Content-Length是如何工作的

Content-Length使用十进制的数字表示了消息的长度, 服务端/客户端通过它来得知后续要读取消息的长度.

如果这个长度不正确, 会发生如下情况:

  1. Content-Length > 实际长度 如果Content-Length比实际的长度大, 服务端/客户端读取到消息结尾后, 会等待下一个字节, 自然会无响应直到超时,同样地, 在响应消息中Content-Length超过实际长度也是一样的效果
  2. Content-Length < 实际长度 如果这个长度小于实际长度, 首次请求的消息会被截取, 比如参数为param=piaoruiqing, Content-Length为10, 那么这次请求的消息会被截取为: param=piao

不确定Content-Length的值怎么办

Content-Length首部指示出报文中实体主体的字节大小. 但如在请求处理完成前无法获取消息长度, 我们就无法明确指定Content-Length, 此时应该使用Transfer-Encoding: chunked

什么是Transfer-Encoding: chunked

数据以一系列分块的形式进行发送. Content-Length 首部在这种情况下不被发送. 在每一个分块的开头需要添加当前分块的长度, 以十六进制的形式表示,后面紧跟着 \r\n , 之后是分块本身, 后面也是\r\n. 终止块是一个常规的分块, 不同之处在于其长度为0.

结语

  1. Content-Length如果存在且生效, 必须是正确的, 否则会发生异常.(大于实际值会超时, 小于实际值会截断并可能导致后续的数据解析混乱)
  2. 如果报文中包含Transfer-Encoding: chunked首部, 那么Content-Length将被忽略.
bingoohuang commented 2 years ago

要计算 Content-Length 响应头就必须缓冲所有响应体数据,因为是先发送 Content-Length 响应头,然后再发送任何的响应体数据。这是一个鸡和蛋的问题。所以对于大响应来说,开销很大。 既然使用了 chunked 编码,就不应再设置 Content-Length 响应头了。 HTTP 1.0 协议是不支持 chunked 编码的,所以在没有 Content-Length 响应头时以连接断开作为响应体的末尾。 尽量使用 HTTP 1.1;毕竟现在连 nginx 的 proxy 模块也支持 HTTP 1.1 了。

-- nginx开启gzip后无法输出content-length问题请教

bingoohuang commented 2 years ago

在nginx中,如果采用gzip,如果是keep alive,则必然是chunked模式。

Response与Transfer-Encoding:chunked、Content-Length、Content-Encoding:gzip

bingoohuang commented 2 years ago
bingoohuang commented 2 years ago
    gzip on;
    gzip_disable "MSIE [1-6]\.";
    gzip_min_length 1k;
    #gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json 'text/plain; charset=UTF-8' 'text/css; charset=UTF-8' 'application/x-javascript; charset=UTF-8' 'text/xml; charset=UTF-8' 'application/xml; charset=UTF-8' 'application/xml+rss; charset=UTF-8' 'text/javascript; charset=UTF-8' 'application/json; charset=UTF-8' 'application/json;charset=UTF-8';

    chunked_transfer_encoding off;

见证代码逻辑:gzip 开启了 + chunked 关闭了 => keep alive 失效了:

image

nginx中开启keepalive后应答反而为close的原因

wecom-temp-442191762a47d78d0dd910edeff6cde8

typercode commented 2 years ago

在upstream显示配置keepalive_requests 也有利于性能提升

Dofault kespalswo  recue sts 19603

http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream

typercode commented 2 years ago

为什么打开gzip、关闭chunked会关闭连接?

因为 Nginx 会采用即时压缩(On-The-Fly Compression),整个压缩过程在内存中流式完成。也就是说,Nginx 不会等文件 GZip 完成再返回响应,而是边压缩边响应,这样可以显著提高 TTFB(Time To First Byte,首字节时间,WEB 性能优化重要指标)。这样唯一的问题是,Nginx 开始返回响应时,它无法知道将要传输的文件最终有多大,也就是无法给出 Content-Length 这个响应头部。

对于 TCP 持久连接上的 HTTP 报文,客户端需要一种机制来准确判断结束位置,所以HTTP Server不启用持久连接(关闭连接的方式)。

从 Nginx 默认不压缩 HTTP/1.0 说起

所以合理的方式是打开gzip、chunked。

image