protocol buffers, gRPC周りの整理

protocol buffers, gRPC周りの整理

この記事に書くこと

書くこと

  • gRPCの簡単な説明
  • protocol buffersの簡単な説明
  • 上記のサンプルコード(公式ドキュメントとほとんど同じ)

最初にざっくりと

  • protocol buffersは読み書きしやすいIDL(XMLみたいなもの)
  • gRPCはクライアント-サーバ間でのメソッド呼び出しを分かりやすく高速に行う機構

gRPC概要

  • クライアントが別環境のサーバーのメソッドをローカルのメソッド呼び出しのように行える仕組み
    • サーバーサイドはインターフェースを実装し、クライアントからの呼び出しをハンドルするためサーバを起動する
    • クライアントサイドは、サーバーサイドと同じメソッドを提供するスタブを持たせる
  • protocol buffers で、型、メソッド、メソッドがどの型を受け取り、どの型を返すかを定義する
    • 型定義はjsonでも問題ないが通常はprotocol buffersを使うことを想定している
  • http/2.0 をベースとしている

protocol buffers 概要

  • 構造化されたデータを自動的に読み書きするための機構
  • XMLみたいなものだが、XMLより可読性高く各言語の構造体へのエンコード、デコードも早い。
  • protoc
    • .protoファイル用コンパイラ
    • protoファイルの定義したデータ構造から任意の言語でのデータアクセス用クラスを生成する

サンプルコード

  • 下記がprotocol buffersの例。
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayGoodBye (GoodByeRequest) returns (GoodByeReply) {}
}

message HelloRequest {
  string name = 1;
  int32 count = 2;
}

message HelloReply {
  string message = 1;
  int32 count = 2;
}

message GoodByeRequest {
  string name = 1;
}

message GoodByeReply {
  string message = 1;
}
  • 詳細はドキュメントを読んでもらうとして、簡単な概要。
    • service Greeterは、rpc SayHelloメソッドと rpc SayGoodByeメソッドを持っている
    • SayHelloメソッドは message HelloRequest型を受け取り、 message HelloReply型を返す
    • Helloメソッドは message HelloRequest型を受け取り、message HelloReply型を返す
    • HelloRequest型は、name(string), count(int32)を持つ型
    • HelloReply型は、message(string), count(int32)を持つ型
    • GoodByeRequest、GoodByeReplyに関しても同様。

この.protoファイルを眺めると、サービスがどんなメソッドを持っていて、何を受け取って何を返すかがすぐに分かる!!

  • protocでgolangのコードを生成すると、下記のようなコードが生成される(一部抜粋)
// -----------------------------------
// 構造体関連
// -----------------------------------
type GoodByeRequest struct {
    Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *GoodByeRequest) Reset()         { *m = GoodByeRequest{} }
func (m *GoodByeRequest) String() string { return proto.CompactTextString(m) }
func (*GoodByeRequest) ProtoMessage()    {}
func (*GoodByeRequest) Descriptor() ([]byte, []int) {
    return fileDescriptor_17b8c58d586b62f2, []int{2}
}

func (m *GoodByeRequest) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_GoodByeRequest.Unmarshal(m, b)
}
func (m *GoodByeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_GoodByeRequest.Marshal(b, m, deterministic)
}
func (m *GoodByeRequest) XXX_Merge(src proto.Message) {
    xxx_messageInfo_GoodByeRequest.Merge(m, src)
}
func (m *GoodByeRequest) XXX_Size() int {
    return xxx_messageInfo_GoodByeRequest.Size(m)
}
func (m *GoodByeRequest) XXX_DiscardUnknown() {
    xxx_messageInfo_GoodByeRequest.DiscardUnknown(m)
}

var xxx_messageInfo_GoodByeRequest proto.InternalMessageInfo

func (m *GoodByeRequest) GetName() string {
    if m != nil {
        return m.Name
    }
    return ""
}

// -----------------------------------
// クライアント関連
// -----------------------------------

// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
    // Sends a greeting
    SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
    // Sends a greeting again
    SayGoodBye(ctx context.Context, in *GoodByeRequest, opts ...grpc.CallOption) (*GoodByeReply, error)
}

type greeterClient struct {
    cc *grpc.ClientConn
}

func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
    out := new(HelloReply)
    err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

