go - 有没有更好的方法限制 "door"的请求?

标签 go server semaphore

现在,我正在 AWS 的一个生产区域中测试一个极其简单的信号量。在部署时,延迟从 150 毫秒跃升至 300 毫秒。我假设会发生延迟,但如果它可以被删除那就太好了。这对我来说有点新,所以我正在试验。我已将信号量设置为允许 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)
    })
}

最佳答案

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

  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)
}

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

如果速率限制的原因是避免后端服务重载,您通常想要做的是对该服务重载使用react,并通过请求链施加背压

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

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

您可能还想考虑将其与 circuit breaker 结合使用, 如果后端服务似乎没有响应或关闭,则停止尝试快速调用后端服务。

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

性能评价

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

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

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

关于go - 有没有更好的方法限制 "door"的请求?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42957767/

相关文章:

go - channel 是否返回两个值?

go - 可以在Go中的zipfile对象中创建目录吗?

android - Android Service在后台数据丢失时运行

tcp - 在 goroutine 中保持 TCP 连接有效,并在连接丢失时检查它是否超时

arrays - 切割排序 slice 的最佳方法

go - 包源之间的循环依赖

arrays - Swift - json 嵌套数组不返回值

Java线程通信

使用信号量的 Java 男女通用浴室程序 - 逻辑问题

c - 关于使用多个信号量进行同步