是否有更好的方法来限制"门"的请求?

ret*_*oot 8 semaphore go server

现在我正在AWS中的一个生产区域测试一个非常简单的信号量.在部署时,延迟从150ms跳到300ms.我假设会发生延迟,但是如果它可以被丢弃那就太好了.这对我来说有点新鲜,所以我正在尝试.我已设置信号量以允许10000个连接.这与Redis设置的最大连接数相同.代码是否最佳?如果没有,有人可以帮助我优化它,如果我做错了等等.我想把它作为一个中间件保存,这样我就可以在服务器上简单地调用它n.UseHandler(wrappers.DoorMan(wrappers.DefaultHeaders(myRouter), 10000)).

package wrappers

import "net/http"

// DoorMan limit requests
func DoorMan(h http.Handler, n int) http.Handler {
    sema := make(chan struct{}, n)

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        sema <- struct{}{}
        defer func() { <-sema }()

        h.ServeHTTP(w, r)
    })
}
Run Code Online (Sandbox Code Playgroud)

Jos*_*ahn 2

您概述的解决方案存在一些问题。但首先,让我们退一步;这里有两个问题,其中一个隐含着:

  1. 如何有效地限制入站连接速率?
  2. 如何防止出站连接使后端服务过载?

听起来你想做的实际上是第二个,防止太多请求到达 Redis。我将从解决第一个问题开始,然后对第二个问题发表一些评论。

限制入站连接速率

如果您确实想在“门口”限制入站连接的速率,那么您通常不应该通过在处理程序内等待来实现这一点。根据您提出的解决方案,该服务将继续接受请求,这些请求将在声明中排队sema <- struct{}{}。如果负载持续存在,它最终会通过耗尽套接字、内存或其他资源来关闭您的服务。另请注意,如果您的请求率接近信号量的饱和,您会发现由于在处理请求之前在信号量处等待的 goroutine 导致延迟增加。

更好的方法是始终尽快响应(尤其是在重负载时)。这可以通过503 Service Unavailable向客户端或智能负载平衡器发送回消息,告诉其退出来完成。

就您而言,它可能看起来像这样:

select {
case sema <- struct{}{}:
    defer func() { <-sema }()
    h.ServeHTTP(w, r)
default:
    http.Error(w, "Overloaded", http.StatusServiceUnavailable)
}
Run Code Online (Sandbox Code Playgroud)

限制后端服务的出站连接速率

如果速率限制的原因是为了避免后端服务过载,那么您通常想要做的是对该服务过载做出反应,并通过请求链施加反压。

实际上,这可能意味着简单的事情,只需将与上面相同类型的信号量逻辑放入保护对后端的所有调用的包装器中,并在信号量溢出时通过请求的调用链返回错误。

此外,如果后端发送类似503(或等效)的状态代码,您通常应该以相同的方式向下传播该指示,或者诉诸其他一些后备行为来处理传入请求。

您可能还需要考虑将其与断路器结合起来,如果后端服务似乎没有响应或关闭,则可以切断快速调用后端服务的尝试。

如上所述,通过限制并发或排队连接的数量来限制速率通常是处理过载的好方法。当后端服务过载时,请求通常会花费更长的时间,从而减少每秒的有效请求数。但是,如果出于某种原因,您希望对每秒的请求数有固定的限制,则可以使用 arate.Limiter而不是信号量来实现。

对性能的评论

在通道上发送和接收普通对象的成本应该是亚微秒。即使在高度拥塞的通道上,仅与通道同步也不会产生接近 150 毫秒的额外延迟。因此,假设处理程序中完成的工作在其他方面是相同的,无论您的延迟增加来自何处,它几乎肯定与在某处等待的 goroutine 相关(例如,在 I/O 上或访问被其他 goroutine 阻塞的同步区域) 。

如果您收到传入请求的速率接近您设置的并发限制 10000 的处理速度,或者您收到请求峰值,则您可能会看到等待中的 goroutine 导致平均延迟增加在通道上排队。

不管怎样,这应该很容易衡量;例如,您可以跟踪处理路径中某些点的时间戳。我会在所有请求的样本(例如 0.1%)上执行此操作,以避免日志输出影响性能。