Go 网络包默认值下的陷阱

  • 超时时间
  • HTTP响应主体
  • HTTP / 1.x保持活动
  • 连接池
  • 验证URI
  • DNS缓存
  • 伪装的DualStacknet.Dial()错误
  • net.IP 是可变的
  • 奖励:GOMAXPROCS,容器和CFS

超时时间

设置它们!

网络不可靠,并且标准库默认客户端和服务器未设置其主要超时,并且所有这些都将零值解释为引导的无穷大。超时取决于用例,Go核心团队已避免进行任何笼统的概括。

注意:这也包括对包级别便利功能的所有使用: http.Get客户端朋友http.ListenAndServe和服务器朋友。

一个必然的结果是,您实际上应该始终有一个定制的 http.Client和/或http.Server生产中的Go服务。

客户端超时

对于客户端,您通常只需要配置主超时。它涵盖了E2E交换,并且很可能是您的RPC思维模型的工作方式:

c := &http.Client{Timeout: 5 * time.Second,
}

此超时包括任何HTTP3xx重定向持续时间,响应正文的读取以及连接和握手时间(除非重新使用了连接)。我发现我通常在这里完成有关客户的工作。

但是,要对这些单个属性以及更多属性进行精细控制,您需要降低到基础传输:

c := &http.Client{Timeout: 5 * time.Second,Transport: &http.Transport{DialContext: (&net.Dialer{// This is the TCP connect timeout in this instance.Timeout: 2500 * time.Millisecond,}).DialContext,TLSHandshakeTimeout: 2500 * time.Millisecond,},
}

注意:由于在返回客户端方法后将读取响应主体,因此time.Timer如果要强制执行读取时间限制,则需要使用a 。

我从来没有需要的传输上有更多超时,例如ResponseHeaderTimeout((在请求写入结束后等待响应头的ExpectContinueTimeout时间)和(100-Continue 如果使用HTTP Expect头则等待的时间)。

还有一些与重用相关的设置,例如传输 IdleConnTimeout和拨号程序的KeepAlive设置。这些都应该放在自己的部分。

服务器超时

与您不希望服务器因为没有超时而使客户端的请求成为人质的意图相同,在编写Go HTTP服务器时,您要考虑相反的问题:您不希望行为不佳或滞后的客户端将服务器的文件描述符作为人质。

为了避免这种情况,您应该始终有一个自定义http.Server实例:

s := &http.Server{ReadTimeout:  2500 * time.Millisecond,WriteTimeout: 5 * time.Second,
}

ReadTimeout这里涵盖了读取请求标头和可选主体所花费的时间,并WriteTimeout涵盖了响应写入结束之前的持续时间。

但是,如果服务器正在处理TLS,则WriteTimeout报价器实际上在读取TLS握手的第一个字节后即开始。在实践中,这意味着您应该考虑整体因素ReadTimeout,然后再考虑要接受的写入内容。

与主要http.Client.Timeout值类似,这是您应该考虑的适当情况值的两个主要服务器超时,但是还有一些其他情况可以提供更精细的控制(例如分别读取和写入标头的时间)。再说一次,我从来不需要使用它们。


这些超时覆盖行为不佳的客户。但是对于服务器,您还应该考虑愿意接受多长时间作为请求处理持续时间。我在上面提到了客户端超时的心理模型;我认为这是服务器端版本,当您考虑时会立即想到:“服务器超时”

使用Go’s,http.Server您可以在处理程序函数本身中使用上下文标记实现这些超时。您还可以使用TimeoutHandler辅助包装器:

func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler

用这种方式包装意味着按惯例,直到dt违反为止,这时将503向下写入带有可选主体的客户端管道msg

HTTP响应主体

关上他们!

作为客户,您可能不在乎响应正文的内容,或者您可能正在期待空的响应。无论哪种方式,您都应该关闭它们。标准库不会代表您执行此操作,这可能会阻止客户端池中的连接,从而阻止重用(即,如果使用HTTP / 1.x保持活动)或更糟的是耗尽主机文件句柄。

但是,即使在没有主体的情况下或具有零长度的主体的情况下,标准库仍保证响应主体为非零。因此,为了安全地关闭事物,需要满足以下条件:

res, err := client.Do(req)
if err != nil {return err
}
defer res.Body.Close()
...

如果您不打算对身体做任何事情,那么完整阅读它仍然很重要。否则会影响重用的可能性,尤其是在服务器正在推送大量数据的情况下。用以下方法冲洗身体:

_, err := io.Copy(ioutil.Discard, res.Body)

注意:视情况而定,可能会尝试重用连接,但是如果服务器推送过多的数据,则关闭连接会更有效。io.LimitedReader在这里可以提供帮助。

HTTP / 1.x保持活动

说到重用,保持活动是Go的默认设置,但有时您不希望使用它们。举例来说,几年前,我曾担任过Webhook发送器的服务。它需要向许多不同的上游目标发出请求(几乎从未如此)。

关闭默认行为的最简单方法是将自定义传输连接到客户端(由于该字段注释中的其他一些原因,我仍然总是这样做):

client := &http.Client{&http.Transport{DisableKeepAlives: true}
}

但是,您也可以通过告诉Go客户端为您关闭该请求,以根据每个请求执行此操作:

req.Close = true

或者以其他方式向行为良好的服务器发送信号,以添加Connection: close 响应标头,Go客户端将知道该响应标头该怎么做。

req.Header.Add("Connection", "close")

连接池

继续以重用为主题。在微型SOA中,我发现自己要在上面构建Webhook发射机服务的可能性要比需要高频集成但只集成到少数上游(例如云数据存储/队列)的服务低得多以及一个或两个相关的API)。

在这种更常见的情况下,我认为Gohttp.Client违约对您不利。

我的意思是,客户端的默认传输在连接池方面表现出一些属性,您应该始终注意以下几点:

var DefaultTransport RoundTripper = &Transport{MaxIdleConns:          100,IdleConnTimeout:       90 * time.Second,
}
...
const DefaultMaxIdleConnsPerHost = 2

这三个设置之间的关系可以概括如下:保留大小为100的连接池,但每个目标主机仅保留2个连接池;如果90秒钟内未使用任何连接,它将被删除并关闭。

因此,以100个goroutine共享相同或默认值http.Client 以向相同的上游依赖项发出请求的情况为例(如果您自己的客户端本身也是较大的微服务生态系统的服务器部分,则对于每个接收到的请求派生例程)也并非如此。这100个连接中的98个 立即关闭

首先,这意味着您的服务正在更加努力。建立连接有无数成本:内核网络堆栈的处理和分配;DNS查找,其中可能有很多(resolv.conf(5):ndots:n 特别是在运行Kubernetes集群时,请阅读);以及要通过的TCP和TLS握手。

这当然不是最佳选择,但是过去还有另一笔隐性成本困扰着我,使整个主机变得无用:closed!= closed(无论如何在Linux中)。

内核实际上将套接字转换为一种TIME_WAIT状态,其目的主要是为了防止来自某个连接的延迟数据包被后续连接接受。内核会将它们保留大约60秒钟(按照RFC793的要求,在Linux中很难更改)。

TIME_WAIT套接字的堆积可能会对繁忙的主机的资源产生不利影响。

一方面,要有额外的CPU和内存来维护内核中的套接字结构,但最关键的是,连接表中有插槽。正在使用的插槽意味着不存在具有相同四联体的另一个连接(源addr:port,dest addr:port),这反过来可能导致短暂的端口耗尽-令人恐惧EADDRNOTAVAIL

验证URI

这是一个很小的方法,但是就我而言,该url.Parse方法本质上是绝对可靠的,它使我流连忘返。您几乎总是想要url.ParseRequestURI,然后进一步检查是否要过滤掉相对URL。

DNS缓存

与JVM不同,Go标准运行时中没有内置的DNS缓存。这是一把双刃剑。我个人对这个默认值表示感谢,因为该默认值在前世被该JVM缓存烧毁了无数次。同时,在尝试生成优化的Go服务时始终要意识到这一点。

Go核心团队的立场是,您应该通过诸如dnsmasq之类的方式依赖底层主机平台来支持DNS缓存需求。但是,值得指出的是,您并不总是能控制这种情况。例如,AWS Lambda的运行时沙箱在其中包含单个远程Route53地址,/etc/resolv.conf并且不提供沙箱本地缓存服务器。

在这种情况下,您还有另一个选择是覆盖DialContexton http.Transport(似乎是本主题的一般主题)并插入内存中的缓存。我可以为此目的推荐dnscache。

注意:net.DefaultResolver如果您不能完全控制客户端的传输,则可能还需要考虑覆盖程序包单例。

如果您对延迟敏感的服务仅具有几个上游依赖项,则可以考虑使用这些选项之一。否则,默认情况下,这些服务将需要连续拨打相同的不变域名。

我之所以说“默认”,是因为您可能有一组经过良好调整的重用连接(也许甚至要感谢有关连接池的部分)。如果是这样的话,那么请告诫-我还有另一本电子书供您参考。

在我以前的客户中,我遇到一个问题,即大量(作为部分)充当反向代理的基于Go的服务一直在代理相同的后端后端端点。这是因为后端目标使用DNS来进行新版本的蓝色/绿色部署,并且尽管主机具有TTL依赖的DNS缓存,但仍在发生。

问题在于该服务如此迅速地重用了连接,以致从未中断空闲超时。

从Go 1.16开始,默认运行时中的任何操作都不会强制关闭那些已建立的连接,并因此获得主机名的已更新解析IP,从而迫使您使用一个单独的goroutine进行创意,以transport.CloseIdleConnections()在不理想的时间间隔内进行调用 。尽管在Go中编写反向代理非常容易,但是我在这里的错误并没有屈服于更专用和端点感知的东西(例如出色的 Envoy代理)。

伪装的DualStacknet.Dial()错误

这一个是细致入微,但是是一个belter如果它击中,它可以面(因为它为我做的),即使你不积极使用IPv6。

以一个库存的Amazon EKS工人节点为例。在撰写本文时,EKS优化的AMI具有以下默认特征:

  1. Docker守护程序未启用实验性IPv6标志,因此不会在容器虚拟网络接口上配置IPv6地址。
  2. 但是内核确实启用了IPv6支持,这意味着/proc/net获取了IPv6构造(即使在容器名称空间中),并且从那里推断出其他一些东西,最关键的是,/etc/hosts接收了两个默认的回送条目:127.0.0.1::1

现在,假设您有一个Go服务,它通过环回调用了另一个服务,例如流程sidecar,但是您使用localhost主机名幼稚地解析了环回地址。

TL; DR:这是您出错的地方。避免麻烦,在这里停止并使用,127.0.0.1或者::1取决于您的目标堆栈,除非您有充分的理由不这样做。继续阅读以了解您为什么要这样做。

您在开发过程中没有注意到,但是在大规模运行的集成环境中,您会偶尔看到::1 cannot assign requested address 拨号程序重复发出的消息。但是,请检查主机和临时端口是否 完全正常。有理论。

但是,该::1IPv6地址族错误是怎么回事?令人担忧的是,从上面的AMI特性来看,我们知道解析该地址的客户端在连接时间上很困难,因为实际上没有绑定到该客户端的网络接口。但话又说回来,如果这是真的,为什么它没有失败所有的时间?

好吧,可能是Go拨号程序伪装了真正的错误**-IPv4错误**!

之所以会发生这种情况,是因为有一些微妙的默认设置:

  1. 首先,当Go拨号程序为同一主机名提供多个地址时,它会根据RFC6724进行排序和选择地址,最重要的是,该RFC首先概述了IPv6的选项。
  2. 然后,由于Go的默认网络传输也具有 RFC6555支持(又名“快乐眼球” /“双堆栈”),它将尝试并行拨打两个地址系列,但使主IPv6提前300毫秒。如果主节点快速失败(由于不存在IPv6接口地址,在这种情况下通常会发生故障),则将取消起始,并立即尝试IPv4。到目前为止一切都很好。但是,如果两个地址都无法拨号,则仅返回主要(即IPv6)错误

因此,如果您的IPv4地址无法偶发拨号(例如,上游不畅导致偶发的连接超时),则所显示的错误将是无关紧要的,并且总是无法拨打IPv6::1 cannot assign requested address而不是更有用的IPv4 connect timeout

net.IP 是可变的

尽管我做一些愚蠢的事情,但这仍然使我有点困扰。不要被欺骗而认为net.IP是一个不变的数据结构。实际上,它是别名为的透明类型[]byte。您传递给它的任何东西都可能使它变异,而Sod / Murphy定律说它会改变。

奖励:GOMAXPROCS,容器和CFS

关于默认行为的奖励条目,虽然它与net软件包没有特别关系,但肯定会间接影响其性能。

Go将使用GOMAXPROCSinit的运行时变量值来确定有多少实际OS线程来复用所有用户级别的Go例程。默认情况下,它设置为在主机环境中发现的逻辑CPU的数量,我想这是Go核心团队需要确定合理默认值的另一个示例,即,在通用上下文中,逻辑CPU的数量是提供最高数量的原因。表现。

不幸的是,Go运行时也恰好没有意识到CFS配额,并且多租户容器编排平台(如Kubernetes)将默认使用它们在运行容器的cgroup上强制执行其各自的CPU限制概念。

注意:CFS是Linux内核的完全公平调度程序—一种按比例分配的调度程序,用于在cgroup之间划分可用的CPU带宽。

继续以Kubernetes为例,默认GOMAXPROCS值与CFS不知道配额的运行时混合Pod在一起意味着您的Go服务,加上经过仔细考虑的CPU资源限制和请求规范,可能会发现自己受到CFS的积极和过度限制

为什么是这样?可以说,例如,您的Go服务Pod规范的CPU资源限制为300m(millicpu),并且采用Linux内核的现有CFS配额窗口为100ms,这使Go服务的每个窗口的CPU时间为30ms。如果它尝试使用更多的资源,它将被CFS限制直到下一个窗口。这很好,可以预期,因为您在本地对服务流程进行了科学基准测试,并首先满足了300m的要求(对吗?)。

但是,要记住的关键是实际上30ms实际上在GOMAXPROCSOS线程之间进一步细分,因此如果您Pod碰巧落在大型商用主机VM上,为简单起见,它具有15个逻辑CPU(这在binpack风格的Kubernetes集群拓扑中很常见) ),那么您的Go服务就会天真地设置GOMAXPROCS为15,从而使每个生成的OS线程每个调度窗口的CPU时间可能只有2 毫秒,然后才能被抢占!

结果是Go运行时调度程序浪费了所有在上下文切换上分配的CPU时间,并且没有完成任何有用的工作,因为它认为自己可以访问15个逻辑CPU,而实际上却只能访问小数0.3。

注意:要查看这是否影响您负责的Kubernetes服务,请使用kubelet cAdvisor指标container_cpu_cfs_throttled_periods_total

GOMAXPROCS是一个整数,因此在上面的示例中,您唯一的方法就是将其硬编码为1。这可以通过使用export GOMAXPROCS=1或使用func包在环境中完成runtime.GOMAXPROCS(1)

不过,更笼统地说,建议使用Uber的automaxprocs插件使Go运行时支持CFS。对于Kubernetes运营商,如果您愿意,也可以在kubelet级别禁用CFS配额强制。

Golang:Go 网络包默认值下的陷阱相关推荐

  1. Android 首选网络模式默认值的修改方法

    高通 Android9 更改以下文件 Y:\project\msm8953_android9\device\qcom\msm8953_64\system.prop ro.telephony.defau ...

  2. 监控io性能,free命令,ps网络命令,查看网络状态,Linux下抓包

    10.6 监控io性能 磁盘的io是一个非常重要的指标,所以要更详细的查看磁盘状态,需要用到iostat命令,如果之前已经安装了sysstat包的话,在安装sysstat包时iostat命令就已经被安 ...

  3. dategridview设置下拉框默认值_smtp服务器怎么设置

    smtp服务器是什么意思 SMTP的全称是"SimpleMailTransferProtocol",即简单邮件传输协议.它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮 ...

  4. struts2下的Action配置的各项默认值

    1.在之前,我介绍过第一个struts2的小程序:struts2搭建环境和第一个小程序,接下来我将介绍Action配置的各项默认值. 2.首先,我们可以copy一个已经配置好struts2环境的web ...

  5. js填充select下拉框并选择默认值

    /* 使用json数组填充下拉框并复选 *//* 初始化下拉框数据 */ var jsonStr = { "data": [] }; for (var str in JsonStr ...

  6. easyui的combobox下拉框初始化默认值以及保持该值一直显示的方法

    easyui的combobox下拉框默认初始值是空,下面是实现从远程加载数据之后初始化默认值,以及让该值一直排在下拉框的最顶部的方式. 目前的需求是需要在初始化的时候添加"全部数据库&quo ...

  7. android选择下拉框的默认值,如何在Android中将默认值设置为微调器下拉列表?

    我需要将默认值文本"选择你的设备"值设置为我的微调器下拉列表,我把它作为列表项,所以它作为一个下拉列表项,我隐藏下拉列表,因为这我的下拉菜单中有一个空白区域. 我该怎么做? 这是我 ...

  8. mysql datetime 默认值_老大让我整理下公司内部MySQL使用规范,分享给大家

    作者:白程序员的自习室 链接:https://www.studytime.xin/article/mysql-internal-specifications.html 最近涉及数据库相关操作较多,公司 ...

  9. yii框架的下拉框多选,设置默认值等(dropDownList)

    一.前言 根据需求,需要更改下拉框为多选框.本来以为只是很简单的东西,但是实际修改起来却碰到不少bug,多选框无法选中,给多选框赋默认值等.这里记录一下 二.修改单选下拉框为多选下拉框 echo $f ...

最新文章

  1. JQuery自定义插件详解之Banner图滚动插件
  2. Fragment与Activity的接口回调
  3. List Set Map的区别
  4. 代码实现:键盘录入任意一个年份,判断该年是闰年还是平年
  5. 【数据科学系统学习】机器学习算法 # 西瓜书学习记录 [12] 集成学习实践
  6. 每天学习一点点(2010年二月)
  7. 【软件质量】软件可维护性
  8. 基于tcp的协议需要设计数据校验码吗_C#与宇电温控表自定义协议通信实例
  9. dc游戏java_极限飞车-穿越峡谷(DC出品)
  10. 传智播客Java引用和数值类型思考
  11. keil+c语言优化,KEIL编译器【C语言编译选项优化等级说明】
  12. 高德地图api中的adcode城市编码
  13. xampp mysql使用教程_在Windows下XAMPP的安装及使用教程
  14. C#程序设计基础试题(一)
  15. Android Ui卡断的原因分析及常见的解决方式
  16. python多进程子进程不运行问题--未解决
  17. [Computer Architecture读书笔记] H.2 Detecting and Enhancing Loop-Level Parallelism
  18. 国网GIM设备三维模型要求细则 - 交流断路器
  19. 笔记-lxf官网面向对象高级编程
  20. Elasticsearch Multi Search(_msearch)

热门文章

  1. 浮点尾数特性详解(02325计算机系统结构)
  2. 树袋熊无线网络打印服务器,树袋熊
  3. ie浏览器下载门户网站的excel文档出现乱码现象的解决办法
  4. 遇到面试心情总是很紧张该怎么缓解
  5. 关于SSM的家具购物系统的设计答辩问题合集
  6. 事件委托是怎么回事?
  7. 985大学计算机考研难度排名2015,39所985院校考研难度排名,看看你是哪个档次?...
  8. IDEA的菱形断点是什么意思
  9. 初中使用计算机教学反思,初中信息技术课堂教学反思随笔
  10. github更新网站内容,然后在本地commit,pull,push后,刷新网站没有出现更新的内容