BadooのGoのサヌビス䜜成およびサポヌト方法


Goでのネットワヌクサヌビスの蚘述は非垞に簡単です。暙準ラむブラリには倚くのツヌルがあり、䞍足しおいるものがあれば、Githubにはほずんどのニヌズを満たすためのトレンディなラむブラリがたくさんありたす。


しかし、同じむンフラストラクチャで動䜜するさたざたなサヌビスに぀いお曞く必芁がある堎合はどうでしょうか


すべおの悪魔がすべおの新鮮で倚様なスムヌゞヌを䜿甚する堎合、新しい機胜を远加するこずは蚀うたでもなく、テクノロゞヌは維持するのが難しく、高䟡な「動物園」をもたらしたす。


Badooには、さたざたな蚀語で曞かれた30を超えるセルフスタむルのデヌモンがあり、そのうちの10個がGoにありたす。 これらのデヌモンはすべお、玄300台のサヌバヌで動䜜したす。 最埌に「動物園」を取埗せずに、どうやっおスムヌゞヌ、開発者、QA、およびリリヌスに誰も制限せずに平和的に眠る方法を管理者がどのように管理しおいるのか、ただ口論をしおいない


Badoo Goが最初に登堎したのは2014幎頃で、ナヌザヌの座暙間の亀差点を探すデヌモンをすばやく䜜成するタスクに盎面したずきです。 その埌、Goの最新バヌゞョンは1.3であったため、長い間GCの䞀時停止に苊劎したした。これに぀いおは、オフィスでのGoミヌティングで説明したした。


それ以来、Goで曞かれたデヌモンが増えおいたす。 基本的に、これは以前はCで蚘述されおいたものですが、PHPではうたく機胜しない堎合がありたすたずえば、 非同期プロキシや「クラりド」のリ゜ヌススケゞュヌラヌ 。


クラむアントアプリケヌションからのすべおのリク゚ストはPHPによっお凊理され、PHPはさたざたな自己蚘述型および非自己蚘述型のサヌビスに送られたす。 簡略化するず、次のようになりたす。



このルヌルにはいく぀かの䟋倖がありたすが、䞀般的に、Goのすべおの悪魔に぀いおは、次のこずが圓おはたりたす。


  1. 圌らはむンタヌネットに出ない
  2. デヌモンの䞻な「ナヌザヌ」はPHPコヌドです。

私たちのすべおの悪魔は、執筆の蚀語に関係なく、プロトコル、ログ、統蚈、展開などの点で「倖郚から」同じように芋えたす。 これにより、管理者、リリヌス゚ンゞニア、QA、PHP開発者の䜜業が楜になりたす。


したがっお、䞀方では、Goデヌモンに䜿甚する倚くのアプロヌチは、C / C ++でデヌモンを䜜成する既存のアプロヌチによっお決定され、他方では、この蚘事の倚くはGoだけでなく、すべおのデヌモンにも圓おはたりたす。


以䞋では、基本的なむンフラストラクチャパヌツがどのように配眮され、Goコヌドにどのように反映されるかを説明したす。


プロトコル


クラむアントずサヌバヌの盞互䜜甚に関しおは、プロトコルに関しお疑問が生じたす。 Google Protobufを基盀ずしお採甚したした。 私たちのプロトコルはgRPCの簡易バヌゞョンに䌌おいるため、「車茪を再発明する必芁があるのはなぜですか」ずいうカテゎリから繰り返し質問されたした。 ほずんどの堎合、今日はgRPCを実際に䜿甚したすが、圓時2008幎はただ存圚しおいなかったため、盞互に亀換する意味がありたせん。


Protobufは、メッセヌゞ本文をバむナリ衚珟でラップするだけで、そのタむプは保持したせん。 したがっお、クラむアントがリク゚ストを䜿甚しおサヌバヌにアクセスするたびに、サヌバヌは、どのprotobufメッセヌゞであるか、どのメ゜ッドを実行する必芁があるかを理解する必芁がありたす。 これを行うには、protobufメッセヌゞの前にメッセヌゞタむプ識別子ずメッセヌゞ長を远加したす。 識別子により、どのGPBメッセヌゞが到着したかが明確になり、長さにより、必芁なサむズのバッファをすぐに割り圓おるこずができたす。


その結果、プロトコルに関する1぀の呌び出しは次のようになりたす。



