英文:
golang: strategies to prevent connection reset by peer errors
问题
该程序同时生成了许多goroutine(getStock),我认为这导致远程服务器立即断开连接。我并不想创建DOS攻击,但我仍然希望积极获取数据而不出现“连接重置”错误。
有哪些策略可以最多只有N(例如20)个同时连接?golang Http客户端中是否有内置的GET请求队列?我还在学习go,如果有更好的设计模式来处理这种类型的代码,那将是很好的。
输出
$ go run s1w.go
sl(size): 1280
body: "AAPL",17.92
body: "GOOG",32.13
body: "FB",42.02
body: "AMZN",195.83
body: "GOOG",32.13
body: "AMZN",195.83
body: "GOOG",32.13
body: "FB",42.02
body: "AAPL",17.92
2017/07/26 00:01:23 NFLX: Get http://goanuj.freeshell.org/go/NFLX.txt: read tcp 192.168.86.28:56674->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 AAPL: Get http://goanuj.freeshell.org/go/AAPL.txt: read tcp 192.168.86.28:56574->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 NFLX: Get http://goanuj.freeshell.org/go/NFLX.txt: read tcp 192.168.86.28:56760->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 FB: Get http://goanuj.freeshell.org/go/FB.txt: read tcp 192.168.86.28:56688->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 AMZN: Get http://goanuj.freeshell.org/go/AMZN.txt: read tcp 192.168.86.28:56689->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 AAPL: Get http://goanuj.freeshell.org/go/AAPL.txt: read tcp 192.168.86.28:56702->205.166.94.30:80: read: connection reset by peer
s1.go
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
// https://www.youtube.com/watch?v=f6kdp27TYZs (15m)
// Generator: function that returns a channel
func getStocks(sl []string) <-chan string {
c := make(chan string)
for _, s := range sl {
go getStock(s, c)
}
return c
}
func getStock(s string, c chan string) {
resp, err := http.Get("http://goanuj.freeshell.org/go/" + s + ".txt")
if err != nil {
log.Printf(s + ": " + err.Error())
c <- err.Error() // channel send
return
}
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close() // close ASAP to prevent too many open file desriptors
val := string(body)
//fmt.Printf("body: %s", val)
c <- val // channel send
return
}
func main() {
start := time.Now()
var sl = []string{"AAPL", "AMZN", "GOOG", "FB", "NFLX"}
// creates slice of 1280 elements
for i := 0; i < 8; i++ {
sl = append(sl, sl...)
}
fmt.Printf("sl(size): %d\n", len(sl))
// get channel that returns only strings
c := getStocks(sl)
for i := 0; i < len(sl); i++ {
fmt.Printf("%s", <-c) // channel recv
}
fmt.Printf("main: %.2fs elapsed.\n", time.Since(start).Seconds())
}
英文:
The program is spawning many goroutines (getStock) simultaneously which, I believe, is resulting in the remote server immediately dropping the connection. I am not trying to create a DOS, but I still want to aggressively get data without getting 'connection reset' errors.
What are some strategies to only have at most N (eg. 20) simultaneous connections? Is there a built-in queue for GET requests in the golang Http client? I'm still learning go it would be great to understand if there are better design patterns for this type of code.
Output
$ go run s1w.go
sl(size): 1280
body: "AAPL",17.92
body: "GOOG",32.13
body: "FB",42.02
body: "AMZN",195.83
body: "GOOG",32.13
body: "AMZN",195.83
body: "GOOG",32.13
body: "FB",42.02
body: "AAPL",17.92
2017/07/26 00:01:23 NFLX: Get http://goanuj.freeshell.org/go/NFLX.txt: read tcp 192.168.86.28:56674->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 AAPL: Get http://goanuj.freeshell.org/go/AAPL.txt: read tcp 192.168.86.28:56574->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 NFLX: Get http://goanuj.freeshell.org/go/NFLX.txt: read tcp 192.168.86.28:56760->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 FB: Get http://goanuj.freeshell.org/go/FB.txt: read tcp 192.168.86.28:56688->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 AMZN: Get http://goanuj.freeshell.org/go/AMZN.txt: read tcp 192.168.86.28:56689->205.166.94.30:80: read: connection reset by peer
2017/07/26 00:01:23 AAPL: Get http://goanuj.freeshell.org/go/AAPL.txt: read tcp 192.168.86.28:56702->205.166.94.30:80: read: connection reset by peer
s1.go
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
// https://www.youtube.com/watch?v=f6kdp27TYZs (15m)
// Generator: function that returns a channel
func getStocks(sl []string) <-chan string {
c := make(chan string)
for _, s := range sl {
go getStock(s, c)
}
return c
}
func getStock(s string, c chan string) {
resp, err := http.Get("http://goanuj.freeshell.org/go/" + s + ".txt")
if err != nil {
log.Printf(s + ": " + err.Error())
c <- err.Error() // channel send
return
}
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close() // close ASAP to prevent too many open file desriptors
val := string(body)
//fmt.Printf("body: %s", val)
c <- val // channel send
return
}
func main() {
start := time.Now()
var sl = []string{"AAPL", "AMZN", "GOOG", "FB", "NFLX"}
// creates slice of 1280 elements
for i := 0; i < 8; i++ {
sl = append(sl, sl...)
}
fmt.Printf("sl(size): %d\n", len(sl))
// get channel that returns only strings
c := getStocks(sl)
for i := 0; i < len(sl); i++ {
fmt.Printf("%s", <-c) // channel recv
}
fmt.Printf("main: %.2fs elapsed.\n", time.Since(start).Seconds())
}
答案1
得分: 2
不要为每个请求启动新的goroutine,而是在程序启动时创建一个固定的goroutine池,并通过共享通道传递订单。每个订单都将是一个与当前传递给getStock
的参数相对应的结构体。如果您需要能够终止该池,情况会变得更加复杂,但仍然不是很难...
基本上,您的新处理程序将是一个循环,从所有处理程序共享的通道中读取订单,执行它,然后将结果发送到订单的响应通道上。
英文:
Instead of spinning up new goroutines for every request, create a fixed pool when your program starts and pass in orders over a shared channel. Each order would be a struct corresponding to the parameters currently passed to getStock
. Things get more complicated if you need to be able to kill the pool, but it still isn't that hard...
Basically your new handler would be a loop, reading an order from a channel shared by all handlers, executing it, then sending the result on the order's response channel.
答案2
得分: 0
你需要使用缓冲通道来限制并行操作的数量。在循环中启动新的goroutine之前,你需要将其发送到该通道,并在调用done后从该通道接收,这样它将释放一个位置,新的请求将能够开始。请查看你修改后的代码在playground上的链接。
英文:
You need to use a buffered channel to limit amount of parallel operations. Before starting a new goroutine in the loop you need to send into this channel and receive from it after call done, so it will release one place and new request will be able to start. Check out yours modified code on playground
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论