ホームページ  >  記事  >  バックエンド開発  >  Goのタイムアウト制御の実装方法を詳しく解説

Goのタイムアウト制御の実装方法を詳しく解説

藏色散人
藏色散人転載
2021-04-06 11:18:293816ブラウズ

次のチュートリアル コラムでは、Go タイムアウト制御の実装方法を紹介します。

Goのタイムアウト制御の実装方法を詳しく解説なぜタイムアウト制御が必要なのでしょうか?

リクエスト時間が長すぎます。ユーザーはこのページから離れた可能性があります。サーバーはまだ処理のためにリソースを消費しており、得られる結果は無意味です。

サーバー側の処理時間がかかりすぎる 占有するリソースが多すぎるため、同時実行性が低下し、さらには利用不能になる事故も発生する
  • #Go タイムアウト制御の必要性
Go は通常、バックエンド サービスの場合、通常、リクエストは複数のシリアルまたはパラレルのサブタスクによって完了します。各サブタスクは別の内部リクエストである可能性があります。その後、リクエストがタイムアウトになったら、すぐに戻って占有されているリソースを解放する必要があります。ゴルーチン、ファイル記述子など。

#サーバー側の共通タイムアウト制御

プロセス内の論理処理

読み取りおよびクライアント側リクエストの作成 (HTTP または RPC リクエストなど)

他のサーバー側リクエストの呼び出し (RPC の呼び出しや DB へのアクセスなど)
  • 何が行われるかタイムアウト制御がない場合はどうなるでしょうか?
  • この記事を簡略化するために、リクエスト関数
hardWork

を例として取り上げます。これが何に使用されるかは関係ありません。名前が示すように、処理が遅くなる可能性があります。
func hardWork(job interface{}) error {
    time.Sleep(time.Minute)
    return nil}func requestWork(ctx context.Context, job interface{}) error {
  return hardWork(job)}
現時点で、クライアントが見ているのは常に見慣れた写真です

ユーザーの大多数は、一分たりとも菊の花を見て、何かを与えるつもりはありません。早めに終了する場合は、通話リンク全体に大量のリソースが占有されたままになります。この記事ではその他の詳細には触れず、タイムアウトの実装のみに焦点を当てます。

タイムアウトの実装方法と、そこに潜む落とし穴について見てみましょう。

Goのタイムアウト制御の実装方法を詳しく解説

最初のバージョンの実装

読むのをやめて、この関数のタイムアウトを実装する方法を考えてみてください。最初に試してみましょう:

