0

I have the following code

package main

import (
    "bytes"
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "time"
    "io"
    httprouter "github.com/fasthttp/router"
    "github.com/valyala/fasthttp"
)

func main() {
    router := mux.NewRouter().StrictSlash(true)
    /*router := NewRouter()*/
    router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, _ = fmt.Fprintf(w, "Hello!!!")
    })

    router.HandleFunc("/{name}", func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        prepare(w, r, vars["name"])

    }).Methods("POST")

    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", 8080), router))

}

//using fast http 
func _() {
    router := httprouter.New()
    router.GET("/", func(w *fasthttp.RequestCtx) {
        _, _ = fmt.Fprintf(w, "Hello!!!")
    })
    router.POST("/:name", func(w *fasthttp.RequestCtx) {
        prepareRequest(w, w.UserValue("name").(string))
    })

    log.Fatal(fasthttp.ListenAndServe(fmt.Sprintf(":%d", 8080), router.Handler))
}

//func prepare(w *fasthttp.RequestCtx, name string)
func prepare(w http.ResponseWriter, r *http.Request, name string) {
    //other part of the code and call to goroutine
    var urls []string
    //lets say all the url loaded, call the go routine func and wait for channel to respond and then proceed with the response of all url
    results := callUrls(urls) //there are 10 urls atleast to call simultaneously for each request everytime
    process(w, results)
}

type Response struct {
    status          int
    url             string
    body            string
}

func callUrls(urls []string) []*Response {
    ch := make(chan *Response, len(urls))
    for _, url := range urls {
        go func(url string) {
            //http post on url,
            //base on status code of url call, add to status code
            //some thing like

            req, err := http.NewRequest("POST", url, bytes.NewBuffer(somePostData))
            req.Header.Set("Content-Type", "application/json")
            req.Close = true

            client := &http.Client{
                Timeout: time.Duration(time.Duration(100) * time.Millisecond),
            }

            response, err := client.Do(req)

            //Using fast http client
            /*req := fasthttp.AcquireRequest()
                req.SetRequestURI(url)
                req.Header.Set("Content-Type", "application/json")
                req.Header.SetMethod("POST")
                req.SetBody(somePostData)

                response := fasthttp.AcquireResponse()
                client := &fasthttp.Client{
                    ReadTimeout: time.Duration(time.Duration(100) * time.Millisecond),
                }
            err := client.Do(req, response)*/

            if err != nil {
                //do other thing with the response received
                _, _ = io.Copy(ioutil.Discard, response.Body)
                _ = response.Body.Close()
            } else {
                //success response
                _, _ = io.Copy(ioutil.Discard, response.Body)
                _ = response.Body.Close()

                body, _:= ioutil.ReadAll(response.Body)
                strBody := string(body)
                strBody = strings.Replace(strBody, "\r", "", -1)
                strBody = strings.Replace(strBody, "\n", "", -1)    
            }

            // return to channel accordingly
            ch <- &Response{200, "url", "response body"}

        }(url)
    }
    var results []*Response
    for {
        select {
        case r := <-ch:
            results = append(results, r)
            if len(results) == len(urls) {
                //Done
                close(ch)
                return results
            }
        }
    }
}

//func process(w *fasthttp.RequestCtx,results []*Response){
func process(w http.ResponseWriter, results []*Response){
    fmt.Println("response", "response body")
}

After serving few request on multi core CPU (there are around 4000-6000 req coming per sec) I get too many files open error and response time and CPU goes beyond limit. (Could CPU be be high because I convert byte to string a few times to replace few character? Any suggestion?)

I have seen other question referring to closing req/res body and/or setting sysctl or ulimit to higher values, I did follow those but I always end up with the error.

Config on the server:

/etc/sysctl.conf net.ipv4.tcp_tw_recycle = 1
open files (-n) 65535

I need the code to respond in millisec but it take upto 50sec when cpu is high.

Have tried both net/http and fast http but with no improvement. My Node.js request npm does everything perfectly on the same server. What will be best way to handle those connection or change in the code needed for improvement.

user3677365
  • 95
  • 2
  • 13
  • 3
    You say you're processing 6000 req/sec, each of which triggers at least 10 concurrent outgoing requests with a timeout of 100 seconds, no connection reuse, and no concurrency limit. It's no surprise that 60k open files doesn't cut it. – Peter Aug 23 '19 at 07:41
  • Yes that's the question I had keep-alive on the client still It didn't help. Also has req.Close = true as read on some post. So I am unsure how to re-use connection and If increasing ulimit is only option? – user3677365 Aug 23 '19 at 07:44
  • Also 100ms timeout can go upto 275ms. It depend upn the url being called using some logic. – user3677365 Aug 23 '19 at 07:50
  • 1
    You reuse connections by using a single http.Client (or at least a single http.Transport). Obviously this only helps if a sufficient number of requests are actually for the same host. Otherwise you must increase the file limit at least by a factor of 100, or reduce any of the factors that contribute to the number of concurrent connections. We're not really in a position to make that decision. – Peter Aug 23 '19 at 07:54
  • The timeout isn't 100 milliseconds, it's 100 *seconds*. – Peter Aug 23 '19 at 07:57
  • Sorry, typo there. its actually `time.Millisecond`. Edited – user3677365 Aug 23 '19 at 10:22

1 Answers1

0

You can use the following library:

Requests: A Go library for reduce the headache when making HTTP requests (20k/s req)

https://github.com/alessiosavi/Requests

It's developed for solve theto many open files dealing with parallel requests.

The idea is to allocate a list of request, than send them with a configurable "parallel" factor that allow to run only "N" request at time.

Initialize the requests (you have already a set of urls)

// This array will contains the list of request
var reqs []requests.Request

// N is the number of request to run in parallel, in order to avoid "TO MANY OPEN FILES. N have to be lower than ulimit threshold"
var N int = 12

// Create the list of request
for i := 0; i < 1000; i++ {
    // In this case, we init 1000 request with same URL,METHOD,BODY,HEADERS 
    req, err := requests.InitRequest("https://127.0.0.1:5000", "GET", nil, nil, true) 
    if err != nil {
        // Request is not compliant, and will not be add to the list
        log.Println("Skipping request [", i, "]. Error: ", err)
    } else {
        // If no error occurs, we can append the request created to the list of request that we need to send
        reqs = append(reqs, *req)
    }
}

At this point, we have a list that contains the requests that have to be sent. Let's send them in parallel!

// This array will contains the response from the givens request
var response []datastructure.Response

// send the request using N request to send in parallel
response = requests.ParallelRequest(reqs, N)

// Print the response
for i := range response {
    // Dump is a method that print every information related to the response
    log.Println("Request [", i, "] -> ", response[i].Dump())
    // Or use the data present in the response
    log.Println("Headers: ", response[i].Headers)
    log.Println("Status code: ", response[i].StatusCode)
    log.Println("Time elapsed: ", response[i].Time)
    log.Println("Error: ", response[i].Error)
    log.Println("Body: ", string(response[i].Body))
}

You can find example usage into the example folder of the repository.

SPOILER:

I'm the author of this little library

alessiosavi
  • 2,753
  • 2
  • 19
  • 38