GOは、高度な並列性と可用性を備えた分散システムを実装します。Logマイクロサービスの実装

GOは、高度な並列性と可用性を備えた分散システムを実装します。Logマイクロサービスの実装

この記事はWeChatの公開アカウント「Coding Disney」から転載したもので、著者はChen Yiです。記事の転載についてはCodi​​ng Disney公式アカウントまでご連絡ください。

ビッグデータの時代では、高い同時実行性、高い可用性、マイクロサービス システム設計の理解を備えた人材が強く求められています。バックエンド開発に従事したい場合、JD の説明で最も一般的な要件は、いわゆる「高同時実行」システム開発の経験です。しかし、市場には「高同時実行性」と「高可用性」を直接ターゲットにしたチュートリアルがないことがわかりました。見つかる情報は、多くの場合、ほんの数語であったり、混乱を招く理論を説明しているものであったりします。しかし、技術の習得は実践から生まれなければなりません。長い間探していたのですが、マイクロサービスをベースにした高並列システムの開発を実践できるインストラクターが非常に少ないことがわかりました。したがって、私は自分の学習と実践経験に基づいて、特に分散システム設計の理論と技術を理解し習得するための具体的な実践を重視しながら、この技術を皆さんと共有したいと考えています。

いわゆる「マイクロサービス」には魔法のようなものは何もありません。もともと集約していたモジュールを、サーバープログラムの存在に基づいて複数の独立した形式に分解するだけです。開発したバックエンドシステムが、ログ、ストレージ、ビジネスロジック、アルゴリズムロジックなどのモジュールに分割されているとします。以前は、これらのモジュールが 1 つに集約され、複雑で大規模なアプリケーションが形成されていました。

このアプローチには多くの問題があります。 1 つ目は、モジュールをあまりに多く組み合わせると、モジュール間にさまざまな論理的な結合があるためにシステム設計が複雑になりすぎて、時間の経過とともにシステムの開発と保守がますます困難になることです。 2つ目は、システムがますます脆弱になっていることです。モジュールの 1 つがエラーを送信したりクラッシュしたりすると、システム全体が崩壊する可能性があります。 3 つ目は、スケーラビリティが強くなく、ハードウェアの性能向上によってシステムが対応する拡張を実現することが難しいことです。

高い同時実行性と高可用性を実現するための基本的な考え方は、モジュールを分解して独立したサーバー プログラムにすることです。モジュールはメッセージを送信することで相互に連携します。

このモデルの利点は次のとおりです。1. モジュールは分離されており、1 つのモジュールに問題が発生してもシステム全体にほとんど影響はありません。 2. スケーラビリティと高可用性。異なるサーバーにモジュールを展開できます。トラフィックが増加した場合は、サーバーの数を増やすだけで、システムの応答性を同様に拡張できます。 3. 堅牢性が向上します。複数のモジュールをバックアップできるため、1 つのモジュールに問題が発生した場合、要求を他の同様のモジュールにリダイレクトすることができ、システムの信頼性が大幅に向上します。

もちろん、いかなる利益にもそれに応じた代償が伴います。分散システムの設計と開発には、元の集約システムよりもはるかに多くの困難が伴います。たとえば、負荷分散、サービス検出、モジュールネゴシエーション、コンセンサスなどです。分散アルゴリズムはこれらの問題の解決を重視しますが、理論は常に抽象的で理解するのが困難です。高可用性と高同時実行性を備えたシステムを実装できない場合、いくら理論を読んでも混乱してしまいます。したがって、実践を通じて理論を理解し、習得する必要があります。まず、最も単純なサービスであるログ サービスから始めます。実装にはGOを使用します。

まず、go_distributed_system という名前のルート ディレクトリを作成します。以降のすべてのサービス モジュールはこのディレクトリに実装されます。次にサブディレクトリ proglog を作成します。入力後、サブディレクトリ internal/server/ を作成します。ここでは、ログ サービスの論理モジュールを実装します。まず、internal/server の下で初期化コマンドを実行します。

  1. go mod init 内部/サーバー