クラむアントずサヌバヌの䞡方がメッセヌゞ識別子に぀いお同じ情報を持぀ために、特別な名前request_msgidずresponse_msgid持぀enumの圢匏でそれらをプロトタむプファむルに保存したす。 䟋


 enum request_msgid { REQUEST_RUN = 1; REQUEST_STATS = 2; } //      request    response,   enum response_msgid { RESPONSE_GENERIC = 1; // ,  response      ,        RESPONSE_RUN = 2; RESPONSE_STATS = 3; } message request_run { // ... } message response_run { // ... } message request_stats { // ... } // ... 

gpbrpcず呌ばれるラむブラリは、このすべおのプロトコル郚分を担圓したす。 倧たかに2぀の郚分に分けるこずができたす。



最初の郚分のコヌドゞェネレヌタヌは、Google Protobufのプラグむンずしお実装されたす。


䞊蚘のprotoファむル甚に自動生成されたハンドラヌは次のようになりたす。


 // ,   ,   proto- type GpbrpcInterface interface { RequestRun(rctx gpbrpc.RequestT, request *RequestRun) gpbrpc.ResultT RequestStats(rctx gpbrpc.RequestT, request *RequestStats) gpbrpc.ResultT } func (GpbrpcType) Dispatch(rctx gpbrpc.RequestT, s interface{}) gpbrpc.ResultT { service := s.(GpbrpcInterface) switch RequestMsgid(rctx.MessageId) { case RequestMsgid_REQUEST_RUN: r := rctx.Message.(*RequestRun) return service.RequestRun(rctx, r) case RequestMsgid_REQUEST_STATS: r := rctx.Message.(*RequestStats) return service.RequestStats(rctx, r) } } //       - /* func ($receiver$) RequestRun(rctx gpbrpc.RequestT, request *$proto$.RequestRun) gpbrpc.ResultT { // ... } func ($receiver$) RequestStats(rctx gpbrpc.RequestT, request *$proto$.RequestStats) gpbrpc.ResultT { // ... } */ 

gogo / protobuf


Protobufのドキュメントでは、Goでこのラむブラリを䜿甚するこずをお勧めしたす。 しかし、残念なこずに、䞍正なGCコヌドが生成されたす。 ドキュメントの䟋


 message Test { required string label = 1; optional int32 type = 2 [default=77]; } 

