thrift clientレポートEOFのエラー

2450 ワード

最近、同僚がオンラインに変更したことがあります.しばらくはサービスBと呼ばれ、その上流はサービスAです.サービスBはthrift serverで、オンラインになった後、サービスAはEOFエラーを報告した.これにより、以下の点が疑われます.
  • C/Sエンドプロトコルが一致しません.CRコードは、Sエンドプロトコルに新しいrequiredフィールドが追加されていることを発見しました.optionalに変更して再オンラインにしたが、EOFエラーがあった.プロトコルが間違っている場合は、各リクエストが失敗するはずですが、一部の失敗にすぎず、他の問題があるはずです.(注:プロトコルが一致しないとEOFが現れるという記事がありますが、goでテストしました.S端reqにrequiredフィールドを追加すると、s端はerror processing request: Not enought frame size、s端の新しいフィールドはランダム値で、c端はすべて正常です.C端reqにrequiredフィールドを追加すると、S端は要求を受け取れず、C端はread io timeoutを提示します)
  • S端子が接続を閉じると、C端子がreadを続け、EOFが読み出されます.サービスBはどのような場合に自発的に接続をオフにしますか?タイムアウトをチェックして大丈夫です.プロセスの開始時間を確認したが、再起動は中止されなかった.thriftソースコードと組み合わせて、Bがリクエストを処理するときにpanicしたが、server全体がpanicしていないため、一部の失敗が発生する可能性があります.Bのhandlerをチェックすると、最初からdefer funcがあり、主に要求と応答を印刷し、このコモンスタックの下層のpanicを受け取るためです.しかし、書き方に問題があります.大体
  • です.
    defer func() {
            breq, _ := json.Marshal(*request)
            bresp, _ := json.Marshal(*response)
            logger.Info("inout||req=%+v||resp=%+v||trace_id=%v", string(req), string(resp), traceId)
            if err := recover(); err != nil {
                // log
            }
    }()
    

    ログを見ると、panicの記録は見つかりませんでした.ここではポインタがオブジェクトを取る操作が見られますが、この操作にはリスクがあります.ポインタが空の場合panicになります.requestは空でないことを保証できますが、responseは後の操作で生成され、空であるかどうかは保証できません.だから疑問点はここに落ちた.ログを追加してオンラインにすると、確かに空の場合があります.実はgoの中のMarshalパラメータはポインタでいいので、オブジェクトを取る必要はありません.オブジェクトを取る操作を外してからオンラインにして、問題が修復されました.
    ここのpanicは、どのようにして接続が閉じてもserverが生存しているのでしょうか.これらのコードを見てみましょう.
    ServerのAcceptLoopでは、新規接続のための個別のコプロセッサが実行されます
    go func() {
            if err := p.processRequests(client); err != nil {
                log.Println("error processing request:", err)
            }
    }()
    

    processRequests関数では、handler関数が最終的に呼び出されます.後者はpanicで、ここのProcess()まで上に投げます.ここでpanicに遭遇するとreturnされ、その前にdeferスタック関数を順次終了して実行します.ここでは実際にtransportつまりsocketを閉じていることがわかります.スタックの上部にrecoverがpanicを接続しているため、serverは削除されず、現在の接続にのみ影響します.サービスAはEOFを受け取ります.
    defer func() {
            if e := recover(); e != nil {
                log.Printf("panic in processor: %s: %s", e, debug.Stack())
            }
    }()
    if inputTransport != nil {
        defer inputTransport.Close()
    }
    if outputTransport != nil {
        defer outputTransport.Close()
    }
    for {
        ok, err := processor.Process(inputProtocol, outputProtocol)
        //...
    }
    

    またgoでのtcpプログラミングも検証されており,serverが要求を受信すると直接conn.Close(),clientはEOFと読み,ここでは後述しない.