Open33

go-zero を学ぶ

135yshr135yshr

go-zero なるフレームワークがあるとのことなので使ってみる

135yshr135yshr

どんなものなのかREADMEをざっくり翻訳
go-zeroは、web と rpc のフレームワークらしい。
大量のアクセスが来たりしても大丈夫なように作ってるとのこと。

135yshr135yshr

go-zero を操作するために goctl というコマンドラインが提供されている。
goctl を使うと作成した api ファイルを元に Go, TypeScript, JavaScript, Dart, Kotlin, Android, iOS のコードを生成してくれる

135yshr135yshr

メリットは、6つ

  1. 毎日数千万のアクティブユーザーを持つサービスの安定性を向上させます。
  2. 組み込みのチェーン化されたタイムアウト制御、同時実行制御、レート制限、適応型サーキットブレーカー、適応型負荷分散など、設定不要の機能を備えています。
  3. 組み込みのミドルウェアは、フレームワークに統合することも可能です。
  4. シンプルなAPI構文で、一つのコマンドで異なる言語のコードを生成できます。
  5. クライアントからのリクエストパラメータを自動的に検証します。
  6. 多数の組み込みマイクロサービス管理および並行ツールキットがあります。

概要図

135yshr135yshr

go-zero の背景

2018年の初めに、私たちはシステムをモノリシックなJava+MongoDBのアーキテクチャからマイクロサービスアーキテクチャに再設計することを決定しました。調査と比較の結果、次のような選択をしました:

  • Golangベース
    • 優れたパフォーマンス
    • シンプルな構文
    • 証明されたエンジニアリングの効率性
    • 極めてスムーズなデプロイ経験
    • サーバーリソースの消費量が少ない
  • 独自設計のマイクロサービスアーキテクチャ
    • マイクロサービスアーキテクチャの設計に豊富な経験があります
    • 問題の特定が容易
    • 機能の拡張が容易
135yshr135yshr

go-zeroの設計における考慮事項

マイクロサービスアーキテクチャの設計により、安定性と生産性の確保を期待しています。そして、最初から以下の設計原則を持っています:

  • シンプルに保つこと
  • 高い可用性
  • 高い並行性での安定性
  • 拡張性が高いこと
  • レジリエンスの設計、障害中心のプログラミング
  • ビジネスロジックの開発に対して使いやすくすること、複雑さをカプセル化すること
  • 一つのことを一つの方法で行う

半年近くの時間をかけて、モノリシックなシステムからマイクロサービスシステムへの移行を完了し、2018年8月にデプロイしました。新しいシステムは、ビジネスの成長とシステムの安定性を保証しました。

135yshr135yshr

go-zeroの実装と機能

go-zeroは、多くのエンジニアリングプラクティスを統合したWebおよびRPCフレームワークです。主な機能は以下の通りです:

  • パワフルなツールが組み込まれており、少ないコードで済む
  • シンプルなインターフェース
  • net/httpと完全に互換性がある
  • ミドルウェアがサポートされており、拡張が容易
  • 高いパフォーマンス
  • 障害中心のプログラミング、レジリエンスの設計
  • 組み込みのサービスディスカバリ、負荷分散
  • 組み込みの同時実行制御、適応型サーキットブレーカー、適応型負荷分散、自動トリガー、自動リカバリー
  • APIリクエストパラメータの自動検証
  • チェーン化されたタイムアウト制御
  • データキャッシュの自動管理
  • コールトレーシング、メトリクス、モニタリング
  • 高い並行性の保護

Multi-layer service protection

135yshr135yshr

ドキュメント に沿って、やってみるか。
とりあえず、Microservices で作るときの使い勝手知りたい。

想定は、ゴリゴリに Microservices でシステム作ってるチームではなく、Microservices はわかったぞ。作ってみるか!ってチームを想定している。

135yshr135yshr

環境構築

大まかな流れ

  1. Go をインストール
    • ドキュメントでは、1.15.1 使ってたけど、1.20.1 (2023-05-31時点) を使って進める
    • Homebrew 使っているのであれば、 brew install golang でもOK
  2. Go のモジュール設定
    • すっ飛ばしても良さそう
  3. Goctl のインストール
    • brew install goctl でもインストールできた
  4. protoc と protoc-gen-go をインストール
    • goctl env check -i -f -v で必要なものインストールされているか確認できる
    • Homebrew 使っているのであれば、 brew install protobuf protoc-gen-go protoc-gen-go-grpc でインストール可能
135yshr135yshr

開発するに当たり、ドキュメントが充実しているのでよく読む
今回は割愛する

とりあえず、Getting Started でサービス作ってみる
お題は、「簡単な注文サービス」
https://go-zero.dev/docs/quick-start/micro-service

モジュールは、「注文管理」と「ユーザー管理」の2つ。
管理と書いたけど、どちらも取得するだけの簡単なもの

135yshr135yshr

概要知るのに重要なので翻訳

サービスの設計分析