func (c *greeterClient) SayGoodBye(ctx context.Context, in *GoodByeRequest, opts ...grpc.CallOption) (*GoodByeReply, error) {
    out := new(GoodByeReply)
    err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayGoodBye", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// -----------------------------------
// サーバー関連
// -----------------------------------

// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
    // Sends a greeting
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
    // Sends a greeting again
    SayGoodBye(context.Context, *GoodByeRequest) (*GoodByeReply, error)
}

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
    s.RegisterService(&_Greeter_serviceDesc, srv)
}

次に書きたいこと

procol buffers で型に値が入っていない場合とデフォルト値を区別する

  • protocol buffers では型のフィールドに値が入っていない場合、デフォルト値が使われてしまう
    • Stringだと""、int32だと0といった具合にデフォルト値が入る。
  • 値が無い場合とデフォルト値をクライアントが入れてきている場合を区別したい場合があるはずだ。
    • 例えば Person型に int32 age を定義した時、age が不明だからageフィールドを空にして送るときがあるはず。一方で age = 0 を送ることも想定されるが、unknownと0は明確に区別したい、など。
  • 上記のような時に、デフォルト型をラップしてnilを使えるようにする。

grpc-gateway

  • 何らかの理由でクライアント-サーバー間で直接gRPCを使えない時があるかもしれない。
  • だけどprotocol buffersはとても分かりやすいインターフェースなので使いたい。
  • grpc-gatewayを使うと、サーバーの前段にjson API を gRPC に変換してくれるリバースプロキシをprotoco buffersの定義から生成してくれるらしい。

参考

rails開発3週間で学んだことなど

rails開発を始めて早いものでもう3週間も経った。 最初の開発も一段落しつつあるので何かメモを残しておこうかなと。 rails以外でも学んだことは多いのでそれも含めてのメモ。

コードリーディング編

使っているライブラリやモジュールを全てgrepで調べることはできない

  • 例えばgolangでは
    • 使うモジュールやライブラリは最初に宣言するので、そこを見ると外部パッケージを使っているのかそれとも内部に実装されているものを使っているのかが分かる
import "github.com/gin-gonic/gin" // 外部パッケージ
import "nekootoko3/app/server/meshi" // 内部実装
  • railsではスクリプト内で明示的に使うパッケージが明示されていないことが多い
    • 親クラスでの実装、あるいはincludeしていればすぐに辿ることができる。
    • しかしgemとしてinstallしているパッケージに関しては突然何の前触れもなく現れてこいつはだれだ!?となる

rails console の登場

  • rails開発はデバッグも含め、このコンソール抜きでは進めることができないと言っても過言ではない
  • pryが入っていて色々めちゃめちゃ便利にできるようになっている。
    • dotenvなどを使って環境変数から変数の中身を取り込むようになっている変数の表示ができる
    • (コンソール上で) >> $ 突然出てきたモジュール名
      • 実装元のファイルとその実装内容を表示してくれる!
    • 実際にモデルやメソッドを動かして何がアウトプットされるのか確認できる
    • (コンソール上で) >> cd オブジェクト
      • オブジェクトに入ることができる!
      • 下記のlsを行うと使える
    • (コンソール上で) >> ls (突然出てきたモジュール名、何も指定しなくてもよい)
>> Delayed::Worker::VARIABLE
=> 3
>> $ Delayed::Worker::VARIABLE
いろいろ出てくる
  • rails のコードリーディングはこれを活用しよう

デバッグ

  • railsではメタプログラミングなるものがフル活用されており、動的にロジックが生成されるのでタイポがあっても普通にサーバーが立ち上がる(静的解析がされていない?)
  • なので、rails consoleでコンソールを立ち上げて、該当のパスへのリクエスト、メソッドの実行などを行って確かめる。

コーディングのTIPS

多値の受け取り

- 関数からメソッドが2つ以上返ってくる時、受け取る変数が1つの場合、暗黙的に配列として受け取る。スカラー値として受け取りたい、ただし、2つ目の値はいらないという場合は下記のように `_` を使い、いらないけどとりあえず受け取っとくみたいなことをやるとよい。
a = return_two_values_method # a = [var1, var2]
a, _ = return_two_values_method # a = var1, var2

返り値にハッシュではなく構造体を返す

  • ハッシュだと a["key"]みたいになって若干書きにくい
  • チェインメソッドを使えるよう構造体を使うとよい(rubyに構造体あるって知らなかった)
  • この記事を見るとよいかも。

return nil 明示的に書かなくてもよい

  • ruby は最後に評価された値が返される
  • 評価される値が何もない場合には、nilが返されるので return だけでも nil が返される。

