问题
我正在使用另一个 goroutine,因为我不希望处理发生在 http 请求-响应周期中。
这是一个常见的谬误(因此也是陷阱)。推理思路似乎是合理的:您正在尝试“在其他地方”处理请求,以尝试
尽快处理入口 HTTP 请求。
问题是“其他地方”仍然是一些代码同时运行以及其余的请求处理流程。
因此,如果该代码运行slower比入口请求的速率,
你的处理 goroutine 会堆积起来,基本上会耗尽一个或
更多资源。具体取决于实际处理:
如果它受 CPU 限制,则会造成 CPU 的自然争用
在所有这些之间GOMAXPROCS
执行的硬件线程;
如果它绑定到网络 I/O,它将在 Go 运行时调度程序上创建负载,该调度程序必须划分其可用的执行量子
在所有那些想要被执行的 goroutine 之间;
如果它绑定到磁盘 I/O 或其他系统调用,您将拥有
创建的操作系统线程激增,等等……
本质上,you are queueing工作单位转换自
入口 HTTP 请求,但是队列不能解决过载问题。它们可能被用来吸收过载的短尖峰,
但这仅在此类尖峰被周期“包围”时才有效
负载至少略低于您提供的最大容量
系统。
您排队的事实在您的案例中并没有直接看到,但它是
在那里,它通过迫使你的系统超越其自然状态而表现出来
容量——你的“队列”开始无限增长。
请阅读这篇经典文章 https://ferd.ca/queues-don-t-fix-overload.html仔细了解为什么你的方法行不通
在现实的生产环境中工作。
密切注意厨房水槽的那些照片。
该怎么办?
不幸的是,几乎不可能给出简单的解决方案
因为我们不会在您的设置中使用您的代码来处理您的工作负载。
尽管如此,这里还是有几个值得探索的方向。
在最广泛的范围内,试着看看你是否有一些容易
您当前无法看到的系统中明显的瓶颈。
例如,如果所有这些并发工作协程最终
与 RDBM 实例对话,其磁盘 I/O 可能很容易连载所有那些只会等待轮到他们的 goroutine
他们的数据被接受。
瓶颈可能更简单——比如,在每个工作协程中
您在持有锁时不小心执行了一些长时间运行的操作
被所有这些 goroutine 竞争;
这显然将它们全部序列化。
下一步将是实际measure(我的意思是,通过编写基准)
单个工人需要多少时间才能完成其工作单元。
然后你需要测量当增加时这个数字如何变化
并发因素。
收集这些数据后,您将能够做到
有根据的预测实际的评价您的系统
能够处理请求。
下一步是仔细考虑制作系统的策略
满足这些计算出的期望。通常这意味着限制速率
的入口请求。有不同的方法可以实现这一目标。
看着golang.org/x/time/rate https://godoc.org/golang.org/x/time/rate基于时间的速率限制器,但可以从技术含量较低的开始
诸如使用缓冲通道作为计数信号量等方法。
超出您能力范围的请求可能会被拒绝
(通常带有 HTTP 状态代码 429,请参阅this https://www.rfc-editor.org/rfc/rfc6585#section-4)。
你也可以考虑短暂排队,但我只会尝试这个
充当馅饼上的樱桃——也就是说,当你拥有其余的时候
完全整理出来了。
如何处理被拒绝的请求取决于您的情况
环境。通常,您会尝试通过部署更多内容来“水平扩展”
不仅仅是一项服务来处理您的请求并指导您的客户
切换可用的服务。 (我要强调的是,这意味着几个独立的服务——如果它们都共享一些收集数据的目标接收器
他们的数据,他们可能受到该接收器的最终容量的限制,
添加更多系统不会给你带来任何好处。)
让我再说一遍,一般问题没有神奇的解决方案:
如果你的完整系统(使用你正在编写的这个 HTTPservice)
只是它的前端、网关、部分)只能处理N
负载的RPS,
没有任何散射go processRequest()
将会成功
更快地处理请求。 Go 提供的简单并发并不是
A银子弹 https://en.wikipedia.org/wiki/No_Silver_Bullet,
这是一把机枪。