开启了http2,但是http2_max_field_size 还在用默认值吗?

开启了http2,但是http2_max_field_size 还在用默认值吗?

排查1个异常接口,学到一个降本和接口提速的新思路。另外找到1个charles的"bug"

现象

测试同学反馈在iOS13设备上请求接口报错,将请求复制为 curl 命令。分别导入 apifox 和 在终端执行:

  • 在 apifox 请求正常 ✅
  • 在终端请求失败 ❌
curl: (56) Failure when receiving data from the peer

测试同学又反馈另一个手机支持请求接口返回正常。

定位

有了正常和错误的请求curl,那直接对比二者差异就很简单了。对比发现,在终端执行失败的请求中多了一个较大的Cookie: app_token。按历史经验基本能确定是因为 Cookie 这个 header 条目太大,超过服务器的限制。 找云平台确定相关配置:

ELB
http1: header头限制 128KB,body 限制一个10G
http2: 单个 header 限制 4KB

curl 加 -v 参数再次请求,发现终端发起的请求使用的是 http2。再使用 --http1.1 参数强制使用 http1.1,接口正常返回。佐证了问题出在 http2,遵循优先快速恢复原则,我们先关闭了 http2,请求恢复正常。但还是有 3 个疑问困扰?

  1. 为什么有问题的curl请求在 apifox 始终能成功?
  2. 测试同学发现,iOS 14 和 iOS 17 的请求都能的成功,Cookie 大小也和 iOS 13的一致,但是却始终返回正常。
  3. 通过手动修改 Cookie 长度,找到了成功和失败的临界点。但是这个值5000多,为什么大于 4KB?

根因排查

第1个问题:apifox 无法在界面指定 http 协议,于是在启动 Charles 抓包,apifox 默认就是使用的 http1.1,所以没有问题。第2个问题:👍 测试同学又抓包发现,iOS 13 和 iOS 14 的请求对 cookie 的处理方式不同:

  • iOS 13 所有的 cookie 合并到一起
  • iOS 14 则逐个分开

我们现在知道了拆分 Cookie 可以跳过 nginx 的 http2_max_field_size,但这就是 iOS 14 为什么要拆分 Cookie 的原因吗?

我们在rfc文档里找到了答案https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.5http2 拆分 Cookie 可以获得更高的压缩率,新版系统应该对此有优化。因为 http2 的设计对 header 的优化其中一点就是:减少冗余数据。通过索引和动态表,减少了重复传输相同头部字段和值所需的字节数。

💡
The Cookie header field [COOKIE] uses a semi-colon (";") to delimit
cookie-pairs (or "crumbs"). This header field doesn't follow the
list construction rules in HTTP (see [RFC7230], Section 3.2.2), which
prevents cookie-pairs from being separated into different name-value
pairs. This can significantly reduce compression efficiency as
individual cookie-pairs are updated.
To allow for better compression efficiency, the Cookie header field
MAY be split into separate header fields, each with one or more
cookie-pairs. If there are multiple Cookie header fields after
decompression, these MUST be concatenated into a single octet string
using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ")
before being passed into a non-HTTP/2 context, such as an HTTP/1.1
connection, or a generic HTTP server application.
Therefore, the following two lists of Cookie header fields are
semantically equivalent.
cookie: a=b; c=d; e=f
cookie: a=b
cookie: c=d
cookie: e=f

HTTP/2 支持 HEADER 压缩,开发了 HPACK 算法,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,还釆用哈夫曼编码来压缩整数和字符串,可以达到 50%~90% 的高压缩率。

回到第2个问题,“iOS 14 和 iOS 17 的请求都能的成功,Cookie 大小也和 iOS 13的一致,但是却始终返回正常”。因为 iOS 14 对 Cookie 的处理方式进行了优化。原因不是为了绕过 http2_max_field_size 的限制,而是为了获取更高的压缩率。

第3个问题:http2_max_field_size 针对单个 HeaderField 的限制,如果进行 Huffman(哈夫曼编码)压缩,那么此限制针对的是对压缩后的值进行比较;

注:此指令自版本 1.19.7 起已过时。应改用 large_client_header_buffers

总结

