Functional Option Pattern

Fuctional Option PatternはGo言語において構造体の初期化時にオプション引数を与えるためのデザインパターンで、元ネタはRob Pike氏のSelf-referential functions and the design of options 、Dave Cheney氏のFunctional options for frendly APIS です。 Go言語には他の言語でオプション引数やキーワード引数と呼ばれる、省略可能な引数が存在しません。 通常は大きな問題は無いのですが、しかし、構造体の初期化時には、省略可能引数がほしくなる場合もあります。 Dave Cheney氏の記事にもある例を見てみましょう。 例 1 2 3 4 5 6 7 8 9 10 11 12 13 type Server struct { listener net.Listener } func NewServer(addr string) (*Server, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } srv := Server{listener: l} go srv.run return &srv, nil } よくある構造体の初期化関数です。 初期化が上手くいけば、ポインタとnilを、上手くいかなければnilとエラーを返す形になっています。 ここで、Serverになにがしかの拡張を加えることを考えます。たとえばタイムアウトや、TLS対応等です。 しかしこれらは指定する必要が無い場合もあります。 Go言語を用いたアプローチですぐに思いつくのは、オプションの組み合わせの数だけ初期化関数を作成することですね。(たとえば、With...というサフィックスを使って) しかしこれは、オプションの数が増えると、作成しなければならない関数の数が膨大になっていきます。 保守の観点から見てもこれは余りうれしくありません。 Config構造体を用いる そこでよく用いられるのが、設定を保持する構造体を用いる方法です。 例としては、以下の様にします。 1 2 3 4 5 6 7 8 type Config struct { Timeout time.Duration Cert *tls.Cert } func NewServer(addr string, config Config) { // ... } これも良く用いられているパターンです。 しかし、オプションを一切与えない場合のパターンを考えてみましょう。 ...

2017-09-26 · nasa9084

go-redis, redigo, boltのベンチマークを取ってみた

tl;dr データベースに接続済みの状態からstringで値をセット・ゲットするベンチマーク BoltのGetがめちゃめちゃ速い go-redisよりはredigoの方が速い Boltのセットがメモリアロケーションすごく多い result 1 2 3 4 5 6 7 8 9 $ go test -bench . BenchmarkRedisSet-4 10000 246527 ns/op 249 B/op 9 allocs/op BenchmarkRedisGet-4 5000 231569 ns/op 225 B/op 9 allocs/op BenchmarkRedigoSet-4 5000 204545 ns/op 70 B/op 4 allocs/op BenchmarkRedigoGet-4 5000 209392 ns/op 80 B/op 6 allocs/op BenchmarkBoltSet-4 10000 166142 ns/op 34287 B/op 57 allocs/op BenchmarkBoltGet-4 1000000 1140 ns/op 488 B/op 8 allocs/op PASS ok practices/redis-bolt-benchmark 8.705s source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package rbbench_test import ( "testing" "github.com/boltdb/bolt" redigo "github.com/garyburd/redigo/redis" redis "github.com/go-redis/redis" ) var redisOpts = &redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, } func BenchmarkRedisSet(b *testing.B) { client := redis.NewClient(redisOpts) defer client.Close() b.ResetTimer() for i := 0; i < b.N; i++ { client.Set("key"+string(i), "value", 0).Err() } } func BenchmarkRedisGet(b *testing.B) { client := redis.NewClient(redisOpts) defer client.Close() b.ResetTimer() for i := 0; i < b.N; i++ { client.Get("key" + string(i)).Val() } } func BenchmarkRedigoSet(b *testing.B) { conn, _ := redigo.Dial("tcp", "localhost:6379") defer conn.Close() b.ResetTimer() for i := 0; i < b.N; i++ { conn.Do("SET", "key"+string(i), "value") } } func BenchmarkRedigoGet(b *testing.B) { conn, _ := redigo.Dial("tcp", "localhost:6379") defer conn.Close() b.ResetTimer() for i := 0; i < b.N; i++ { redigo.String(conn.Do("GET", "key"+string(i))) } } func BenchmarkBoltSet(b *testing.B) { db, _ := bolt.Open("bolt.db", 0600, nil) defer db.Close() b.ResetTimer() for i := 0; i < b.N; i++ { db.Update(func(tx *bolt.Tx) error { b, _ := tx.CreateBucketIfNotExists([]byte("bucket")) b.Put([]byte("key"+string(i)), []byte("value")) return nil }) } } func BenchmarkBoltGet(b *testing.B) { db, _ := bolt.Open("bolt.db", 0600, nil) defer db.Close() b.ResetTimer() for i := 0; i < b.N; i++ { db.View(func(tx *bolt.Tx) error { _ = string(tx.Bucket([]byte("bucket")).Get([]byte("key" + string(i)))) return nil }) } }

