timeout 和 deadline

128 篇文章 0 订阅
123 篇文章 0 订阅

如果我们查看 net/http 的源码,尤其是看到 `conn` 类型[1] 时,我们会发现conn 实际上使用了 net.Conn 连接,net.Conn 表示底层的网络连接:

// Taken from: https://github.com/golang/go/blob/bbbc658/src/net/http/server.go#L247
// A conn represents the server-side of an HTTP connection.
type conn struct {
    // server is the server on which the connection arrived.
    // Immutable; never nil.
    server *Server

    // * Snipped *

    // rwc is the underlying network connection.
    // This is never wrapped by other types and is the value given out
    // to CloseNotifier callers. It is usually of type *net.TCPConn or
    // *tls.Conn.
    rwc net.Conn

    // * Snipped *
}

换句话说,我们的 HTTP 请求实际上是基于 TCP 连接的。从类型上看,TLS 连接是 *net.TCPConn 或 *tls.Conn 。

serve 函数[2]处理每一个请求[3]时调用 readRequest 函数。readRequest使用我们设置的 timeout 值[4]来设置 TCP 连接的 deadline

// Taken from: https://github.com/golang/go/blob/bbbc658/src/net/http/server.go#L936
// Read next request from connection.
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
        // *Snipped*

        t0 := time.Now()
        if d := c.server.readHeaderTimeout(); d != 0 {
                hdrDeadline = t0.Add(d)
        }
        if d := c.server.ReadTimeout; d != 0 {
                wholeReqDeadline = t0.Add(d)
        }
        c.rwc.SetReadDeadline(hdrDeadline)
        if d := c.server.WriteTimeout; d != 0 {
                defer func() {
                        c.rwc.SetWriteDeadline(time.Now().Add(d))
                }()
        }

        // *Snipped*
}

从上面的摘要中,我们可以知道:我们对服务设置的 timeout 值最终表现为 TCP 连接的 deadline 而不是 HTTP 超时。

所以,deadline 是什么?工作机制是什么?如果我们的请求耗时过长,它们会取消我们的连接吗?

一种简单地理解 deadline 的思路是,把它理解为对作用于连接上的特定的行为的发生限制的一个时间点。例如,如果我们设置了一个写的 deadline,当过了这个 deadline 后,所有对这个连接的写操作都会被拒绝。

尽管我们可以使用 deadline 来模拟超时操作,但我们还是不能控制处理器完成操作所需的耗时。deadline 作用于连接,因此我们的服务仅在处理器尝试访问连接的属性(如对 http.ResponseWriter 进行写操作)之后才会返回(错误)结果。

为了实际验证上面的论述,我们来创建一个小的 handler,这个 handler 完成操作所需的耗时相对于我们为服务设置的超时更长:

package main

import (
 "fmt"
 "io"
 "net/http"
 "time"
)

func slowHandler(w http.ResponseWriter, req *http.Request) {
 time.Sleep(2 * time.Second)
 io.WriteString(w, "I am slow!\n")
}

func main() {
 srv := http.Server{
  Addr:         ":8888",
  WriteTimeout: 1 * time.Second,
  Handler:      http.HandlerFunc(slowHandler),
 }

 if err := srv.ListenAndServe(); err != nil {
  fmt.Printf("Server failed: %s\n", err)
 }
}

上面的服务有一个 handler,这个 handler 完成操作需要两秒。另一方面,http.Server 的 WriteTimeout 属性设为 1 秒。基于服务的这些配置,我们猜测 handler 不能把响应写到连接。

我们可以用 go run server.go 来启动服务。使用 curl localhost:8888 来发送一个请求:

$ time curl localhost:8888
curl: (52) Empty reply from server
curl localhost:8888  0.01s user 0.01s system 0% CPU 2.021 total

这个请求需要两秒来完成处理,服务返回的响应是空的。虽然我们的服务知道在 1 秒之后我们写不了响应了,但 handler 还是多耗了 100% 的时间(2 秒)来完成处理。

虽然这是个类似超时的处理,但它更大的作用是在到达超时时间时,阻止服务进行更多的操作,结束请求。在我们上面的例子中,handler 在完成之前一直在处理请求,即使已经超出响应写超时时间(1 秒)100%(耗时 2 秒)。

最根本的问题是,对于处理器来说,我们应该怎么设置超时时间才更有效?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值