シナリオの要約によると、注文は直接ユーザーに関連しており、HTTPプロトコルを介してデータにアクセスします。また、注文内部ではユーザーに関するいくつかの基本的なデータを取得する必要があります。マイクロサービスアーキテクチャを使用しているため、2つのサービス(user, order)はデータをやり取りする必要があります。つまり、サービス間の通信です。ここまで来たところで、適切な通信プロトコルを使用してサービス間の通信を実現する必要があります。ここでは、サービス間の通信にRPCを選択し、すでに「RPCサービスの役割は何ですか?」というシナリオをより良く説明しました。もちろん、開発前の設計分析だけではサービスに関してはまだ多くの要素がありますので、ここでは詳細には触れません。以上から、私たちは次の2つのサービスを初期のデモ実装に使用する必要があることがわかります。

  • ユーザーRPC
  • 注文API
135yshr135yshr

まずは、プロジェクト作る
お決まりをただただ書く

cd $GOPATH/src/github.com/135yshr
mkdir go-zero-example
cd go-zero-example
go mod init go-zero-example
135yshr135yshr

proto ファイル作る

mkdir -p mall/user/rpc
vim mall/user/rpc/user.proto
mall/user/rpc/user.proto
syntax = "proto3";

package user;
  
// protoc-gen-go version is greater than 1.4.0, proto file needs to add go_package, otherwise it can't be generated
option go_package = "./user";

message IdRequest {
    string id = 1;
}
  
message UserResponse {
    // user id
    string id = 1;
    // user name
    string name = 2;
    // user gender
    string gender = 3;
}
  
service User {
    rpc getUser(IdRequest) returns(UserResponse);
}
135yshr135yshr

proto ファイル元にコードジェネレート!

cd mall/user/rpc
goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.

go-zero 知る前は、 go gen で生成してた。
都度、そのディレクトリに移動しないとだめなんだろうか。。。
ちょっと面倒だ

135yshr135yshr

ビジネスロジックを書き換え

mall/user/rpc/internal/logic/getuserlogic.go
@@ -24,7 +24,8 @@ func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserLo
 }

 func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
-       // todo: add your logic here and delete this line
-
-       return &user.UserResponse{}, nil
+       return &user.UserResponse{
+               Id:   "1",
+               Name: "test",
+       }, nil
 }

コピペして気付かなかったけど、ID を文字列で作ってたのか。
とりあえずいいか。

135yshr135yshr

api ファイルを作ってコードを生成する

mkdir -p order/api && cd order/api
vim order.api
cd -
order/api/order.api
type(
    OrderReq {
        Id string `path:"id"`
    }
  
    OrderReply {
        Id   string `json:"id"`
        Name string `json:"name"`
    }
)

service order {
    @handler getOrder
    get /api/order/get/:id (OrderReq) returns (OrderReply)
}
135yshr135yshr

APIファイルを元にコードをジェネレート!

goctl api go -api order.api -dir .

ディレクトリを都度移動しないとだめなんだろうか。。。(2回目

135yshr135yshr

ユーザー情報を取得するRPCを呼び出しできるように設定する

order/api/internal/config/config.go
@@ -1,6 +1,9 @@
 package config

-import "github.com/zeromicro/go-zero/rest"
+import (
+       "github.com/zeromicro/go-zero/rest"
+       "github.com/zeromicro/go-zero/zrpc"
+)
@@ -4,4 +4,5 @@ import "github.com/zeromicro/go-zero/rest"

 type Config struct {
        rest.RestConf
+       UserRpc zrpc.RpcClientConf
 }
order/api/etc/order.yaml
@@ -1,3 +1,8 @@
 Name: order
 Host: 0.0.0.0
 Port: 8888
+UserRpc:
+  Etcd:
+    Hosts:
+    - 127.0.0.1:2379
+    Key: user.rpc
135yshr135yshr

注文管理のビジネスロジックを書く

order/api/internal/svc/servicecontext.go
@@ -1,15 +1,19 @@
 package svc

 import (
+       "github.com/SwipeVideo/go-zero-example/mall/user/rpc/types/user"
        "github.com/SwipeVideo/go-zero-example/order/api/internal/config"
+       "github.com/zeromicro/go-zero/zrpc"
 )

 type ServiceContext struct {
-       Config config.Config
+       Config  config.Config
+       UserRpc user.UserClient
 }

 func NewServiceContext(c config.Config) *ServiceContext {
        return &ServiceContext{
-               Config: c,
+               Config:  c,
+               UserRpc: user.NewUserClient(zrpc.MustNewClient(c.UserRpc).Conn()),
        }
 }
order/api/internal/logic/getorderlogic.go
@@ -2,9 +2,12 @@ package logic

 import (
        "context"
+       "errors"

+       "github.com/SwipeVideo/go-zero-example/mall/user/rpc/types/user"
        "github.com/SwipeVideo/go-zero-example/order/api/internal/svc"
        "github.com/SwipeVideo/go-zero-example/order/api/internal/types"

        "github.com/zeromicro/go-zero/core/logx"
 )
@@ -23,8 +26,20 @@ func NewGetOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetOrder
        }
 }