2017-09-07 · nasa9084

二重にプロキシされたRancher Serverのリバースプロキシ設定

常時SSLの環境で二重にリバースプロキシされた環境のRancherは、公式ドキュメント通りの設定を行っても正常に動作しません。 X-Forwarded-ProtoがHTTPSの状態でRancher Serverまで届くように設定することで問題を解消できます。

2017-09-04 · nasa9084

Rancher-HAProxyでHSTSを設定する

HSTSはHTTP Strict Transport Securityの略で、HTTPでの接続を強制的にHTTPSへと変更するようウェブブラウザへ伝達するセキュリティ機構です。 Rancher-HAProxyでロードバランシングしている場合にもHSTSを使えるように設定してみました。

2017-09-02 · nasa9084

io.Writer.Write()とfmt.Fprintf()のBenchmark

tl;dr 基本的にio.Writer.Write()を使用するのが高速なようです。 result 1 2 3 4 5 6 $ go test -bench . -benchmem BenchmarkWrite-4 30000000 48.7 ns/op 16 B/op 1 allocs/op BenchmarkWriteWithBytes-4 500000000 3.95 ns/op 0 B/op 0 allocs/op BenchmarkFprintf-4 20000000 91.5 ns/op 0 B/op 0 allocs/op BenchmarkWriteTo-4 100000000 10.0 ns/op 0 B/op 0 allocs/op BenchmarkWriteWithBufferBytes-4 300000000 4.31 ns/op 0 B/op 0 allocs/op source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package main_test import ( "bytes" "fmt" "io" "net/http" "testing" ) var s = "Hello, my world" var bs = []byte(s) var buf = bytes.Buffer{} type NullWriter struct{} func (w *NullWriter) Write(b []byte) (int, error) { return len(b), nil } func BenchmarkWrite(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { w.Write([]byte(s)) } } func BenchmarkWriteWithBytes(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { w.Write(bs) } } func BenchmarkFprintf(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(w, s) } } func BenchmarkWriteTo(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { buf.WriteTo(w) } } func BenchmarkWriteWithBufferBytes(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { w.Write(buf.Bytes()) } }

2017-09-02 · nasa9084

sync.WaitGroup