func requestWork(ctx context.Context, job interface{}) error {
    ctx, cancel := context.WithTimeout(ctx, time.Second*2)
    defer cancel()

    done := make(chan error)
    go func() {
        done <h2>Let'sテストするために main 関数を作成してください<span class="header-link octicon octicon-link"><pre class="brush:php;toolbar:false">func main() {
    const total = 1000
    var wg sync.WaitGroup
    wg.Add(total)
    now := time.Now()
    for i := 0; i 
実行して効果を確認してください
➜ go run timeout.go
elapsed: 2.005725931s

タイムアウトが発生しました。しかし、これは終わったでしょうか?

ゴルーチンのリーク

main 関数の最後にコード行を追加して、実行されたゴルーチンの数を確認しましょう

time.Sleep(time.Minute*2)fmt.Println("number of goroutines:", runtime.NumGoroutine())

sleep 2 分間待つとすべてのタスクが終了し、現在のゴルーチン数を出力します。実行して結果を見てみましょう
➜ go run timeout.go
elapsed: 2.005725931s
number of goroutines: 1001
groutin がリークされています。なぜこれが起こるのか見てみましょう。まず、

requestWork

関数は 2 秒のタイムアウト後に終了します。

requestWork

関数が終了すると、

done チャネル

にはそれを受信するゴルーチンがなくなります。実行done このコード行はスタックして書き込めなくなり、各タイムアウト要求がゴルーチンを占有することになります。これは大きなバグです。リソースが使い果たされると、時間が経過すると、サービス全体が応答しなくなります。 <code>それではどうやって修正すればい​​いのでしょうか?実際、これは非常に簡単で、次のように make chan のときに バッファ サイズ を 1 に設定するだけです:

done := make(chan error, 1)

This way, done タイムアウトに関係なく、ゴルーチンをブロックせずに書き込むことができます。この時点で、受信するゴルーチンがないチャネルに書き込んでも問題はないのかと疑問に思う人もいるかもしれませんが、Go では、チャネルは一般的なファイル記述子とは異なります。閉じる必要はありません。 <code>close(channel) は、書き込むものが何もないことを受信者に伝えるためにのみ使用され、他の目的はありません。

このコード行を変更した後、もう一度テストしてみましょう: <pre class="brush:php;toolbar:false">➜ go run timeout.go elapsed: 2.005655146s number of goroutines: 1</pre>Goroutine リークの問題は解決されました。

パニックは捕捉できません

hardWork

関数の実装を
panic("oops")
Modify

main## に変更しましょう# 関数と例外をキャッチするコードは次のとおりです:

go func() {
  defer func() {
    if p := recover(); p != nil {
      fmt.Println("oops, panic")
    }
  }()

  defer wg.Done()
  requestWork(context.Background(), "any")}()
この時点で実行すると、パニックをキャプチャできないことがわかります。これは ## 内のゴルーチンで他のパニックが生成されているためです。 #requestWork goroutine がキャッチできません。

解決策は、処理のために panicChan

requestWork

に追加することです。同様に、panicChan

バッファ サイズ

が必要です次のように 1 になります:<pre class="brush:php;toolbar:false">func requestWork(ctx context.Context, job interface{}) error {     ctx, cancel := context.WithTimeout(ctx, time.Second*2)     defer cancel()     done := make(chan error, 1)     panicChan := make(chan interface{}, 1)     go func() {         defer func() {             if p := recover(); p != nil {                 panicChan &lt;p&gt;改完就可以在 &lt;code&gt;requestWork&lt;/code&gt; 的调用方处理 &lt;code&gt;panic&lt;/code&gt; 了。&lt;/p&gt;&lt;h2&gt; &lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;超时时长一定对吗?&lt;/h2&gt;&lt;p&gt;上面的 &lt;code&gt;requestWork&lt;/code&gt; 实现忽略了传入的 &lt;code&gt;ctx&lt;/code&gt; 参数,如果 &lt;code&gt;ctx&lt;/code&gt; 已有超时设置,我们一定要关注此传入的超时是不是小于这里给的2秒,如果小于,就需要用传入的超时,&lt;code&gt;go-zero/core/contextx&lt;/code&gt; 已经提供了方法帮我们一行代码搞定,只需修改如下:&lt;/p&gt;&lt;pre class=&quot;brush:php;toolbar:false&quot;&gt;ctx, cancel := contextx.ShrinkDeadline(ctx, time.Second*2)</pre><h2> <span class="header-link octicon octicon-link"></span>Data race</h2><p>这里 <code>requestWork 只是返回了一个 error 参数,如果需要返回多个参数,那么我们就需要注意 data race,此时可以通过锁来解决,具体实现参考 go-zero/zrpc/internal/serverinterceptors/timeoutinterceptor.go,这里不做赘述。

完整示例

package mainimport (
    "context"
    "fmt"
    "runtime"
    "sync"
    "time"

    "github.com/tal-tech/go-zero/core/contextx")func hardWork(job interface{}) error {
    time.Sleep(time.Second * 10)
    return nil}func requestWork(ctx context.Context, job interface{}) error {
    ctx, cancel := contextx.ShrinkDeadline(ctx, time.Second*2)
    defer cancel()

    done := make(chan error, 1)
    panicChan := make(chan interface{}, 1)
    go func() {
        defer func() {
            if p := recover(); p != nil {
                panicChan <h2>
<span class="header-link octicon octicon-link"></span>更多细节</h2><p>请参考 <code>go-zero</code> 源码:</p>
  • go-zero/core/fx/timeout.go
  • go-zero/zrpc/internal/clientinterceptors/timeoutinterceptor.go
  • go-zero/zrpc/internal/serverinterceptors/timeoutinterceptor.go

项目地址

github.com/tal-tech/go-zero

欢迎使用 go-zerostar 支持我们!

以上がGoのタイムアウト制御の実装方法を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。