-func (l *GetOrderLogic) GetOrder(req *types.OrderReq) (resp *types.OrderReply, err error) {
-       // todo: add your logic here and delete this line
+func (l *GetOrderLogic) GetOrder(req *types.OrderReq) (*types.OrderReply, error) {
+       user, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{
+               Id: "1",
+       })
+       if err != nil {
+               return nil, err
+       }
+
+       if user.Name != "test" {
+               return nil, errors.New("not found")
+       }

-       return
+       return &types.OrderReply{
+               Id:   req.Id,
+               Name: "test order",
+       }, nil
 }
135yshr135yshr

いろいろなライブラリのダウンロード!

go mod tidy
135yshr135yshr

作成したプログラムを起動する

大まかな手順

  1. etcd を起動する
  2. user モジュールを起動する
  3. order モジュールを起動する
135yshr135yshr

Getting Started では、etcd コマンドで実行しているが、今回は Docker で etcd を起動する

docker network create app-tier --driver bridge
docker run -d --rm \
    --name Etcd-server \
    --network app-tier \
    --publish 12379:2379 \
    --publish 12380:2380 \
    --env ALLOW_NONE_AUTHENTICATION=yes \
    --env ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0r:2379 \
    bitnami/etcd:latest

docker network の作成に関しては、この資料を参照
なお、macOSだと1000番未満のポートにアタッチできないので、ポート番号を変えてます。

ポート番号を変えたことによって、正しく動作しなくなるのでユーザー管理モジュールと注文管理モジュールの設定を変更する必要があります。

mall/user/rpc/etc/user.yaml
@@ -2,5 +2,5 @@ Name: user.rpc
 ListenOn: 0.0.0.0:8080
 Etcd:
   Hosts:
-  - 127.0.0.1:2379
+  - 127.0.0.1:12379
   Key: user.rpc
order/api/etc/order.yaml
@@ -4,5 +4,5 @@ Port: 8888
 UserRpc:
   Etcd:
     Hosts:
-    - 127.0.0.1:2379
+    - 127.0.0.1:12379
     Key: user.rpc
135yshr135yshr

ユーザー管理モジュールを起動する

go run ./mall/user/rpc/user.go -f ./mall/user/rpc/etc/user.yaml
135yshr135yshr

注文管理モジュールを起動する

go run ./order/api/order.go -f ./order/api/etc/order.yaml
135yshr135yshr

注文を取得してみる

curl -i -X GET http://localhost:8888/api/order/get/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-fd6d164ed3c5cf4b1dafc48faf2f5424-43071c77095d041d-00
Date: Wed, 31 May 2023 14:35:08 GMT
Content-Length: 30

{"id":"1","name":"test order"}%
135yshr135yshr

一晩おいたら、ディレクトリ間違えていることに気付いた

.
├── README.md
├── go.mod
├── go.sum
├── mall
│   └── user
│       └── rpc
│           ├── etc
│           │   └── user.yaml
│           ├── internal
│           │   ├── config
│           │   │   └── config.go
│           │   ├── logic
│           │   │   └── getuserlogic.go
│           │   ├── server
│           │   │   └── userserver.go
│           │   └── svc
│           │       └── servicecontext.go
│           ├── types
│           │   └── user
│           │       ├── user.pb.go
│           │       └── user_grpc.pb.go
│           ├── user.go
│           ├── user.proto
│           └── userclient
│               └── user.go
└── order  <-- 本来は、mallの配下にあるべき
    └── api
        ├── etc
        │   └── order.yaml
        ├── internal
        │   ├── config
        │   │   └── config.go
        │   ├── handler
        │   │   ├── getorderhandler.go
        │   │   └── routes.go
        │   ├── logic
        │   │   └── getorderlogic.go
        │   ├── svc
        │   │   └── servicecontext.go
        │   └── types
        │       └── types.go
        ├── order.api
        └── order.go
135yshr135yshr

昨夜まで読めていた英語ドキュメントが読めなくなってしまった。。。

135yshr135yshr

使ってみて良かったところ

  1. ベースファイル(APIやproto)作って、コマンドぽちぽちでapiサーバー立ち上げられるのは良さげ
  2. 誰がやっても同じようにサービスが作れる
  3. 細かいところに気を使わなくていい
  4. まだ使ったないけど、DDL作ったり、Dockerfile作ったりできる
  5. 設定ファイルの定義考えるの面倒だけど、枠組みできてるから楽
135yshr135yshr

使ってみて改善したいところ

  1. いちいちディレクトリを変更しないとだめなのでちょっと面倒
  2. 生成されたコードでlinter通そうとすると設定が面倒
  3. ないものねだりだけど、テストコードも書いてくれると助かるのに

以上。
ほかにも出てくると思われるが、Getting Started やっただけだとこれくらい