朝日ネット 技術者ブログ

朝日ネットのエンジニアによるリレーブログ。今、自分が一番気になるテーマで書きます。

microservicesはじめました (1)

はじめに

朝日ネットで開発をしています。muratamです。 あたらしいプロダクトをmicroservicesで作る…ということがあるかもしれない、ということで軽く触れてみたいと思います。 今回は、GoとgRPCを使ってみます。

microservices とは

micro、つまりちいさなサービスを複数作り、それらを組み合わせてひとつのサービスを提供するアーキテクチャのことです。 スイミーですね。 個々のサービスは単一の機能を提供する独立した存在で、それらがメッセージをやり取りすることで一体として動きます。 microservice architectureの採用例としてはNetflixがよく知られています。 microserviceとは逆に単一アプリケーションで提供している状態をmonolithic(一枚岩)といいます。

利点としては、高負荷なサービスのインスタンスだけを増やすことができる性能面のメリット、一部に障害が起きても部品を交換するように新しいインスタンスを投入して復旧できる可用性があります。また開発面でもコードが分離・独立しているのでメンテナンスしやすく、別々の小規模なチームで同時に開発を進めやすいという点があります。

逆に難しい点としてはサービス全体のコントロールでしょうか。 多くのサービスが動いているためそれらのデプロイや監視をどうするかというのは問題になります。

gRPCについて

ちいさなサービスが複数動いていて連携する、ということはサービス間の通信がとても重要になります。 ここでは通信手段として gRPC を使います。 grpc.io gRPCはGoogleが開発したRPCフレームワークです。高速で、多くのプログラム言語に対応しています。 RPCでやりとりするデータのシリアライズにはProtocol Buffersを使います。 Protocol Buffersで定義したものを各プログラム言語のソースコードに変換して、プログラムから利用できるようにします。

gRPCで通信してみる

ここでは単純なサーバとクライアントだけでやってみます。 クライアントが文字列を送るとサーバは文字数と単語数を返します。

gRPCの入手

Goをつかいます。 まずはGo向けのgRPCライブラリを入手

% go get -u google.golang.org/grpc

そして、Protocol Buffersをソースコードに変換するprotocを入手します。

https://github.com/protocolbuffers/protobuf/releases

また、Protocol BuffersのGo言語対応ランタイムも入手します。 1

% go get -u github.com/golang/protobuf/protoc-gen-go

Protocol Buffersの定義

$GOPATH/src/grpc01/以下に作成していきます。 proto/wc.protoにProtocol Buffers定義を書きます。

// grpc01/proto/wc.proto
syntax = "proto3";
package wc;
service Count {
    rpc WCount(Request) returns (Response) {}
}
message Request {
    string msg = 1;
}
message Response {
    int32 words = 1;
    int32 chars = 2;
}

Countというserviceを定義します。WCountというメソッドを持っていて、Requestというメッセージを受け取ってResponseというメッセージを返します。

これをprotocで変換します。

% protoc -I=proto/ --go_out=plugins=grpc:wc/ proto/wc.proto

変換結果がwc/wc.pb.goに出力されました。

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: wc.proto

package wc

import proto "github.com/golang/protobuf/proto"
...

サーバの実装

先ほど生成したwc.pb.goにはCountServerのinterfaceができています。 wc.protoで定義したWCountがinterfaceに含まれています。

// CountServer is the server API for Count service.
type CountServer interface {
    WCount(context.Context, *Request) (*Response, error)
}

というわけでWCountを実装したserverを作り、これをgRPCのサーバとして動かします。 サーバプログラムはserver/server.goに作ることにします。

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "grpc01/wc"
    "net"
    "os"
    "regexp"
)

func main() {
    listener, err := net.Listen("tcp", ":5300")
    if err != nil {
        fmt.Fprintf(os.Stderr, "net.Listen: %v\n", err)
        return
    }
    grpcSrv := grpc.NewServer()
    wc.RegisterCountServer(grpcSrv, &server{})
    grpcSrv.Serve(listener)
}

type server struct{}

var spaceRE, _ = regexp.Compile("[ \\s]+")

func (s *server) WCount(ctx context.Context, req *wc.Request) (*wc.Response, error) {
    msg := req.Msg
    var numW, numC int32
    numC = int32(len([]rune(msg)))
    words := spaceRE.Split(msg, -1)
    numW = int32(len(words))
    res := &wc.Response{
        Chars: numC,
        Words: numW,
    }
    return res, nil
}

クライアントの実装

wc.pb.goにはクライアントのinterfaceもあります。 こちらもWCountを含んでおり、さらにそれを実装したcountClientが定義されています。

// CountClient is the client API for Count service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type CountClient interface {
    WCount(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}

type countClient struct {
    cc *grpc.ClientConn
}

func NewCountClient(cc *grpc.ClientConn) CountClient {
    return &countClient{cc}
}

func (c *countClient) WCount(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
 ...
}

クライアントプログラムからはそのクライアントからWcountを呼び出します。 クライアントプログラムはclient/client.goに作ることにします。

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "grpc01/wc"
    "os"
)

func main() {
    args := os.Args
    conn, err := grpc.Dial("127.0.0.1:5300", grpc.WithInsecure())
    if err != nil {
        fmt.Fprintf(os.Stderr, "grpc.Dial: %v\n", err)
        return
    }
    defer conn.Close()
    client := wc.NewCountClient(conn)
    for i, v := range args[1:] {
        req := &wc.Request{Msg: v}
        res, err := client.WCount(context.Background(), req)
        if err != nil {
            fmt.Fprintf(os.Stderr, "WCount: %v for %v\n", err, v)
            continue
        }
        fmt.Fprintf(os.Stdout, "%d\t%v: %d characters, %d words\n", i+1, v, res.Chars, res.Words)
    }
}

動かす

サーバを立てて

% go build -o ./wcserver server/server.go
% ./wcserver &

クライアントを動かすと結果を返してくれます。

% go build -o ./wcclient client/client.go
% ./wcclient "water blue new world" "The quick brown fox jumps over the lazy dog" "いろはにほへと"
1       water blue new world: 20 characters, 4 words
2       The quick brown fox jumps over the lazy dog: 43 characters, 9 words
3       いろはにほへと: 7 characters, 1 words

今回のまとめ

gRPCを使って通信をしてみました。 次回はこのgRPCを使って通信する、小規模なmicoservices構成のwebサービスを作ってみたいと思います。

採用情報

朝日ネットでは新卒採用・キャリア採用を行っております。


  1. 10/12追記