使用并发方法执行超时

时间:2017-11-19 00:36:00

标签: api go concurrency timeout

我正在构建一个使用一些外部api来获取数据的go应用程序。不幸的是我的一个来源非常不可靠,所以我想为我的api调用实现一个超时功能,这样我的应用程序就不会被卡住了。不幸的是,我不知道如何开始。请你帮助我好吗?谢谢!

go func() {
        for {

            ch := make(chan bool, 1)
            defer close(ch)

            apiCall1()

            apiCall2()

            apiCall3()
        }
    }()

3 个答案:

答案 0 :(得分:2)

如果您的API调用是http请求,则实现超时的最佳方法是在http客户端本身中指定一个。这样,http.Get(url)将超时并返回可以适当执行的​​错误。默认的http客户端具有极长的超时,可能导致应用程序挂起。阅读this以获得对此的良好描述。

以下是apiCall1()的示例实现,如果发生错误则返回错误,包括如果在10秒内未收到响应则超时。

func apiCall1() error {
    var netClient = &http.Client{
        Timeout: time.Second * 10,
    }

    resp, err := netClient.Get("http://someurl.com/apiEndpoint")

    if err != nil {
        return err
    }

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("http request returned code %d", resp.StatusCode)
    }

    return handleResponse(resp) // Return error if there is one, nil if not.
}

func handleResponse(resp *http.Response) error {
    ...
}

当您致电apiCall1()时,您可以随时处理错误。

if err := apiCall1(); err != nil {
    log.Print(err)
}

问题的并发性方面有点不清楚,但我会留下两个有一些想法的要点:

答案 1 :(得分:1)

你可以使用一种叫做断路器设计的东西,如果你想要完整的证据和强大的参考: - https://godoc.org/github.com/rubyist/circuitbreaker - https://martinfowler.com/bliki/CircuitBreaker.html

OR

创建这样的东西:

package main

import "fmt"
import "time"

func apiCall(u string, checked chan<- bool) {
    time.Sleep(4 * time.Second)
    checked <- true
}


func call(urls []string) bool {

    ch := make(chan bool, 1)
    for _, url := range urls {
        go func(u string) {
            checked := make(chan bool)
            go apiCall(u, checked)
            select {
            case ret := <-checked:
                ch <- ret
            case <-time.After(5 * time.Second):
                ch <- false
            }
        }(url)
    }
    return <-ch
}

func main() {
    fmt.Println(call([]string{"url1"}))
}

答案 2 :(得分:1)

研究anyCall()函数所有的魔法都在其中:) 在这里,我假设你想要3个电话中的一个或超时中的任何一个回复。

https://play.golang.org/p/b-XREKSnP1

package main

import (
    "fmt"
    "math/rand"
    "time"
)

var sluggishness = 10

// your 3 synchronous unreliable api sources
// time.Sleep() is hard work of your api backends :)
func apiCall1() string {
    time.Sleep(time.Second * time.Duration(rand.Intn(sluggishness)))
    return "api call 1"
}

func apiCall2() string {
    time.Sleep(time.Second * time.Duration(rand.Intn(sluggishness)))
    return "api call 2"
}

func apiCall3() string {
    time.Sleep(time.Second * time.Duration(rand.Intn(sluggishness)))
    return "api call 3"
}

// apiCall makes 3 calls concurrently and returns first
func anyCall() string {
    // our communicaton channels
    api1ret := make(chan string)
    api2ret := make(chan string)
    api3ret := make(chan string)

    // here we fire off 3 api calls concurrently
    go func() {
        // call and block till we get reply
        api1ret <- apiCall1()
        // close channel after we are done
        // since we are only sending one value
        defer close(api1ret)
    }()
    go func() {
        api2ret <- apiCall2()
        defer close(api2ret)
    }()
    go func() {
        api3ret <- apiCall3()
        defer close(api3ret)
    }()

    // select blocks till one of channels unblocks with a value
    // or time.After() unblocks after 5 sec
    select {
    case val := <-api1ret:
        return val

    case val := <-api2ret:
        return val

    case val := <-api3ret:
        return val

    case <-time.After(time.Second * 5):
        return "timeout after 5 sec"
    }
}

func main() {
    // make the apiCall 10 times
    for i := 0; i < 10; i++ {
        fmt.Println(anyCall())
    }

    fmt.Println("done")
}

这是一个简单的例子,假设apiCall1 / 2/3总是最终在合理的时间内返回。对于这些调用可能会阻塞很长时间的情况,您可能需要更复杂的方案。由于所有go func()次呼叫最终会累积到大量泄漏。

玩得开心!