このセクションでは、gRPCを使用するのは簡単です。詳細については、gRPC でのクライアントとサーバー間のデータ相互作用の4つのモードを参照してください。
序文
シリーズの概要
「Golangマイクロサービスチュートリアル」は10の記事に分かれており、マイクロサービスの開発、テスト、デプロイのプロセス全体を要約しています。
このセクションでは、最初にマイクロサービスの基本概念と用語を紹介し、次にマイクロサービス委託サービスの最初の簡潔なバージョンを作成します。次のセクション2〜10では、次のマイクロサービスを次々に作成します。
- 預託サービス(貨物サービス)
- 在庫サービス(倉庫サービス)
- ユーザーサービス
- 認証サービス
- ロールサービス
- 船舶サービス(貨物船サービス)
使用される完全なテクノロジースタックは次のとおりです。
Golang, gRPC, go-micro // 开发语言及其 RPC 框架
Google Cloud, MongoDB // 云平台与数据存储
Docker, Kubernetes, Terrafrom // 容器化与集群架构
NATS, CircleCI // 消息系统与持续集成
コードウェアハウス
著者コード:EwanValentine / shippy、翻訳者の中国語注釈コード: wuYin / shippy
各章は倉庫のブランチに対応しています。たとえば、この記事のpart1のコードは feature / part1にあります
開発環境
著者の開発環境はmacOSです。この記事では、makeツールを使用して効率的にコンパイルします。Windowsユーザーは手動でインストールする必要があります
$ go env
GOARCH="amd64" # macOS 环境
GOOS="darwin" # 在第二节使用 Docker 构建 alpine 镜像时需修改为 linux
GOPATH="/Users/wuyin/Go"
GOROOT="/usr/local/go"
準備ができて
Golangの基本的な文法をマスターする:Xie Daの「Go Webプログラミング」を読むことをお勧めします
gRPC / protobufをインストールする
go get -u google.golang.org/grpc # 安装 gRPC 框架
go get -u github.com/golang/protobuf/protoc-gen-go # 安装 Go 版本的 protobuf 编译器
マイクロサービス
どんなプロジェクトを書こうか?
港の貨物管理プラットフォームを構築したい。このプロジェクトはマイクロサービスアーキテクチャを使用して開発されています。さっそく、マイクロサービスの旅を始めましょう。
マイクロサービスとは何ですか?
従来のソフトウェア開発では、アプリケーション全体のコードは単一のコードベースで編成され、通常は次の分割コードの形式になります。
- 特性に応じて分割:MVCパターンなど
- 機能に応じて分割:大規模なプロジェクトでは、さまざまなビジネスを処理するパッケージにコードをカプセル化し、パッケージを再度分割することができます
それがどのように分割されていても、最終的には2つのコードが開発と管理のためにライブラリに集中されます。参照できるのは、Googleの単一コードライブラリ管理です。
マイクロサービスは、上記の2番目の分割方法の拡張であり、コードは機能に応じていくつかのパッケージに分割され、それらはすべて独立して実行できる単一のコードライブラリです。違いは次のとおりです。
マイクロサービスの利点は何ですか?
複雑さを軽減
アプリケーション全体のコードは、機能に応じて小さく独立したマイクロサービスコードベースに分割されます。これは、Unixの哲学を連想させるものですが、 Do One ThingとDo It Well、従来の単一コードベースのアプリケーションでは、モジュール間は緊密に結合されたファジー境界。製品が繰り返し使用されるにつれて、コードの開発と保守はより複雑になり、潜在的なバグと脆弱性がますます増えます。
スケーラビリティを向上させる
プロジェクト開発では、コードの一部が複数のモジュールで頻繁に使用される場合があります。このような再利用性の高いモジュールは、抽出され、検証モジュールなどのパブリックコードベースとして使用されることがよくあります。機能を拡張したい場合(SMS検証コードのログインなどを追加します)、単一のコードベースのサイズが増加するだけで、アプリケーション全体を再デプロイする必要があります。マイクロサービスアーキテクチャでは、検証モジュールを単一のサービスとして分離し、独立して実行、テスト、デプロイできます。
マイクロサービスの概念に従うと、コードを分割することでモジュール間の結合を大幅に減らすことができ、水平方向の拡張がはるかに容易になります。これは、現在の高性能、高可用性、分散型のクラウドコンピューティング開発環境に適しています。
Nginxには、マイクロサービスの多くの概念を探る一連の記事があり ます。ここをクリックして読む
Golangを使用する利点は何ですか?
マイクロサービスは、特定のフレームワークプロジェクトではなく、アーキテクチャの概念です。多くのプログラミング言語を実装できますが、一部の言語にはマイクロサービス開発に固有の利点があります。Golangはその1つです。
Golang自体は非常に軽量で高効率であると同時に、マルチコアプロセッサをより有効に活用できる並行プログラミングをネイティブでサポートしています。組み込みの net
標準ライブラリは、ネットワーク開発にも完全に対応しています。Xie Daの短い記事を参照できます:Go言語の利点
さらに、Golangコミュニティには、次のセクションで使用する優れたオープンソースのマイクロサービスフレームワークである go-mircoがあります。
プロトブフとgRPC
従来のアプリケーションの単一のコードベースでは、各モジュール間で関数を直接呼び出すことができます。ただし、マイクロサービスアーキテクチャでは、各サービスに対応するコードベースは独立して実行され、直接呼び出すことはできないため、相互間の通信は大きな問題であり、2つの解決策があります。
JSONまたはXMLプロトコルのAPI
マイクロサービスはHTTPベースのJSONまたはXMLプロトコルを使用して通信できます。サービスAがサービスBと通信する前に、Aは転送するデータをJSON / XML形式にエンコードし、それを文字列の形式でB、Bに渡す必要があります。受信したデータは、コードで使用する前にデコードする必要があります。
- 利点:データは読みやすく、使いやすいブラウザと対話するために必要なプロトコルです。
- 短所:大量のデータの場合、エンコードとデコードのオーバーヘッドが大きくなり、余分なフィールド情報により伝送コストが高くなります。
RPCプロトコルAPI
JSONの使用に関する以下のデータ description
、weight
および他のメタデータには、ブラウザのパースを容易にするために、たくさんのブラウザ/サーバ・アーキテクチャでは、データそのものの意義を説明します。
{
"description": "This is a test consignment",
"weight": 550,
"containers": [
{
"customer_id": "cust001",
"user_id": "user001",
"origin": "Manchester, United Kingdom"
}
],
"vessel_id": "vessel001"
}
ただし、2つのマイクロサービス間で通信する場合、データ伝送フォーマットが互いに合意されていれば、バイナリデータストリームを直接通信に使用でき、かさばる冗長なメタデータは不要になります。
gRPCの概要
gRPC は、Googleがオープンソース化した軽量のRPC通信フレームワークです。通信プロトコルはバイナリデータフローに基づいているため、gRPCは優れたパフォーマンスを発揮します。
gRPCはHTTP 2.0プロトコルをサポートし、データ送信にバイナリフレームを使用し、通信の両当事者に対して連続的な双方向データストリームを確立することもできます。参照:Google HTTPの概要/ 2
通信プロトコルとしてのprotobuf
2つのマイクロサービスは、HTTP 2.0に基づくバイナリデータフレームを介して通信するので、バイナリデータの形式について合意するにはどうすればよいですか。その答えは、gRPCに組み込まれたprotobufプロトコルを使用することであり、その DSL 構文は、サービス間の通信のデータ構造を明確に定義できます。参照:gRPC Go:基本を超えて
委託サービスマイクロサービス開発
上記の必要な概念的説明の後、最初のマイクロサービスである委託サービスの開発を始めましょう。
プロジェクトの構造
このプロジェクトの名前が shippyであるとすると、次のことを行う必要があります。
- では
$GOPATH
srcディレクトリの下に新しいshippyプロジェクトディレクトリ - プロジェクトディレクトリに新しいファイルを作成する
consignment-service/proto/consignment/consignment.proto
指導の便宜上、このプロジェクトのすべてのマイクロサービスコードをShippyディレクトリに配置します。このプロジェクト構造は「モノリポジトリ」と呼ばれ、読者は各マイクロサービスを「マルチリポジトリ」に分割することもできます独立したプロジェクト。REPOスタイルのバトルへの参照 :MONO VS MULTI
プロジェクト構造は次のようになります。
$GOPATH/src
└── shippy
└── consignment-service
└── proto
└── consignment
└── consignment.proto
開発プロセス
protobuf通信プロトコルファイルを定義する
// shipper/consignment-service/proto/consignment/consignment.proto
syntax = "proto3";
package go.micro.srv.consignment;
// 货轮微服务
service ShippingService {
// 托运一批货物
rpc CreateConsignment (Consignment) returns (Response) {
}
}
// 货轮承运的一批货物
message Consignment {
string id = 1; // 货物编号
string description = 2; // 货物描述
int32 weight = 3; // 货物重量
repeated Container containers = 4; // 这批货有哪些集装箱
string vessel_id = 5; // 承运的货轮
}
// 单个集装箱
message Container {
string id = 1; // 集装箱编号
string customer_id = 2; // 集装箱所属客户的编号
string origin = 3; // 出发地
string user_id = 4; // 集装箱所属用户的编号
}
// 托运结果
message Response {
bool created = 1; // 托运成功
Consignment consignment = 2;// 新托运的货物
}
構文リファレンス: Protobufドキュメント
プロトコルコードを生成する
protocコンパイラはgrpcプラグインを使用して.protoファイルをコンパイルします
ターミナルでコンパイルと実行のコマンドが繰り返し実行されるのを防ぐために、このプロジェクトではmakeツールを使用して consignment-service/Makefile
build:
# 一定要注意 Makefile 中的缩进,否则 make build 可能报错 Nothing to be done for build
# protoc 命令前边是一个 Tab,不是四个或八个空格
protoc -I. --go_out=plugins=grpc:$(GOPATH)/src/shippy/consignment-service proto/consignment/consignment.proto
実行 make build
、proto/consignment
ディレクトリに生成さ れます consignment.pb.go
consignment.protoとconsignment.pb.goの間の対応
service:外部呼び出しとして公開されるCreateConsignment
マイクロサービスShippingServiceの関数を定義します。インターフェイスを生成するprotobufコンパイラのgrpcプラグインによって生成され ます。
type ShippingServiceClient interface {
// 托运一批货物
CreateConsignment(ctx context.Context, in *Consignment, opts ...grpc.CallOption) (*Response, error)
}
message:通信のデータ形式を定義します。これはprotobufコンパイラーによって処理され、構造体を生成します
type Consignment struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"`
Weight int32 `protobuf:"varint,3,opt,name=weight" json:"weight,omitempty"`
Containers []*Container `protobuf:"bytes,4,rep,name=containers" json:"containers,omitempty"`
// ...
}
サーバーを実装する
サーバーはShippingServiceClient
インターフェースを実装して作成する必要が ありますconsignment-service/main.go
package main
import (
// 导如 protoc 自动生成的包
pb "shippy/consignment-service/proto/consignment"
"context"
"net"
"log"
"google.golang.org/grpc"
)
const (
PORT = ":50051"
)
//
// 仓库接口
//
type IRepository interface {
Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
}
//
// 我们存放多批货物的仓库,实现了 IRepository 接口
//
type Repository struct {
consignments []*pb.Consignment
}
func (repo *Repository) Create(consignment *pb.Consignment) (*pb.Consignment, error) {
repo.consignments = append(repo.consignments, consignment)
return consignment, nil
}
func (repo *Repository) GetAll() []*pb.Consignment {
return repo.consignments
}
//
// 定义微服务
//
type service struct {
repo Repository
}
//
// service 实现 consignment.pb.go 中的 ShippingServiceServer 接口
// 使 service 作为 gRPC 的服务端
//
// 托运新的货物
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
// 接收承运的货物
consignment, err := s.repo.Create(req)
if err != nil {
return nil, err
}
resp := &pb.Response{Created: true, Consignment: consignment}
return resp, nil
}
func main() {
listener, err := net.Listen("tcp", PORT)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
log.Printf("listen on: %s\n", PORT)
server := grpc.NewServer()
repo := Repository{}
// 向 rRPC 服务器注册微服务
// 此时会把我们自己实现的微服务 service 与协议中的 ShippingServiceServer 绑定
pb.RegisterShippingServiceServer(server, &service{repo})
if err := server.Serve(listener); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
上記のコードは、委託サービスマイクロサービスに必要なメソッドを実装し、ポート50051でリッスンするようにgRPCサーバーを確立します。この時点 go run main.go
で実行すると、サーバーは正常に起動します。
クライアントを実装する
チェックする商品の情報を入れます consignment-cli/consignment.json
:
{
"description": "This is a test consignment",
"weight": 550,
"containers": [
{
"customer_id": "cust001",
"user_id": "user001",
"origin": "Manchester, United Kingdom"
}
],
"vessel_id": "vessel001"
}
クライアントはこのJSONファイルを読み取り、商品を発送します。プロジェクトディレクトリに新しいファイルを作成します。consingment-cli/cli.go
package main
import (
pb "shippy/consignment-service/proto/consignment"
"io/ioutil"
"encoding/json"
"errors"
"google.golang.org/grpc"
"log"
"os"
"context"
)
const (
ADDRESS = "localhost:50051"
DEFAULT_INFO_FILE = "consignment.json"
)
// 读取 consignment.json 中记录的货物信息
func parseFile(fileName string) (*pb.Consignment, error) {
data, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
}
var consignment *pb.Consignment
err = json.Unmarshal(data, &consignment)
if err != nil {
return nil, errors.New("consignment.json file content error")
}
return consignment, nil
}
func main() {
// 连接到 gRPC 服务器
conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
if err != nil {
log.Fatalf("connect error: %v", err)
}
defer conn.Close()
// 初始化 gRPC 客户端
client := pb.NewShippingServiceClient(conn)
// 在命令行中指定新的货物信息 json 文件
infoFile := DEFAULT_INFO_FILE
if len(os.Args) > 1 {
infoFile = os.Args[1]
}
// 解析货物信息
consignment, err := parseFile(infoFile)
if err != nil {
log.Fatalf("parse info file error: %v", err)
}
// 调用 RPC
// 将货物存储到我们自己的仓库里
resp, err := client.CreateConsignment(context.Background(), consignment)
if err != nil {
log.Fatalf("create consignment error: %v", err)
}
// 新货物是否托运成功
log.Printf("created: %t", resp.Created)
}
実行go run main.go
後、再度 実行します go run cli.go
。
RPCを追加してすべての委託を表示し、GetConsignments
メソッドを追加して、既存のものをすべて表示できるようにしますconsignment
。
// shipper/consignment-service/proto/consignment/consignment.proto
syntax = "proto3";
package go.micro.srv.consignment;
// 货轮微服务
service ShippingService {
// 托运一批货物
rpc CreateConsignment (Consignment) returns (Response) {
}
// 查看托运货物的信息
rpc GetConsignments (GetRequest) returns (Response) {
}
}
// 货轮承运的一批货物
message Consignment {
string id = 1; // 货物编号
string description = 2; // 货物描述
int32 weight = 3; // 货物重量
repeated Container containers = 4; // 这批货有哪些集装箱
string vessel_id = 5; // 承运的货轮
}
// 单个集装箱
message Container {
string id = 1; // 集装箱编号
string customer_id = 2; // 集装箱所属客户的编号
string origin = 3; // 出发地
string user_id = 4; // 集装箱所属用户的编号
}
// 托运结果
message Response {
bool created = 1; // 托运成功
Consignment consignment = 2; // 新托运的货物
repeated Consignment consignments = 3; // 目前所有托运的货物
}
// 查看货物信息的请求
// 客户端想要从服务端请求数据,必须有请求格式,哪怕为空
message GetRequest {
}
次にmake build
、最新のコンパイル済みマイクロサービスインターフェースを取得するために実行します。この時点go run main.go
で実行すると、次のようなエラーメッセージが表示されます。
Goに慣れている人は、interface
必要なメソッドを実装するのを忘れたことを知っているはずです。更新しましょうconsignment-service/main.go
:
package main
import (
pb "shippy/consignment-service/proto/consignment"
"context"
"net"
"log"
"google.golang.org/grpc"
)
const (
PORT = ":50051"
)
//
// 仓库接口
//
type IRepository interface {
Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
GetAll() []*pb.Consignment // 获取仓库中所有的货物
}
//
// 我们存放多批货物的仓库,实现了 IRepository 接口
//
type Repository struct {
consignments []*pb.Consignment
}
func (repo *Repository) Create(consignment *pb.Consignment) (*pb.Consignment, error) {
repo.consignments = append(repo.consignments, consignment)
return consignment, nil
}
func (repo *Repository) GetAll() []*pb.Consignment {
return repo.consignments
}
//
// 定义微服务
//
type service struct {
repo Repository
}
//
// 实现 consignment.pb.go 中的 ShippingServiceServer 接口
// 使 service 作为 gRPC 的服务端
//
// 托运新的货物
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
// 接收承运的货物
consignment, err := s.repo.Create(req)
if err != nil {
return nil, err
}
resp := &pb.Response{Created: true, Consignment: consignment}
return resp, nil
}
// 获取目前所有托运的货物
func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest) (*pb.Response, error) {
allConsignments := s.repo.GetAll()
resp := &pb.Response{Consignments: allConsignments}
return resp, nil
}
func main() {
listener, err := net.Listen("tcp", PORT)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
log.Printf("listen on: %s\n", PORT)
server := grpc.NewServer()
repo := Repository{}
pb.RegisterShippingServiceServer(server, &service{repo})
if err := server.Serve(listener); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
今それを使用する場合go run main.go
、すべてが正常であるはずです:
最後に更新consignment-cli/cli.go
してconsignment
情報を取得します。
func main() {
...
// 列出目前所有托运的货物
resp, err = client.GetConsignments(context.Background(), &pb.GetRequest{})
if err != nil {
log.Fatalf("failed to list consignments: %v", err)
}
for _, c := range resp.Consignments {
log.Printf("%+v", c)
}
}
この時点go run cli.go
で再度実行すると、作成されたすべてのものconsignment
が表示されます。複数の実行では、複数の商品が委託されていることがわかります。
これまでに、protobufとgrpcを使用してマイクロサービスとクライアントを作成しました。
次の記事では、go-micro
フレームワークの使用と2つ目のマイクロサービスの作成について紹介します。次の記事では、Dockerがマイクロサービスをコンテナ化できるようにする方法を紹介します。