参考

  • この記事も参考になりそう
    • 会社のrubyにはこの記事に入っているpryが入っている気がする

プロを目指す人のためのRuby入門を読んだ

10月から転職してRubyも書く人になったので、プロを目指す人のためのRuby入門を読んでみた。 この流れで勉強すればよかったな、っていう振り返りも最後に合わせて記載。

この本をやる前の状態

読んでどうだったか

  • ちょっとはプログラミング経験あって本気でRuby習得目指す人向けとあるように、初歩の初歩的な内容はほぼない。
  • 本全体に渡ってサンプルコードが多く、説明もわかりやすかった。
  • とはいえ、「第10章 yieldとProcを理解する」はわからなかった部分もあった。
    • よく分からなかった部分もなんとなくそういうものがあるのだな、とは分かったので業務の中で直面した時に戻ってくればよいかな。
  • Railsチュートリアルを読む前にやるべきだったな、と強く思う
    • 特に、シンボルやクラス、モジュールの辺りはざっとでも読んでから入ったら習得具合が違ったのではないかと思う。
    • Railsチュートリアル自体は非常によいチュートリアルだった。プログラミング初心者があの内容をしっかりできたら非常に力がつくのではないかと思う。というか、心がとても強いと思う。
  • 他の本を読んでいないので比較はできないが、自分には優しすぎず難しすぎずなちょうどいいレベル感だと感じた。
    • Railsやっていてよく分からないなぁ、というところも分かりやすく書いてあった印象。実務に戻ってみないと効果のほどは分からんが、、、

Rails学習のオススメな道筋

プログラミング経験1年以上を想定。 初心者目な人はProgateを先にガッツリやってみてから下記ステップへ。

  1. この本(プロを目指す人のためのRuby入門)をざっとやる。そんな時間かけなくてもいい。
  2. Railsチュートリアルをやる。ちゃんとやる。
    • Rubyに関して分からないところが出てきたら、この本に立ち戻る
    • Railsに関して分からないところが出てきたら、railsガイドを見よう。特にActiveRecordとかは重点的に見るといいと思う。

道筋と書いたけどこの2ステップになるかな〜

python3でプロコンのためのデータ構造とアルゴリズム

プログラミングコンテスト攻略のためのアルゴリズムとデータ構造の基礎編までを終えた。
アリ本が有名だが、こちらの方が評判がよくまとまっていると感じた。
新しい言語でやりたかったというのもあって、python3で書いている。
githubにコードと簡単なコメントあり。

なぜデータ構造とアルゴリズムの勉強を始めたのか

  • 自分は文系出身だが情報工学を専攻していたエンジニアがベースとして持っている知識は身につけようと考えたから。
  • インタプリタの実装をしてみたときや、広告配信サーバ周りの会話でちょっとしたアルゴリズムの話が出ることがあり、エンジニアのベースだと感じたから。

やってみて何かいいことあった?

  • コードやライブラリの計算量を少し意識できるようになった
  • 木の巡回やヒープ、動的計画法など、今までなんとなく見たようなものが体系だって学べた
  • この問題はこんな綺麗に解決できるのか!という感動があるw

コンピュータサイエンスおもしろい!

ハノイの塔を実装する

ハノイの塔についてこちら

前提

  • 3つの杭を3つの配列(orig, tmp, to)で表現する
  • 各配列をスタックとみなし行える操作はpushとpopのみ
  • 円盤1つを配列内のintとして表現。大きな円盤は大きな数字で
    • 例えば、3段のハノイの塔は[3,2,1]といった形で表現する

目標

  • n段のorigをtoに移し替えること

まずは

  • 1段のとき、2段のとき、3段のときを考えてその法則性を探る

1段

  1. orig (1)-> to
orig = []
tmp  = []
to   = [1]

2段

  1. orig (1)-> tmp
# 1段と同じ操作をorig -> tmp
orig = [2]
tmp  = [1]
to   = []
  1. orig (2)-> to
# 1段と同じ操作
orig = []
tmp  = [1]
to   = [2]
  1. tmp (1)-> to
# 1段と同じ操作をtmp -> to
orig = []
tmp  = []
to   = [2,1]

3段

  1. orig (1)-> to
  2. orig (2)-> tmp
  3. to (1)-> tmp
# 2段のときと同じ操作を orig -> tmp で実行
orig = [3]
tmp  = [2,1]
to   = []
  1. orig (3)-> to
