现在我在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)
})
}
答案 0 :(得分:1)
我为此使用了稍微不同的机制,可能是这里描述的工作池:
https://gobyexample.com/worker-pools
我实际上说要保持10000个goroutines运行,(他们将等待在阻塞频道上等待接收,因此它并不是真正浪费资源),并发送请求+他们进来时回应游泳池。
如果您希望在池已满时响应错误的超时,您也可以使用select
块实现该超时。
答案 1 :(得分:1)
您概述的解决方案存在一些问题。但首先,让我们退一步吧;这有两个问题,其中一个暗示:
你想要做的事实上是第二个,以防止太多请求击中Redis。我将首先解决第一个问题,然后对第二个问题做一些评论。
如果你确实想要对入口连接进行速率限制&#34;在门口#34;你通常应该通过等待处理程序来做到这一点。使用您提出的解决方案,该服务将继续接受请求,这些请求将在sema <- struct{}{}
语句中排队。如果加载持续存在,它将最终通过耗尽套接字,内存或其他资源来关闭您的服务。另请注意,如果您的请求率接近信号量的饱和度,您会看到goroutine在处理请求之前等待信号量导致的延迟增加。
更好的方法是始终尽快回复(特别是在负载很重的情况下)。这可以通过将503 Service Unavailable
发送回客户端或智能负载均衡器,告诉它退回来完成。
在你的情况下,它可以看起来像这样的东西:
select {
case sema <- struct{}{}:
defer func() { <-sema }()
h.ServeHTTP(w, r)
default:
http.Error(w, "Overloaded", http.StatusServiceUnavailable)
}
如果速率限制的原因是为了避免后端服务过载,那么您通常要做的就是对该服务过载做出反应并在请求链中应用背压。 / p>
实际上,这可能意味着在保护所有对后端的调用的包装器中放置与上述相同类型的信号量逻辑这样简单的事情,并且如果信号量溢出则通过请求的调用链返回错误。 / p>
此外,如果后端发送状态代码如503
(或等效),您通常应以相同方式向下传播该指示,或采用其他一些后备行为来处理传入请求。
您可能还想考虑将其与circuit breaker结合使用,如果它似乎没有响应或关闭,请切断尝试快速调用后端服务。
通过限制上述并发或排队连接数来限制速率通常是处理过载的好方法。当后端服务过载时,请求通常需要更长时间,这将减少每秒的有效请求数。但是,如果由于某种原因,您希望对每秒请求数量设置一个固定限制,则可以使用rate.Limiter
而不是信号量来实现此目的。
在通道上发送和接收普通对象的成本应该是亚微秒。即使在高度拥挤的信道上,也只有与信道同步才能接近150毫秒的额外延迟。因此,假设在处理程序中完成的工作是相同的,无论您的延迟增加来自它,几乎肯定会与等待某处的goroutine相关联(例如,在I / O上或访问被其他goroutine阻止的同步区域)
如果您以接近设置的并发限制10000处理的速率获取传入请求,或者如果您的请求数量激增,则可能会看到源自goroutines的平均延迟增加在频道的等待队列中。
无论哪种方式,这都应该很容易衡量;例如,您可以在处理路径中的某些点跟踪时间戳。我会对所有请求的样本(例如0.1%)执行此操作,以避免日志输出影响性能。