记一次curl post请求数据被截断问题处理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次curl post请求数据被截断问题处理相关的知识,希望对你有一定的参考价值。

参考技术A 通过linux curl 命令和php脚本的curl请求一个接口地址,返回的json数据比较大,结果只获取到部分数据,并且linux curl终端还报了个错,如下:
curl: (18) transfer closed with outstanding read data remaining

怀疑服务端有异常,查看nginx日志发现
nginx[warn]:an upstream response is buffered to a temporary

虽然是个warn,但出现问题的时候也一样要警示。
于在nginx.conf 的http 里加入如下一段:

加大buffer的配置,居然没管用

后经查,负载均衡采用的nginx反向代理,于是到nginx代理服务器上,http里加入如下一段:

问题解决。

分析:
Nginx 的 buffer 机制,对于来自 FastCGI Server 的 Response,Nginx 将其缓冲到内存中,然后依次发送到客户端。缓冲区的大小由 fastcgi_buffers 和 fastcgi_buffer_size 两个值控制。fastcgi_buffer_size 则是处理 Response 时第一个缓冲区的大小,不包含在fastcgi_buffers中,如上配置
最大内存缓冲区大小是 8 * 128 + 4 = 1028K

当 Response的内容 小于等于 1028K 时,所有数据当然全部在内存中处理。如果 Response 大于 1028K ,多出来的数据会被临时写入到文件中,放在fastcgi_temp目录下面。此时会在看到类似如下 warning:

这个值太小,Nginx 会频繁读写硬盘,影响性能,太大也不好,会吃掉内存,所以根据实际Response的内容大小来定。

[debug]记一次竞态更新bug的解决

公司的django项目,有一个旧接口,使用POST方法更新用户的一种记录型数据。

这个接口的历史有点长,最早的时候没有那么多需求,只会更新两个布尔字段。后来,加入一个需要高频次记录的字段。这些字段都属于同一个model。

然后,偶然被发现有时更新会失败的情况。

Debug

开始debug,步骤是这样的:

  1. 观察nginx请求日志,观察每次POST的参数(我们记录了request_body),肉眼编译代码,尝试找出漏洞。看不出,失败!
  2. 编写单元测试,将日志中的数据,模拟成testcase,对接口反复测试。没有重现,失败!
  3. 准备查看mysql日志...
  4. 突然有了灵感。重新观察日志,发现一个特点,客户端有时会并发请求两个不同payload,但是更新同一对象的请求。原来是race condition!

解决方法

model.save(update_fields=[‘...‘])

解析

虽然这个接口是POST,但是其实它属于PATCH更新,或者说局部更新。

而之前的代码中,使用的是全量更新。

这就导致了: 在两个同时进行的更新操作中,前面完成的一次更新,会被后一次更新覆盖。 因为后一次中对象的数据都是更新前的,所以第一次更新的效果都会恢复。

Tips

Model.save(force_insert=False, force_update=False, using=DEFAULT_DB, update_fields=None)

  • force_insert & force_update

    django的model一般会根据instance是否有主键,决定是INSERT还是UPDATE。

    但是,你可以使用这两个参数来强制选择你要执行的方式.

    force_insert不算很有用,你也可以确认对象是否有pk,有的话就删除它,然后来执行强制INSERT。

    不过,force_update应该算很有用。在django中,大多数时候UPDATE都需要先找到对象,然后再执行更新。这样稍微有点性能的浪费,如果有类似mongo的upsert功能就好了。

    用django实现的upsert:

    def upsert(instance, pk, **kwargs):
        # 注意需要指定pk,除非你的model中的pk是自动生成的(auto_increment不算)
        type(instance)(pk=pk, **kwargs).save(force_update=True)

    不过上面的方式还有点问题,如果你在kwargs中没有指定model的所有字段,那么这些字段会被重设回默认值。

  • update_fields

    这个参数,等于force_update的加强版。它代表只更新指定的几个字段,没有指定的字段就让它们保持原样好了,也就是等于REST概念中的PATCH更新。

    def upsert(instance, pk, **kwargs):
        type(instance)(pk=pk, **kwargs).save(update_fields=list(kwargs.keys()))

PATCH更新的好处很多,不止是预防了bug,还能减少payload数据量,增加性能。

为什么不用Queryset.update()

不是不用,只是这个批量更新接口不会调用.save(),也就导致了你在.save()中放的hook也不会被调用。

而且批量更新也不会触发django signals,详情请搜索我翻译的相关文章.

以上是关于记一次curl post请求数据被截断问题处理的主要内容,如果未能解决你的问题,请参考以下文章

springboot post请求参数中有&还是被截断

记一次php curl导致的故障

记一次HTTP POST请求变成GET请求

记一次重构:并行化调用接口实践

php ajax提交post请求出现数组被截断情况的解决方法

记一次Api请求