# 1段のときと同じ操作
orig = []
tmp  = [2,1]
to   = [3]
  1. tmp (1)-> orig
  2. tmp (2)-> to
  3. orig (1)-> to
# 2段目のときと同じ操作を tmp -> to で実行
orig = []
tmp  = []
to   = [3,2,1]

ちょっと多いけど4段目

  1. orig (1)-> tmp
  2. orig (2)-> to
  3. tmp (1)-> to
  4. orig (3)-> tmp
  5. to (1)-> orig
  6. to (2)-> tmp
  7. orig (1)-> tmp
# 3段のときと同じ操作を orig -> tmp で実行
orig = [4]
tmp  = [3,2,1]
to   = []
  1. orig (4)-> to
# 1段のときの操作
orig = []
tmp  = [3,2,1]
to   = [4]
  1. tmp (1)-> to
  2. tmp (2)-> orig
  3. to (1)-> tmp
  4. tmp (3)-> to
  5. orig (1)-> tmp
  6. orig (2)-> to
  7. tmp (1)-> to
# 3段のときと同じ操作を tmp -> to で実行
orig = []
tmp  = []
to   = [4,3,2,1]

上記の試行から3つの手順に分けられる

N段のハノイの場合

  1. N-1段をorig->tmpに移動
  2. 1番下(N段目)をorig->toに移動
  3. N-1段をtmp->toに移動

手順1と3を再帰的に行うことで

コードにすると(python)

動かした回数を出したい場合は、実際に動かす操作を行なっている箇所、つまりto.appendの前後どちらかでカウントをインクリメントすればよい

def move(n, orig, to, tmp):
    if n == 0:
        return
    # n-1段をorigからtmpに移動する
    move(n-1, orig, tmp, to, cnt)
    # n段目をtoに移動する
    to.append(orig.pop())
    # n-1段をtmpからtoに移動する
    move(n-1, tmp, to, orig, cnt)


import sys

# 引数でハノイの塔の高さを指定する
if len(sys.argv) != 2:
    exit
N = int(sys.argv[1])

# N段の塔、空の塔を配列で擬似的に表す
orig = [i for i in range(N, 0, -1)]
tmp = []
to = []

# origの塔をtoに動かす
move(N, orig, to, tmp)

カウントありのpythonコード

goのチャネルの簡単なまとめ

Goならわかるシステムプログラミングを読んで。

概要

  • キュー構造である
    • ただしランダムアクセスはできず、投入と取り出しのみ行うことができる
  • 並列処理されても正しくデータを受け渡す同期機構である
    • goroutine間での情報共有としての利用が推奨されている
  • 読み込み・書き込みで準備ができるまでブロックする機能である
    • データがない状態で取り出そうとすると、他のgoroutineがデータを投入して読み込みの準備ができるまでブロックして待つ
    • バッファに空きがない状態で書き込もうとすると、他のgoroutineがデータを取り出して空きができるまでブロックして待つ

sample

下記スクリプトを実行する。

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("script start")
    done := make(chan bool)

    go func() {
        fmt.Println("start sub()")
        time.Sleep(2 * time.Second)
        fmt.Println("sub() is finished")
        done <- true
    }()

    fmt.Println("I'm waiting for tasks to be done")
    <-done
    fmt.Println("all tasks are finished")
}

main は <-done の箇所で、チャネルであるdoneに何らかのデータが入るのを待っている。
goroutineがdoneにtrueを入れると、all tasks are finishedとなる。

参考

mattnさんのこの記事とても分かりやすいです

ブラウザアクセスでファイルをダウンロードさせる方法

やり方

HTTPレスポンスヘッダーにContent-Disposition: attachmentをセットする。
デフォルトでは、Content-Disposition: inlineとなっており、Webページとして表示される。 ちなみにdispositionの意味は、何かの置き方や配置の仕方、という意味。

 The way in which something is placed or arranged, especially in relation to other things

サンプル

例えばgoで書いてみるとこんな感じ。
localhost:8080 にアクセスするとファイルが素敵なお手紙がダウンロードされる。

package main

import (
    "bytes"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.Header().Set("Content-Disposition", "attachment; filename=nekootoko3.txt")

    var buf bytes.Buffer
    buf.WriteString("Hello!\n")
    buf.WriteString("I'm nekootoko3\n")
    buf.WriteString("I'm happay everyday!\n")
    buf.WriteString("\n")
    buf.WriteString("Sincerely Yours\n")
    buf.WriteString("nekootoko3")
    txt := buf.Bytes()

    w.Write(txt)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

参考