Go语言中的软件架构:HTTP服务器中的弹性

67 阅读4分钟

什么是弹性?

根据维基百科的说法,Resilience (强调是我的)是。

......在面对故障和对正常操作的挑战时,提供和维持可接受的服务水平的能力。

或者换句话说,它意味着能够处理错误,使我们的服务仍然能够运行;这些错误可能来自我们的实现内部,也可能是由我们从客户那里收到的输入引起的。

在这篇文章中,我将专注于HTTP服务器,更具体地说,就是Go中标准库的类型 net/http.Server是Go中标准库的一部分,以及我们在使用它时需要说明的配置选项。超时。

net/http.Server 中的超时

net/http.Server 类型包括不同的超时字段,用于配置请求应该花费多少时间,这取决于连接目前所处的步骤,确保这些值配置正确,可以让我们的 HTTP 服务器正确地决定何时放弃连接。

  • ReadHeaderTimeout:是允许读取请求头的时间量。
  • ReadTimeout: 是读取整个请求的最大时间,包括正文。
  • WriteTimeout:是超时写入响应前的最大持续时间。
  • IdleTimeout:是启用keep-alives时等待下一个请求的最大时间。

下面的图表应该能让我们更清楚地了解这些超时的使用情况。

net/http.Server timeouts

与这些超时字段相关的另一个有趣的函数,叫做 http.TimeoutHandler的函数,在某些情况下可能很有用,我们想为具体的处理程序指定一个更细化的超时。

让我们看一下下面这个简单的HTTP服务器的例子。

 1func main() {
 2	router := httprouter.New()
 3	router.POST("/hello", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 4		body, err := io.ReadAll(r.Body)
 5		if err != nil {
 6			fmt.Println("io.ReadAll", err)
 7
 8			return
 9		}
10
11		fmt.Println("written")
12
13		fmt.Fprint(w, "Hello ", string(body))
14	})
15
16	s := &http.Server{
17		Addr:        ":8080",
18		Handler:     router,
19	}
20
21	log.Fatal(s.ListenAndServe())
22}
  • L2-14:使用httprouter定义一个POST处理程序(你也可以使用标准库,但我想更清楚地说明使用POST )。
  • L16-22:服务器被初始化并准备好监听连接。

默认情况下,所有的超时值都使用零值,这意味着在实践中所有的超时值都表示没有超时。这种默认配置在我们有慢的客户或试图破坏我们服务的假想的坏演员的情况下是很危险的。

以我写的模拟慢速调用的坏演员客户端为例,将其与上面的服务器代码一起运行。你会注意到服务器处理程序本身会花费客户需要的时间来完成请求,这很好,因为我们想接收客户发送的完整的有效载荷,但是考虑到成千上万的客户都在做同样的事情,我们的服务器会开始退化,最终无法处理其他流量。

如果我们回去更新我们的服务器,修改s ,在第4行加入一个具体的ReadTimeout值。

1	s := &http.Server{
2		Addr:        ":8080",
3		Handler:     router,
4		ReadTimeout: 500 * time.Millisecond,
5	}

那么我们的服务器将防止类似的问题。同样,在编写HTTP服务器时,也应该考虑其他的超时字段,通常我使用的配置是如下。

ReadTimeout:  100 * time.Millisecond,
WriteTimeout: 100 * time.Millisecond,

下一个例子涵盖了我们想对一些处理程序应用更细化的超时的情况,对于这些情况,我们可以使用http.TimeoutHandler ,比如。

router.Handler(http.MethodPost, "/slow",
	http.TimeoutHandler(http.HandlerFunc(slowHandler), 2*time.Second, "Request took too long"))

其中slowHandler 被定义为。

 1	slowHandler := func(w http.ResponseWriter, r *http.Request) {
 2		body, err := io.ReadAll(r.Body)
 3		if err != nil {
 4			fmt.Println("io.ReadAll", err)
 5
 6			return
 7		}
 8		defer r.Body.Close()
 9
10		fmt.Println("Sleeping...")
11
12		time.Sleep(3 * time.Second)
13
14		fmt.Fprintf(w, "H.e.l.l.o %s", string(body))
15	}

上面的处理程序将总是返回一个超时错误,因为第12行是违规的,如果我们把它改成比我们之前使用的更短的内容(2*time.Second ),那么处理程序将能够按照预期完成。

总结

为了在Go中建立Resilient HTTP服务器,我们必须,除其他外,定义具体的超时值,这样我们可以防止客户过度使用资源,以及在多个客户试图访问我们的服务时可能出现的错误。