Goroutineを使用して複数の処理を並列で実行、すべてが終わったら次の処理に進みたいという場合があると思います。 Goroutineでデータのリストを作るという処理を考えます。 データの順番は関係なく、すべてのGoroutineでのデータがそろったら次の処理をしたいという設定です。 この場合、単純に考えると以下のようなコードになりますが、以下のコードではデータがそろう前に次の処理が行われます。 1 2 3 4 5 6 7 datalist := []string{} for i := 0; i < 10; i++ { go func() { // something w/datalist } } fmt.Println("next step") このような場合に、sync.WaitGroupを使用します。 sync.WaitGroupは基本的にはただのカウンタですが、カウンタがゼロになるまで処理を待つことができます。 言葉で説明してもわかりにくいと思いますので、ソースコードを見てみましょう。 1 2 3 4 5 6 7 8 9 10 11 12 datalist := []string{} wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) // Goroutineの数だけカウンタを増やす go func() { // something w/datalist wg.Done() // カウンタを減らす } } wg.Wait() // カウンタが0になるまでブロックする fmt.Println("next step") 上記の様にすることで、for文の部分ではGoroutineで並列に実行しつつ、次の処理は並列実行部分が終わってからという動作をさせることができます。 ポイントはカウンタを増やす部分です。 Goroutine内ではなく、外側でAdd(1)します。 Goroutine内でAdd(1)してしまうと、wg.Wait()に到達した時点でGoroutineがまだどれも実行されておらず、次の処理に進んでしまう可能性があるので、必ずGoroutineの外側で実行することが肝要です。

2017-09-01 · nasa9084

Go 1.9 is released

先日2017年8月24日にGo 1.9がリリースされました。 ダウンロードページ からダウンロード可能です。 最大の変更点はGo1.9rc1 is released! でもお伝えしたように、Type Alias でしょう。 型名に対して別名をつけることができる機能です。 また、そのほかにも多くの変更が加えられています。 リリースノートはこちら です。 以下では、いくつか変更点を見ていきます。 Ports Go 1.9から新しく2つのOSと1つのプロセッサアーキテクチャがサポートされています。 POWER8 IBMのPOWER8プロセッサがサポートされています。 GOARCH=ppc64またはGOARCH=ppc64leで使用することができます。 FreeBSD FreeBSD 9.3で動作します。。。が、すでにFreeBSDはサポート切れです。(なんでや・・・・) Go 1.10からはFreeBSD 10.3+で動作する様になる予定です。 OpenBSD 6.0 OpenBSD 6.0がサポートされました。 かわりに、Go 1.9ではOpenBSD5.9をサポートしていません。 Parallel Compilation パッケージの関数を並列コンパイルできるようになりました。 並列コンパイルはデフォルトでONになっており、無効化するには環境変数でGO19CONCURRENTCOMPILATIONを0に設定します。 Vendor matching with ./… これまで、./...というディレクトリ表現はvendorディレクトリも含んでいました。しかし、go testの場合などvendorディレクトリは含まれない方がうれしい場合も多く、実際glide nvなどでvendorディレクトリを含まないディレクトリマッチングが実装されていました。 go1.9からは./...にはvendorディレクトリが含まれないようになり、vendorディレクトリにマッチさせたい場合は./vendor/...と書く必要があります。 Moved GOROOT Go 1.9から、GOROOTが移動となりました。 起動されたパスから自動でGOROOTを探索します。 これにより、Goのインストールパスが違う場所に移動しても、Goのツール類は継続して使える用になりました。 Compiler Toolchain 複素数の割り算がC99準拠となりました。 Doc 長い引数リストは省略されます。 これはgo docで生成されるコードの可読性向上のためです。 また、構造体フィールドのドキュメンテーションがサポートされました。go doc http.Client.Jarなどでどうなったのか確認することができます。 env go env -jsonフラグによりJSON出力することができるようになりました。 Test go testコマンドに-listフラグが追加されました。 これに正規表現で引数を与えることで、テスト名・ベンチマーク名・Exampleテスト名を調べることができます。 Vet vetコマンドがより強化されました。 ...

2017-08-28 · nasa9084

docker-machineでRancherOSを使う

docker-machineとそのメリット docker-machine は仮想マシン上にDocker Engine をインストールするツールです。 docker-machineコマンドを使用することで、Dockerホストを作成・管理することが可能です。 docker-machineを使用してDockerホストを作成すると、 1 $ docker-machine env <MACHINE_NAME> でシェル評価可能なDocker接続情報を得ることができ、 1 $ eval $(docker-machine env <MACHINE_NAME>) とすることにより、そのセッション内ではあたかもローカル環境のDockerの様にコンテナを操作することが可能となります。 docker-machineで使用するOS 扨、通常docker-machineでDockerホストを作成すると、インストールされるOSはBoot2Docker ですが、docker-machineでは、ホスト作成時のコマンドラインオプションでisoイメージやシェルスクリプトを指定することでOSやDocker Engineのバージョンを変更することができます。 RancherOS Boot2Dockerに類似したOSとして、RancherOS があり、rosコマンドを使用することでインストール後でもDockerのバージョンを簡単に切り替えることができます。 RancherOSは以前、仮想マシン環境のサポートとして、Vagrant 用の環境を提供していましたが、現在(2017年8月)では、すでにサポートが終了しており、docker-machineを使用するようにというアナウンスが出ています。 そこで、今回はdocker-machineを使用してRancherOSを立ちあげてみようと思います。 docker-machineでRancherOSを立ちあげる 公式ドキュメント 上に示されたコマンドをそのまま使用しても、途中でエラーが出てしまい(エラーが出ること自体は記述されていますが)、docker-machineをの大きなメリットである、docker-machine envが使用できません。 エラーを回避するためには、Rancherのリポジトリ上にあるインストールスクリプト を指定します。 1 $ docker-machine create -d virtualbox --virtualbox-boot2docker-url https://releases.rancher.com/os/latest/rancheros.iso --engine-install-url https://raw.githubusercontent.com/rancher/install-docker/master/17.06.sh インストールが完了したら、docker-machine lsコマンドで実行中のDockerホストの一覧を表示することができます。 1 2 3 $ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS ros - virtualbox Running tcp://192.168.99.100:2376 v17.06.0-ce Docker Engineのバージョンを切り替える せっかくRancherOSを使用しているので、Docker Engineのバージョンを切り替えてみましょう。 Docker Engineのバージョンを切り替えるにはRancherOS上でrosコマンドを使用するのでした。 ...

2017-08-24 · nasa9084

Docker multi-stage builds

Docker 17.05から、新機能としてmulti-stage builds というものが導入されました。 これは、コンテナイメージをより最適化するために有用な機能で、Dockerfileからコンテナイメージをビルドする際にビルド依存のライブラリ/環境とランタイム依存のライブラリ/環境を切り分けることができる機能です。 具体例を見てみましょう。 Go言語で書かれた何らかのアプリケーションをコンテナ上で動かすことを考えます。 以前までであれば、以下のような二つのDockerfileを用いて作成します。 まずはビルド用Dockerfileです 1 2 3 4 FROM golang:1.7.3 WORKDIR /go/src/github.com/someone/foo/ COPY app.go . RUN GOOS=linux go build -a -o app . つぎに、実行用のDockerfileです。 1 2 3 4 FROM busybox:latest WORKDIR /root/ COPY app . CMD ["./app"] このようにすることで、ビルド時にはGo言語のビルド環境が入ったコンテナを、実行時は(Go言語環境は不要なので)busyboxコンテナを使用することで、実行イメージを小さく抑えることができます。 しかし、このように二つのDockerfileを使用する場合、コンテナイメージのビルド手順が煩雑になる、複数ファイルのため管理しにくいなどの問題がありました。 multi-stage buildsを実装されたことで、以下のようにDockerfileを一つにまとめることができます。 1 2 3 4 5 6 7 8 9 FROM golang:1.7.3 AS build WORKDIR /go/src/github.com/someone/foo/ COPY app.go . RUN GOOS=linux go build -a -o app . FROM busybox:latest WORKDIR /root/ COPY --from=build /go/src/github.com/someone/foo/app . CMD ["./app"] 一行目のAS build、九行目の--from=buildがポイントです。 ...

2017-08-17 · nasa9084

Go1.9rc1 is released!

Go1.9rc1がリリースされました! そこで、Go1.9のリリースノートをさらっと見てみようと思います。 (まだrc1なので、今後変更される場合があります。ご注意を) 全部見ていくと、結構な量になりそうなので、すぐに影響のありそうな部分だけ、軽く見ていきましょう。 type alias Go1.9ではType Aliasというものが導入されます。 これはその名の通り、型に別名をつけられるというもの。 言葉で説明するより、コードを見た方が早いと思いますので、コードを用意しました。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package main import ( "fmt" "log" ) type T1 struct { ID string Name string } type T2 = T1 func main() { t := T1{ ID: "id", Name: "Taro", } log.Println("call t1.Call()") fmt.Println(t.Call()) log.Println("call t2.Call2()") fmt.Println(t.Call2()) return 0 } func (t *T1) Call() string { return "hello, " + t.Name } func (t *T2) Call2() string { return "hi, " + t.ID } go1.8以前ではsyntax errorになるこのコードですが、go1.9では正常に動作し、以下のような出力をします。 ...

2017-07-26 · nasa9084