2017-11-19 10 views
1

私はいくつかの外部APIを使用してデータを取得するgoアプリケーションを構築しています。残念ながら、私のソースの1つはかなり信頼できないので、私は自分のアプリケーションが詰まっていないように、私のAPI呼び出しのタイムアウト機能を実装したいと思います。残念ながら、私は始める方法を知らない。手伝っていただけませんか?ありがとう!あなたが参照する、完全な証拠と堅牢になりたい場合は並行メソッドでタイムアウトを実行する

go func() { 
     for { 

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

      apiCall1() 

      apiCall2() 

      apiCall3() 
     } 
    }() 
+2

タイムアウトすることはできますか?それぞれの個別の電話は? –

+0

https://gobyexample.com/timeouts – dm03514

+0

はい、私はそれぞれの呼び出しをタイムアウトしたいと思います – donfrigo

答えて

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"})) 
} 
+0

これは['Context.WithTimeout'](https://golang.org/pkg/context/#)を使ってもっと慣用的に実装されますWithTimeout)。 – Adrian

+0

さて、それは良い方法かもしれないと思う。 –

1

研究anyCall()すべての魔法が機能しています:) ここでは、3つのコールの1つまたはタイムアウトのいずれかからの返信を希望すると仮定しています。

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()コールが最終的に大量のリークに累積するためです。

楽しくお楽しみください!

+1

これは、['Context.WithTimeout'](https://golang.org/pkg/context/#WithTimeout)を使ってより慣用的に実装されます。 – Adrian

+0

@Adrianはそんな感じですか? https://play.golang.org/p/f38hjqXtqJ – biosckon

2

API呼び出しがhttp要求の場合、タイムアウトを実装する最も良い方法は、httpクライアント自体にAPI呼び出しを指定することです。そうすれば、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) 
} 

質問の同時実行性の側面は少し不明であるが、私はいくつかのアイデアを持つ2つの箇条書きを残しておきます:

  • go apiCall1()をしてapiCall1()機能にエラー/タイムアウトを処理します。
  • https://play.golang.org/p/klee4hwulNこれは完璧ではありませんが、WaitGroupを使用して同時通話を行う方法を示しています。
+0

これは最も簡単な解決策です。これは、他の回答の複雑さが増した理由がない限り、私の推薦です。 – Adrian

関連する問題