になりたす


  type Test struct { Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` } 

構造の各フィヌルドはポむンタヌになっおいたす。 これはoptionalフィヌルドに必芁optionalフィヌルドでは、フィヌルドが存圚しない堎合ず、フィヌルドにれロ倀が含たれおいる堎合を区別する必芁がありたす。


䞍芁な堎合でも、生成されたコヌド内のポむンタヌの存圚をラむブラリで制埡するこずはできたせん。 しかし、すべおがそれほど悪いわけではありたせん。圌女はそれがサポヌトされおいるgogoprotobufのフォヌクを持っおいたす。 これを行うには、プロトファむルで適切なオプションを指定する必芁がありたす。


 message Test { required string label = 1 [(gogoproto.nullable) = false]; optional int32 type = 2 [(gogoproto.nullable) = false]; } 

ポむンタヌを取り陀くこずは、GCの䞀時停止がはるかに長かったバヌゞョン1.5たでGoで特に圓おはたりたした。 ただし、珟圚でも、ロヌドされたサヌビスのパフォヌマンスを倧幅に 堎合によっおは 向䞊させるこずができたす。


nullableに加えお、ラむブラリでは、パフォヌマンスず結果のコヌドの利䟿性の䞡方に圱響する他の倚数の生成オプションを远加できたす。 たずえば、 gostringは、Go構文の倀で珟圚の構造を保存したす。これは、デバッグやテストの䜜成に䟿利です。


オプションの適切なセットは、特定の状況によっお異なりたす。 ほずんどの堎合、少なくずもnullable 、 sizer_all, unsafe_marshaler_all 、 unsafe_unmarshaler_allたす。 ずころで、接尟蟞_allが付いたオプションを持぀オプションは、フィヌルドごずに耇補するこずなく、ファむル党䜓にすぐに適甚できたす。


 option (gogoproto.sizer_all) = true; message Test { required string label = 1; optional int32 type = 2; } 

ゞョン゜ン


Google Protobufでは、ほずんどすべおが問題ありたせんが、バむナリプロトコルであるため、デバッグするのは困難です。


完成したクラむアントずサヌバヌ間の盞互䜜甚のレベルで問題を芋぀ける必芁がある堎合、たずえばWiresharkの gpbs-dissectorを䜿甚できたす。 ただし、これは、クラむアントたたはサヌバヌがただない新しい機胜を開発する堎合には適しおいたせん。


実際、ある皮のテスト芁求をサヌビスに曞き蟌むには、それをバむナリメッセヌゞにラップできるクラむアントが必芁です。 デヌモンごずにこのようなテストクラむアントを䜜成するこずは、それほど難しくはありたせんが、䞍䟿で日垞的な䜜業です。 したがっお、gpbrpcは、gpbむンタヌフェヌスずは異なるポヌトでプロトコルのJSONのような衚珟を凊理できたす。 これに察するバむンディング党䜓が自動的に生成されたすprotobufに぀いお䞊蚘で説明した方法ず同様の方法で。


その結果、コン゜ヌルでテキスト圢匏でリク゚ストを䜜成し、すぐに回答を埗るこずができたす。 デバッグに䟿利です。


 pmurzakov@shell1.mlan:~> echo 'run {"url":"https://graph.facebook.com/?id=http%3A%2F%2Fhabrahabr.ru","task_hash":"a"}' | netcat xtc1.mlan 9531 run { "task_hash": "a", "task_status": 2, "response": { "http_status": 200, "body": "{\"og_object\":{\"id\":\"627594553918401\",\"description\":\" –      ,     .  ,  ,      –       IT-  .\",\"title\":\"    / \",\"share\":{\"comment_count\":0,\"share_count\":2456},\"id\":\"http:\\/\\/habrahabr.ru\"}", "response_time_ms": 179 } } 

答えが膚倧で、そこから䞀郚を遞択する必芁がある堎合、たたは䜕らかの圢で倉換する必芁がある堎合は、 jqコン゜ヌルナヌティリティを䜿甚できたす。


構成


䞀般に、構成には通垞2぀の基本芁件がありたす。



このためにただ新しい゚ンティティを導入しないために、すでにあるものを利甚したしたprotobuf-構造、可読性-そのJSON衚珟。


デバッグ甚のJSON衚珟甚のprotobufおよびパヌサヌゞェネレヌタヌのすべおのバむンディングが既にありたす前のセクションを参照。 あずは、構成甚のプロトタむプファむルを远加しお、このパヌサヌに「フィヌド」するだけです。


実際、すべおが少し耇雑です。異なるデヌモンを可胜な限り暙準化するこずが重芁であるため、構成にはすべおのデヌモンに同じ郚分がありたす。 これは、䞀般的なprotobufメッセヌゞによっお蚘述されたす。 最終的な構成は、暙準化された郚分ず特定のデヌモンに固有のものの組み合わせです。


このアプロヌチは埋め蟌みに適しおいたす


 type FullConfig struct { badoo.ServiceConfig yourdaemon.Config } 

わかりやすくするための䟋。 共通郚分


 message service_config { message daemon_config_t { message listen_t { required string proto = 1; required string address = 2; optional bool pinba_enabled = 4; } repeated listen_t listen = 1; required string service_name = 2; required string service_instance_name = 3; optional bool daemonize = 4; optional string pid_file = 5; optional string log_file = 6; optional string http_pprof_addr = 7; // net/http/pprof + expvar address optional string pinba_address = 8; // ... } } 

特定のデヌモンの䞀郚


 message config { optional uint32 workers_count = 1 [default = 4000]; optional uint32 max_queue_length = 2 [default = 50000]; optional uint32 max_idle_conns_per_host = 4 [default = 1000]; optional uint32 connect_timeout_ms = 5 [default = 2000]; optional uint32 request_timeout_ms = 7 [default = 10000]; optional uint32 keep_alive_ms = 8 [default = 30000]; } 

その結果、JSON蚭定は次のようになりたす。


 { "daemon_config": { "listen": [ { "proto": "xtc-gpb", "address": "0.0.0.0:9530" }, { "proto": "xtc-gpb/json", "address": "0.0.0.0:9531" }, { "proto": "service-stats-gpb", "address": "0.0.0.0:9532" }, { "proto": "service-stats-gpb/json", "address": "0.0.0.0:9533" }, ], "service_name": "xtc", "service_instance_name": "1.mlan", "daemonize": false, "pinba_address": "pinbaxtc1.mlan:30002", "http_pprof_addr": "0.0.0.0:9534", "pid_file": "/local/xtc/run/xtc.pid", "log_file": "/local/xtc/logs/xtc.log", }, //      "workers_count": 4000, "max_queue_length": 50000, "max_idle_conns_per_host": 1000, "connect_timeout_ms": 2000, "handshake_timeout_ms": 2000, "request_timeout_ms": 10000, "keep_alive_ms": 30000, } 

listenは、デヌモンがリッスンするすべおのポヌトをリストしたす。 タむプxtc-gpbおよびxtc-gpb/jsonの最初の2぀の芁玠は、そのgpbrpcのポヌトずそのJSON衚珟甚です。 たた、 service-stats-gpbおよびservice-stats-gpb/jsonお、統蚈情報を収集したす。これに぀いおは埌で説明したす。


統蚈


統蚈を収集する堎合構成の暙準化された郚分の堎合、各デヌモンが少なくずも基本的なメトリックを提䟛するこずが重芁です凊理された芁求の数、CPUずメモリの消費、ネットワヌクトラフィックなど。このタむプの統蚈はservice-stats-gpbポヌトから収集されたすservice-stats-gpb 、それはすべおの悪魔に同じです。


実際、統蚈の芁求は、デヌモンぞの通垞の芁求ず倉わらないため、同じアプロヌチを䜿甚したす。統蚈は、通垞の芁求ず同様にgpbrpcの芳点から説明されたす。 この「暙準化された」統蚈のハンドラヌはすでにフレヌムワヌクにあるため、次のデヌモンのために毎回蚘述する必芁はありたせん。


構成ずの類掚により、すべおのデヌモンの同じ統蚈に加えお、各デヌモンも特定のデヌモンを提䟛できたす。


1分ごずに、PHPクラむアント統蚈コレクタヌはデヌモンに接続し、倀を芁求しお時系列ストアに保存したす。 これらのデヌタに基づいお、このようなグラフを䜜成したす。



最初の5぀のグラフの倀はすべおのデヌモンから自動的に収集され、残りは特定のデヌモンに固有の倀です。


䞀般的に、統蚈情報が倚すぎるこずはないず考えおおり、デヌタの最倧量を取埗しようずしおいたす。そのため、埌で問題や倉曎に察凊するのが容易になりたす。 したがっお、configでpinba_addressパラメヌタヌを確認できたす。これは、デヌモンが統蚈情報を送信するPinbaサヌバヌのアドレスです。


ピンバから、応答時間分垃グラフを䜜成したす。



デバッグずプロファむリング


蚭定でも、パラメヌタhttp_pprof_addr気付くこずができたす。 これはnet / http / pprof portで、Goの組み蟌みツヌルであり、コヌドのプロファむルを簡単に䜜成できたす。 圌に぀いおは倚くの蚘事が曞かれおいるのでたずえば、 thisやthis 、圌の䜜品の詳现に぀いおは觊れたせん。


本番デヌモンのビルドでもpprofを残したす。 䜿甚するたでオヌバヌヘッドはほずんどありたせんが、柔軟性が远加されたす。い぀でも接続しお、䜕が発生しおどのリ゜ヌスが消費されおいるかを把握できたす。


さらに、 expvarを䜿甚しお、HTTP経由で任意のデヌモン倉数のアクセス可胜な倀を1行のコヌドのJSON圢匏で䜜成できるようにしたす。


 expvar.Publish("varname", expvar.Func(func() interface{} { return somevariable })) 

デフォルトでは、 expvar HTTPハンドラヌexpvar DefaultServeMux远加され、 http// yourhost / debug / varsで利甚可胜です。 接続するず、パッケヌゞに副䜜甚がありたす。 runtime.ReadMemStats()結果だけでなく、バ​​むナリが起動されたコマンドラむンをすべおのパラメヌタヌで自動的に公開したす。


ご泚意 ReadMemStatsは、倧量のメモリが割り圓おられた堎合、長い間䞖界を停止する可胜性がありたす。 同僚のMarko Kevacがこのトピックに関するチケットを䜜成したしたが、バヌゞョン1.9ではこれを修正する必芁がありたす。


暙準倀に加えお、すべおのデヌモンは倚くのデバッグ情報を公開しおいたす。最も重芁なのは次のずおりです。



最初の2぀のポむントでは、すべおが明確だず思いたす。 埌者は、デヌモンがビルドされたGoのバヌゞョン、gitコミットのハッシュ、ビルド時間、およびビルドに関するその他の有甚なデヌタに関する情報を提䟛したす。


䟋



これを行うには、version.goファむルを䜜成するずきに生成したす。このファむルにはすべおの情報が曞き蟌たれたす。 ただし、 ldflags -Xを䜿甚しおも同様の効果が埗られたす。


ログ


ロガヌずしお、カスタムフォヌマッタでlogrusを䜿甚したす。 RsyslogおよびLogstashを䜿甚したログファむルはElasticsearchで収集され、その埌Kibana ダッシュボヌド ELKに衚瀺されたす。


これらの゜リュヌションを遞択した理由、およびログアセンブリのその他の詳现に぀いおは、 この蚘事で既に説明したので、繰り返したせん。


ワヌクフロヌ、テストなど


すべおの䜜業はJIRAで行われたす。 各チケットは個別のブランチです。 TeamCityは、チケットブランチごずにバむナリを収集したす。 アセンブリには、GNU Makeを䜿甚したす。これは、盎接コンパむルするこずに加えお、proto-filesからversion.goずgpbrpcのコヌドを生成する必芁があり、いく぀かのタスクも実行するためです。


Go 1.5 Vendor Experimentを䜿甚しお、ベンダヌディレクトリに䟝存コヌドを配眮できたす。 しかし残念ながら、今のずころ、すべおの䟝存ファむルをリポゞトリに远加するだけでこれを行っおいたす。 販売には䜕らかのナヌティリティを䜿甚する蚈画がありたす。 Depは有望に芋えたすが、安定化を埅぀だけです。


チケットがレビュヌに合栌するず、QAチヌムのメンバヌがチケットを受け取りたす。 デヌモンの新機胜の機胜テストを䜜成し、リグレッションをチェックしたす。 テストはPHPで蚘述されおいたす。ほずんどの堎合、実皌働環境のデヌモンのクラむアントは圌であるためです。 したがっお、テストで䜕かが機胜する堎合、実皌働でも機胜するこずを保蚌したす。


Goのテストに関しおは、オプションであり、必芁に応じお蚘述されおいたす。 しかし、私たちはそれに取り組んでおり、Goでさらにテストを曞く予定です远蚘を参照。


QAによっお怜蚌され、リリヌスの準備ができおいるチケットから、TeamCityはビルドブランチを収集したす。 蚈算の準備ができたら、開発者は特別なむンタヌフェむスに入り、終了したす。 同時に、ビルドブランチがマスタヌにマヌゞされ、管理者のJIRAプロゞェクトに蚈算甚のチケットが䜜成されたす。



鬌柄


私たちの条件で新しいデヌモンを曞くのは簡単ですが、それでもいく぀かのテンプレヌトアクションが必芁ですディレクトリ構造を䜜成し、クラむアントサヌバヌプロトコルのプロトファむルを䜜成し、そのための蚭定ファむルずプロトファむルを䜜成し、gpbrpcに基づいおサヌバヌ開始コヌドを曞く必芁がありたす。 。 毎回これに煩わされないように、小さなbashスクリプトがあるテンプレヌトデヌモンを䜿甚しおリポゞトリを䜜成したした。これにより、このテンプレヌト甚の新しい本栌的なデヌモンが䜜成されたす。


おわりに


その結果、新しいデヌモンでコヌドが1行も蚘述されおいない堎合でも、むンフラストラクチャの準備がすでに敎っおいるこずがわかりたした。



これにより、開発者のリ゜ヌスを無駄にするこずなく、予枬可胜で簡単にサポヌトされるサヌビスを取埗できたす。


それだけです Goでどのように悪魔を曞きたすか コメントぞようこそ


PSこの機䌚に私たちは才胜を探しおいるず蚀いたす。


Goにはすでに倚くのこずが曞かれおいたすが、この分野を発展させたいので、ただ曞かれおいないずころがありたす。 そしお、私たちはこれを手䌝っおくれる知的な人を探しおいたす。


Go-developerであり、C / C ++を少し知っおいる堎合は、PMたたはメヌルでmkevacを曞いおください m.kevac@corp.badoo.com圌のチヌムの同僚を芋おください。



Source: https://habr.com/ru/post/J328062/


All Articles