要让 HPACK 的“字典”持续使用,必须确保Key-Value不会发生变更。所以尽量拆分 HEADER 内容,不常变更的和常变更的要分开。少了 HEADER 的传输,带宽和请求时延都有提升,这是不是一个降本的好思路呢,快来检查你们接口的请求和响应 HEADER,看看有没有优化的可能性。

  • HTTP/2 支持将 Cookie 字段可以拆分为单独的 HEADER 字段,每个标头字段包含一个或多个 cookie 对。
  • 是否将 Cookie 拆分是请求方逻辑,部分旧客户端虽然支持 HTTP/2,但是不一定会进行此优化。

最后也发现 Charles 的小 Bug 🪳:测试反馈接口问题,服务端可能让提供 Charles 请求复制成 Curl Request。但把拆分 Cookie 请求,复制成 curl, Cookie 会合并到一起🌝。

参考:

Read more

代码 Refactoring

重构不必谈之色变。 它也不是洪水猛兽,而是开发过程中持续进行的优化过程。让我们开始学习这个主题,重新认识它的价值。 🌟整洁代码 重构的主要目的是消除技术债务。它将混乱变成整洁的代码和简单的设计。 * 对于其他程序员来说,整洁的代码是显而易见的。 我并不是在谈论超级复杂的算法。糟糕的变量命名、臃肿的类和方法、魔法数字 - 等等,所有这些都会让代码变得混乱和难以理解。 * 整洁的代码不包含重复。 每次你需要对重复的代码进行更改时,你都必须记住对每个实例进行相同的更改。这会增加认知负担并减慢进度。 * 整洁的代码包含最少数量的类和其他活动部件。 代码越少,脑子里需要记住的东西就越少。代码越少,维护工作就越少。代码越少,错误就越少。代码就是责任,保持代码简短。 * 整洁的代码通过所有测试。 如果只有 95% 的测试通过,你就知道代码不整洁。如果测试覆盖率为 0%,你就知道你完蛋了。 * 整洁的代码维护成本低! 🗑️技术债(What) 每个人都尽最大努力从头开始编写出色的代码。可能没有程序员会故意编写不干净的代码,从而损害项目。但是干净的代码在什么时

By brian

CSV 格式说明和应用

问题 我们常常将多个字符串item使用逗号拼接成一个字符串,用来表示数组,使用时再用逗号切割成为数组。比如安卓机型列表: ALN-AL10,ALN-AL10,BRA-AL00,ALN-AL00/ALN-AL80 直到有一天,苹果设备也要用到这个机型列表,而它的每个机型都带着逗号,那我们使用逗号切割就得到了错误的数据。 iPhone15: iPhone15,4 iPhone15Plus: iPhone15,5 iPhone15Pro: iPhone16,1 iPhone15Pro_Max: iPhone16,2 为了解决这个问题,首先想到了换一个分隔符,比如 | ,再比如用一些不可见字符 : 0x01。 但我们不能保证这些字符串 item 一定不包含这些特殊字符,也许还有更好的方法。 既然是逗号分隔,首先想到的就是 CSV格式,毕竟 CSV 的全称就是Comma-Separated Values逗号分隔值。它是如何解决这个问题的? CSV格式 CSV 的RFC说明文档:https://datatracker.ietf.

By brian

Minikube 操作开启 ipvs

1. 编辑 kube-proxy 配置 编辑 kube-proxy 配置以启用 ipvs 模式。您可以使用以下命令编辑配置: kubectl edit configmap kube-proxy -n kube-system 在配置中,找到 mode 字段并将其设置为 ipvs。例如: apiVersion: v1 kind: ConfigMap metadata: name: kube-proxy namespace: kube-system data: mode: "ipvs" 保存并关闭配置。 2. 重新启动 kube-proxy 重新启动 kube-proxy 以应用新配置: kubectl rollout restart daemonset kube-proxy -n kube-system

By brian

在 Minikube 中直接上手学习 K8S Service

K8S 为什么需要 Service? 1. Pod 的 IP 不是固定的; 2. 一组 Pod 实例之间有负载均衡的需求; 上机操作 以下使用 minikube 作实例: # 安装 brew install minikube # 启动 minikube start # 创建 ~/.kube/config 请注意先备份 minikube update-context # 进入宿主机 minikube ssh 集群启动后,创建 Deployment 和 Service apiVersion: v1 kind: Service metadata: namespace: default name: nginx-svc spec: type: NodePort selector:

By brian
沪ICP备2022013452号-1