ここで開発されたモジュールは他のモジュールから参照されるため、mod ファイルを作成する必要があります。まず、ログ システムに必要な基礎データ構造を完成させ、log.go ファイルを作成する必要があります。対応するコードは次のとおりです。

  1. パッケージサーバー
  2.  
  3. 輸入 (
  4. 「fmt」  
  5. 「同期」  
  6.  
  7. 型 Log 構造体 {
  8. mu sync.Mutex
  9. 記録 [] 記録
  10. }
  11.  
  12. NewLog()関数 *Log {
  13. &Log{ch : make(chan Record),}を返します
  14. }
  15.  
  16. func(c *Log) Append(record レコード) (uint64, error) {
  17. c.mu.ロック()
  18. c.mu.Unlock() を延期する
  19. レコード.オフセット = uint64(len(c.records))
  20. c.records = 追加(c.records、レコード)
  21. record.Offset, nil を返す
  22. }
  23.  
  24. func (c *Log) Read (offset uint64)(Record, error) {
  25. c.mu.ロック()
  26. c.mu.Unlock() を延期する
  27. オフセット >= uint64(len(c.records)) の場合 {
  28. レコード{}、ErrOffsetNotFoundを返す
  29. }
  30.  
  31. c.records[オフセット]、nilを返す
  32. }
  33.  
  34. レコード構造体型{
  35. 値 []byte `json: "value" `
  36. オフセット uint64 `json: "offset" `
  37. }
  38.  
  39. var ErrOffsetNotFound = fmt.Errorf( "オフセットが見つかりません" )

ログ サービスは、ログの読み取りおよび書き込み要求を http サーバー プログラムの形式で受信するため、複数の読み取りまたは書き込み要求が同時に実行されるため、レコード配列に対して排他操作を実行する必要があります。したがって、レコード配列の各読み取りの前に、ミューテックス ロックを使用してロックを取得します。これにより、複数の読み取りおよび書き込み要求を同時に受信したときに、サービスがデータの一貫性を損なうことを防ぐことができます。

すべてのログの読み取りおよび書き込み要求は http POST および GET の形式で開始され、データは json にカプセル化されるため、以下の http サーバー オブジェクトを作成し、新しいファイル http.go を作成し、次のコードを完成させます。

  1. パッケージサーバー
  2.  
  3. 輸入 (
  4. 「エンコーディング/json」  
  5. 「ネット/http」  
  6. 「github.com/gorilla/mux」  
  7.  
  8. func NewHttpServer(addr string) *http.Server {
  9. httpsrv := 新しいHttpServer()
  10. r := mux.NewRouter()
  11. r.HandleFunc( "/" 、 httpsrv.handleLogWrite).Methods( "POST" )
  12. r.HandleFunc( "/" 、 httpsrv.hadnleLogRead).Methods( "GET" )
  13.  
  14. &http.Server{を返す
  15. 住所:住所、
  16. ハンドラ: r,
  17. }
  18. }
  19.  
  20. タイプ httpServer 構造体 {
  21. ログ *ログ
  22. }
  23.  
  24. 関数newHttpServer() *httpServer {
  25. &httpServer {を返す
  26. ログ: NewLog(),
  27. }
  28. }
  29.  
  30. WriteRequest構造体型{
  31. レコード レコード `json: "record" `
  32. }
  33.  
  34. WriteResponse構造体型{
  35. オフセット uint64 `json: "offset" `
  36. }
  37.  
  38. ReadRequest構造体型{
  39. オフセット uint64 `json: "offset" `
  40. }
  41.  
  42. ReadResponse構造体型{
  43. レコード レコード `json: "record" `
  44. }
  45.  
  46. func (s *httpServer) handleLogWrite(w http.ResponseWriter, r * http.Request) {
  47. var req 書き込みリクエスト
  48. //サービスはjson形式でリクエストを受け取ります
  49. エラー:= json.NewDecoder(r.Body).Decode(&req)
  50. err != nil の場合 {
  51. http.Error(w, err.Error(), http.StatusBadRequest)
  52. 戻る  
  53. }
  54.  
  55. オフ、エラー:= s.Log.Append(req.Record)
  56. err != nil の場合 {
  57. エラー:
  58. 戻る  
  59. }
  60.  
  61. res := WriteResponse{オフセット:オフ}
  62. //サービスは結果をjson形式で返します
  63. エラー = json.NewEncoder(w).Encode(res)
  64. err != nil の場合 {
  65. エラー:
  66. 戻る  
  67. }
  68. }
  69.  
  70. func (s *httpServer) hadnleLogRead(w http.ResponseWriter, r *http.Request) {
  71. var req 読み取りリクエスト
  72. エラー:= json.NewDecoder(r.Body).Decode(&req)
  73. err != nil の場合 {
  74. http.Error(w, err.Error(), http.StatusBadRequest)
  75. 戻る  
  76. }
  77.  
  78. レコード、err := s.Log。読み取り(req.Offset)
  79. err == ErrOffsetNotFound の場合 {
  80. http.Error(w, err.Error(), http.StatusNotFound)
  81. 戻る 
  82. }
  83.  
  84. err != nil の場合 {
  85. エラー:
  86. 戻る  
  87. }
  88.  
  89. res := ReadResponse{レコード: レコード}
  90. エラー = json.NewEncoder(w).Encode(res)
  91. err != nil の場合 {
  92. エラー:
  93. 戻る 
  94. }
  95. }

上記のコードは、「分散」と「マイクロサービス」の特徴を示しています。対応する機能コードは別のサーバーの形で実行され、ネットワークを介してサービス要求を受信します。これは「分散型」に相当します。それぞれの独立したモジュールは、「マイクロサービス」に対応する特定のタスクのみを完了します。この方法は、異なるマシン上で同時に実行できるため、「スケーラビリティ」を実現します。

同時に、サービスは http サーバーの形式で存在するため、サービス要求と戻りも Http 形式である必要があり、データは Json でカプセル化されます。同時に実装されるロジックは非常にシンプルです。ログ書き込み要求がある場合、要求をレコード構造に解析し、キューの末尾に追加します。ログの読み取り要求があった場合、クライアントから送信された読み取りオフセットを取得し、対応するレコードを取り出して JSON 形式にカプセル化し、顧客に返します。

サーバーコードが完成したら、サーバーを実行する必要があります。モジュール化を実現するために、サーバーの起動を別の場所に配置しました。 proglog ルート ディレクトリに cmd/server を作成し、そこに main.go を追加します。

  1. パッケージメイン
  2.  
  3. 輸入 (
  4. "ログ"  
  5. 「内部/サーバー」  
  6.  
  7. 関数main() {
  8. srv := server.NewHttpServer( ":8080" )
  9. ログに記録されます。致命的です(srv.ListenAndServe())
  10. }

同時に、internal/server の下のモジュールを参照するには、go mod init cmd/server を使用して cmd/server の下で初期化し、go.mod ファイルに次の行を追加する必要があります。

  1. internal/server を ../../internal/server に置き換えます

次に、go mod tidy コマンドを実行して、ローカル モジュールが指定されたディレクトリ変換に従ってモジュールを参照するようにします。最後に、go run main.go を使用してログ サービスを開始します。ここで、サーバーの可用性をテストする必要があります。また、ディレクトリ内にserver_test.goを作成し、テストコードを記述します。基本的なロジックは、サーバーにログ書き込み要求を送信し、次に読み取り要求を送信して、読み取られたデータが書き込んだデータと一致しているかどうかを比較することです。コードは次のとおりです。

  1. パッケージメイン
  2.  
  3. 輸入(
  4. 「エンコーディング/json」  
  5. 「ネット/http」  
  6. 「内部/サーバー」  
  7. 「バイト」  
  8. 「テスト」  
  9. 「io/ioutil」  
  10.  
  11. 関数 TestServerLogWrite(t *testing.T) {
  12. var テスト = [] 構造体 {
  13. リクエストサーバー.WriteRequest
  14. want_response サーバー.WriteResponse
  15. } {
  16. {リクエスト: server.WriteRequest{server.Record{[]byte(`これはログリクエスト1です`), 0}},
  17. want_response: server.WriteResponse{オフセット: 0, },},
  18. {リクエスト: server.WriteRequest{server.Record{[]byte(`これはログリクエスト2です`), 0}},
  19. want_response: server.WriteResponse{オフセット: 1, },},
  20. {リクエスト: server.WriteRequest{server.Record{[]byte(`これはログリクエスト3です`), 0}},
  21. want_response: server.WriteResponse{オフセット: 2, },},
  22. }
  23.  
  24. _の場合、テスト:=範囲テスト{
  25. //リクエストをJSON形式に変換し、ログサービスに投稿します
  26. リクエスト:= &test.request
  27. request_json、エラー:= json.Marshal(リクエスト)
  28. err != nil の場合 {
  29. t.Errorf( "リクエストをJSONに変換できませんでした" )
  30. 戻る  
  31. }
  32.  
  33. 応答、エラー:= http.Post( "http://localhost:8080" "application/json" 、bytes.NewBuffer(request_json))
  34. 延期resp.Body.Close ()
  35. err != nil の場合 {
  36. t.Errorf( "http post リクエスト失敗: %v" , err)
  37. 戻る 
  38. }
  39.  
  40. //ログサービスの戻り結果を解析する
  41. 本文、エラー:= ioutil.ReadAll(resp.Body)
  42. var レスポンス サーバー.WriteResponse
  43. err = json.Unmarshal([]byte(body), &response)
  44. err != nil の場合 {
  45. t.Errorf( "書き込み応答のアンマーシャルに失敗しました: %v" , err)
  46. }
  47.  
  48. // 結果が期待される結果と一致しているかどうかを確認します
  49. response.Offset != test.want_response.Offset {の場合
  50. t.Errorf( "オフセット: %d を取得しましたが、必要なオフセット: %d" 、 response.Offset、 test.want_response.Offset)
  51. }
  52.  
  53. }
  54.  
  55. var read_tests = []構造体{
  56. リクエストサーバー.ReadRequest
  57. server.ReadResponse が必要
  58. } {
  59. {リクエスト: server.ReadRequest{オフセット: 0,},
  60. 欲しいもの: server.ReadResponse{server.Record{[]byte(`これはログリクエスト1です`), 0}} },
  61. {リクエスト: server.ReadRequest{オフセット: 1,},
  62. 欲しいもの: server.ReadResponse{server.Record{[]byte(`これはログリクエスト2です`), 0}} },
  63. {リクエスト: server.ReadRequest{オフセット: 2,},
  64. 欲しいもの: server.ReadResponse{server.Record{[]byte(`これはログリクエスト3です`), 0}} },
  65. }
  66.  
  67. _の場合、テスト:=範囲read_tests{
  68. リクエスト:= test.request
  69. request_json 、エラー:= json.Marshal(リクエスト)
  70. err != nil の場合 {
  71. t.Errorf( "読み取り要求を JSON に変換できませんでした" )
  72. 戻る  
  73. }
  74.  
  75. //リクエストをjsonに変換し、GETリクエスト本体に挿入します
  76. クライアント:= &http.Client{}
  77. 要求、エラー:= http.NewRequest(http.MethodGet、 "http://localhost:8080" 、bytes.NewBuffer(request_json))
  78. 必須ヘッダーを設定します( "Content-Type" "application/json" )
  79. 応答、エラー:= client.Do(req)
  80. err != nil の場合 {
  81. t.Errorf( "読み取り要求失敗: %v" , err)
  82. 戻る  
  83. }
  84.  
  85. //読み取り要求によって返された結果を解析する
  86. 延期resp.Body.Close ()
  87. 本文、エラー:= ioutil.ReadAll(resp.Body)
  88. var 応答サーバー.ReadResponse
  89. err = json.Unmarshal([]byte(body), &response)
  90. err != nil の場合 {
  91. t.Errorf( "読み取り応答のアンマーシャルに失敗しました: %v" , err)
  92. 戻る  
  93. }
  94.  
  95. res := bytes.Compare(response.Record.Value、test.want.Record.Value) を返します。
  96. res != 0 の場合 {
  97. t.Errorf( "値: %q を取得しましたが、必要な値は : %q" 、 response.Record.Value、 test.want.Record.Value)
  98. }
  99. }
  100.  
  101. }

上記のコードが完成したら、go test を使用して実行します。結果は以下のようになります。

結果から、テストが合格したことがわかります。つまり、ログ サービスに書き込み要求を送信しても、読み取り要求を送信しても、得られる結果は期待どおりになります。要約すると、このセクションでは、JSON ベースの http 書き込み要求と読み取り要求を受信できるシンプルな JSON/HTTP ログ サービスを設計しました。今後はgPRC技術をベースにしたマイクロサービス開発技術についても学習していきます。

コードを取得する

https://github.com/wycl16514/golang_distribute_system_log_service.git

<<:  Techo Hubテクノロジーツアー北京駅がオープン、多くの専門家が「デジタル金融イノベーションと実践」の饗宴に参加

>>:  企業向けマルチクラウドコンピューティングのメリットとデメリット

推薦する

グレープシティは2018年のマイクロソフトテクノロジー&エコシステムカンファレンスに登場し、「開発者を支援する」という使命を果たし続けています。

2018年マイクロソフトテクノロジー&エコシステムカンファレンス(マイクロソフトテックサミット201...

ビッグデータ時代の化粧品業界の「美の真実」を読み解く

ビッグデータは良いものであり、またビッグデータは悪いものです。メイクは良いことであり、メイクは悪いこ...

Ce氏:SEOにおけるキーワードセグメンテーション技術についての簡単な説明

背景情報: Ce氏——Ceenの「世界名靴淘宝」プロモーションコンテストの特別審査員昇格コンテストの...

ニュース:クラウドサーバープロバイダーのVULTRは、ユーザーに少なくとも30%多いSSDを無料で提供すると発表しました

世界的に有名なクラウド サーバー (VPS) プロバイダーである Vultr は、2 月 26 日に...

SEO マネージャーに応募する際によくある質問 10 選

1. ウェブサイトのキーワードをホームページでランク付けしたいのですが、どうすればよいですか?チーム...

パニックが襲う!クラウド コンピューティングは業界の大手企業を転覆させると予想されていますか?

クラウド コンピューティングは、世界的な業界大手に多大なメリットをもたらす一方で、このテクノロジーは...

コミュニティウェブサイトのSEOはウェブサイト構築システムから始まります

コミュニティ ウェブサイトは、その名前が示すように、インターネット上の小さなコミュニティです。例えば...

中央銀行がオンライン決済監督の嵐を巻き起こし、アリババとテンセントの競争の内幕が明らかに

これは、2つのインターネット金融大手によるカーブでの競争ゲームです。中央銀行はタイミングよく介入し、...

hmbcloud の年間 30 ドルの NAT ベースの韓国 cn2 gia vps の簡単なレビュー

hmbcloudはアメリカの企業(中国系アメリカ人が経営)で、cn2 giaとIPLCのハイエンドラ...

アンカーテキストを使用して半分の労力で 2 倍の結果を達成する方法

サイトの最適化プロセスにおけるキーワードの役割については、誰もがよくご存知だと思います。しかし、SE...

SaaS 更新交渉を成功させる 6 つのステップ

契約交渉を成功させる鍵は準備であることは周知の事実ですが、SaaS の更新には独自の課題が伴います。...

テンセントクラウド浜海5Gエッジコンピューティングセンターが正式にオープン、テンセントの新インフラに新たなサポートを追加

10月14日、テンセントクラウド初の5Gエッジコンピューティングセンターが正式に一般公開されました。...

yourserver - スウェーデンの VPS、1Gbps の帯域幅、無制限のトラフィック、苦情防止、著作権なし、月額料金は 4 ユーロから

yourserver はラトビアの VPS 販売業者です。設立年は明らかではありません (このサイト...

分散型クラウドの時代において、クラウドコンピューティングのリソースを効率的に活用するにはどうすればよいでしょうか?

デジタル変革の加速により、より多くのユーザーが必要なコンピューティング リソースを入手できるようにな...

WeChatの第一層入口がJD.comにオープンし、WeChatストアが全面的にオープン

【Ebrun Power Networkニュース】4月17日、情報筋はEbrun Power Net...