CAグループ限定GO勉強会

こんにちわ。CAリワード開発部の小高です。

先日、CAリワードが主催するCyberAgentグループ限定のGo勉強会を実施しました。

今回は、その時の熱気に満ちた会場の様子と、LT内容についてリポートしたいと思います。

タイムテーブル

時間 内容
19:00 - 19:05 挨拶
19:05 - 19:20 AbemaTVにおけるGo
19:20 - 19:35 Go言語とDockerでAIバトル基盤を書いたら便利だった話
19:35 - 19:50 GAE/Goのススメ
19:50 - 20:00 休憩
20:00 - 20:15 10年物のレガシーPHPをGoにリプレイスしている話
20:15 - 20:30 クリラボにGo導入している話
20:30 - 20:45 優秀な若手に狂奔するオッサン劇場 〜生き残りをかけて臨んだGoの道〜
20:45 - 21:00 Goトラップ 〜中級者向けGo言語でよく引っかかる同期処理など周りの問題、分析と解決方法〜
- 21:30 懇親会

勉強会の様子

それでは早速覗いてみましょう。どん!

f:id:kotakotaking:20160701014147j:plain

Go勉強会!!!

勉強する意気込みがひしひしと伝わってきます。

Goを扱う猛者たちは今か今かと目を光らせ会場入りしてきます。

そんな中、Gopher君はいつもと変わらずゆる〜い感じで迎えてくれます。グッジョブGopher

今回はCAグループ限定という事もあり、「事前予約なし、会場にきてね!」という謳い。

ここはGopher君が発する一言に模すなら「事前予約なし、会場にGoGo」だろ!と思う人も居たとか居ないとか。

そして、勉強会の時間がやってきたのですが・・・

なんと!当初の想定を大きく上回る100人以上のエンジニアが集まり、グループ内でもGoに対する関心が高い事が伺えました。

はい、その時の写真どん!!!

f:id:kotakotaking:20160701015233j:plain

おーーーーーっ!

「Goをやってる」「興味がある」というエンジニアがグループ内でもこんなにいたんだ!とワクワクしてきます。

と、同時に、ワタクシも気分が高揚してまいりました。

そう!何を隠そう、今日はワタクシもスピーカーとして登壇する予定なのであります。

高揚しない訳がありません。

そして、、、いよいよ始まりました!Go勉強会

其の1(GoでつくるAbemaTV)

初めに登壇いただいたいのは、AbemaTVの開発エンジニア「西尾」さん

AbemaTVと言えば、皆さんご存知「無料でテレビ番組が見れるインターネットテレビ局」

そんな注目のプロダクトを1から開発してきた西尾さんに登壇いただきました。

f:id:kotakotaking:20160701022153j:plain

日々のトラフィックをストレスなく捌く事はもちろんの事、

今後を見据え、柔軟にシステム拡張できるよう設計してるのはさすがの一言でした。

個人的に気になったのは、「外部ライブラリを使用する時には挙動を把握しましょう」という事。

内部のシステムをいかにしっかり作っても、外部ライブラリで思わぬPanicを発生させたりするものが結構あるそうです。

また、使っているライブラリ自体がそうでなくても、ライブラリが外部依存しているものだと、

ライブラリ自体は問題なくても、依存ライブラリがPanicを起こしたりなども多々あるそうで、

内製以外のライブラリを使用する際にはソースレベルで事前確認し、起こりえる症状をハンドルしないと危険だなと思いました。

其の2(Go言語とDockerでAIバトル基盤を書いたら便利だった話)

入社2年目にして堂々としたスピーチをしたのはアプリボットの「向井」さん

f:id:kotakotaking:20160701024322j:plain

リッチなUI部分のみCocos2d-xを使い、それ以外はほぼGolangで書いたという「ボンバー猛者」

GoにAIの要素を盛り込み、4人対決のとあるゲームを作った話をしてくれました。

デモンストレーションが始まったと同時に、昔なつかしBGMが流れてきて会場が笑いの渦に包まれます笑

AIはアドテクスタジオも注目している分野なので、

4プレイヤー全てが独立したAIで、どのように学習していくのかとても興味が湧きました。

其の3(GAE/Goのススメ)

GAEについて登壇していただいたのは、同じアドテクスタジオに所属するLodeoの「池田」さん。

f:id:kotakotaking:20160701024435j:plain

GAEとは、GoogleAppEnginenの略で、GoogleCloudで提供されるサービスの1つ。

みなさん耳にした事がある人もいるでしょう、SaaS(サーズ)ですね。

現在、GAEはJavaPHP、Pyrhon、Goをサポートしています(Standard Editionの場合)

動画元年という言葉が出たのも記憶に新しいですが、

SGAを活用しての動画生成サーバの話は未体験領域なだけに興味深かったですね。

其の4(10年物のレガシーPHPをGoにリプレイスしている話)

同じCAリワードに所属するスーパーエンジニアのDaichiです。

f:id:kotakotaking:20160701024507j:plain

今回、彼は弊社のコアとなるシステムのGo化を推し進めている一人で、

現行のレガシーPHPをGoにリプレイスした際の進め方や設計方針などをスピーチしてくれました。

進行がとても上手で、しっかりと詰まった内容の中にも会場をホットにさせる笑いのトークがあり、

聞いている側はすっかり心を持っていかれてしまいました笑

其の5(クリラボにGo導入している話)

アドテクスタジオ iXam Creative Lab. に所属する「水沼」さんによる、インフィード広告でGoを採用した話。

f:id:kotakotaking:20160701024535j:plain

元々Javaベースで開発していたものが一段落し、技術チャレンジとしてGoを取り入れたようです。

エンジニアもJava使いが殆どという中、敢えてGoでの開発に挑んだスピリッツ。素晴らしい!

チームとしてGoを習得するための取り組みで毎週Goの勉強会を実施したり、

「Go養成ギプス」と称した静的コード解析ツールを導入するなど、

これからGoに移行していく企業にとっては、チームの技術力底上げという観点で非常にためになる内容だったと思います。

其の6(優秀な若手に狂奔するオッサン劇場 〜生き残りをかけて臨んだGoの道〜)

はい。ワタクシ「小高」です。

f:id:kotakotaking:20160701025212j:plain

PHPをメインとして主に管理画面の開発を行ってきましたが、このたびGoで配信システムの開発を行いました。

今までフロントからバッチまでPHP一辺倒で書き倒す事が多かったですが、

どうしてもバッチなどパフォーマンスが重要視される分野においては限界を感じていました。

優秀な若手に突き上げられながら、脱PHP → Goへの転身を果たし、配信サービスを作り切った話を発表させていただきました。

其の7(Goトラップ 〜中級者向けGo言語でよく引っかかる同期処理など周りの問題、分析と解決方法〜)

最後はAmeba技術本部の「Mario」さんが登壇

f:id:kotakotaking:20160701025251j:plain

goroutineによるコードの間違いを会場の人達に当ててもらうという、他のスピーカーとは異なるアプローチで臨んでくれました。

唯一の会場参加型で、問題のあるコードがどこか、みんなスクリーンに目を凝らして探しているのが印象的でした。

goroutineで陥りやすい罠など、図解で説明していただきとても分かりやすかったです。

勉強会を終えて

私自身、何度か社外のGo勉強会に参加させていただきましたが、

今回の勉強会は参加者も多く内容も充実しており、まずは成功といったところではないでしょうか。

もちろん課題はありますが、このような取り組みをこれだけの規模で実施したのは初だったので、

良いも悪いも次に繋がるよい財産になったと思います。

今後、「Goと言えばCAリワード」と言われるよう、社外にもアプローチしていこうと思いますので、その際はみなさん足をお運びくださいね。

http://techblog.ca-reward.co.jp/techblog/2016/07/post-38.htmltechblog.ca-reward.co.jp

GulpでJavaScript開発効率化

WEBページに掲載されている広告ありますよね。

あんな感じの広告をJavaScriptタグで配信する機能の開発を行っています。

今回、プロジェクト内でGulp(ガルプ)を使っていたので、触れた感想について書きたいと思います。

f:id:kotakotaking:20160509192048p:plain

Node.jsのインストール

Gulpを使うにはNode.jsが必要です。公式サイトよりインストーラのダウンロードとインストールを行います。

f:id:kotakotaking:20160509102706p:plain

インストール確認

$ node -v
v6.1.0

Gulp.jsインストール

グローバル

node.js配下のディレクトリにパッケージが展開されます

sudo npm install gulp -g

ローカル

カレントディレクトリのnode_module配下にパッケージが展開されます

npm install --save-dev gulp

package.jsonの作成

初回は下記コマンドでpackage.jsonを作成します。

今回は既存のプロジェクトの為、既にpackage.jsonがありますので、この作業はスキップしました。

npm init

gulpfile.jsの作成

このファイルに実行するタスクを記述していきます。

今回は、ファイルに変更があったら自動で*.min.jsに圧縮するようにしたいと思います。

圧縮後の.min.jsへのリネームは、gulp-renameというライブラリを使います

gulp-renameインストール

(既存プロジェクトでは既にインストールされていたので、この工程はスキップしました)

npm install --save-dev gulp-rename

gulp-uglifyインストール

次いで圧縮ライブラリのインストール

(既存プロジェクトでは既にインストールされていたので、この工程はスキップしました)

npm install --save-dev gulp-uglify

gulpfile.jsにコードを記述

var uglify = require('gulp-uglify');
var rename = require('gulp-rename');

// .js to .min.js
gulp.task("js_min", function() {
    gulp.src(['./**/*.js', '!./**/*.min.js'])
        .pipe(uglify())
        .pipe(rename({ suffix: '.min' })) 
        .pipe(gulp.dest(./));
});
gulp.task('watch', function(){
    gulp.watch('./**/*', [
        'js_min'
    ]); 
});

ポイントはパスの書き方。

上記の場合は、修正したファイルと同じパスに.min.jsをディレクトリ再帰して吐き出してくれます。

Watchしてみる

$ gulp watch
[16:38:12] Using gulpfile gulpfile.js
[16:38:12] Starting 'watch'...
[16:38:12] Finished 'watch' after 13 ms

この状態でJSを改修すると、.min.jsが自動で出来ている事が確認出来ました。

プラグイン

Gulpは沢山のプラグインが出てますね。自分がよさそうだなと思ったのを紹介します。

gulp-rename

今回使用したプラグイン

gulp-uglify

今回使用したプラグイン

browser-sync

ファイル保存時に、ブラウザをオートリロードしてくれる

gulp-jshint

JavaScriptの文法チェック自動化

便利なサイト

gulpfile.jsとpackage.jsonを自動生成してくれるサイト。これは便利。

steelydylan.github.io

まとめ

今まで、JSとしっかり向き合う事があまりなかったです(反省)

Gulpに代表されるような便利な機能を使うか使わないかでは、開発効率に大きく差が出てきますね。

機械が出来る事は機械に任せて、人間にしか出来ない事をやる事でもっとラクしなければ!と思いました。

作業ログを収集して可視化する「WakaTime」

普段通りプログラムしているだけで、自動で作業ログを収集しグラフで可視化してくれるツール「WakaTime」を入れてみました。 これを入れると自分がどれくらいの時間、どの言語で開発しているかなどが可視化されて、プログラムに費やした時間などが分かるようになります。

wakatime.com

サポートしているIDEも多数!普段使っているエディアがそのまま使えます。

f:id:kotakotaking:20160418123948p:plain

自分はターミナル操作を行う事が多いので、今回はbash用のものをインストールしました。 画面左メニューのSupported IDEsから使用するエディタを選択します。

f:id:kotakotaking:20160418132519p:plain

それぞれのIDEごとにインストール方法が記載されていますので、マニュアルに沿ってインストール

[vagrant@localhost ~]$ sudo pip install wakatime
[vagrant@localhost ~]$ git clone https://github.com/gjsheep/bash-wakatime.git

bashに下記を追記

[vagrant@localhost ~]$ vi ~/.bash_profile

.bash_profile(パスは適宜変更)

source /home/vagrant/bash-wakatime/bash-wakatime.sh

Installing for bashセクションにある「API Key」というリンクをクリックすると、 API Keyがポップアップされるので、これをコピーして ~/.wakatime.cfg に貼付けます

f:id:kotakotaking:20160418164248p:plain

vi ~/.wakatime.cfg

~/.wakatime.cfg

[settings]
debug = false
api_key = <ここにAPI Keyを張り付け>
hidefilenames = false
exclude =
    ^COMMIT_EDITMSG$
    ^TAG_EDITMSG$
    ^/var/
    ^/etc/
include =
    .*  
offline = true
timeout = 30

実際にターミナル上で何か作業をしてみると、こんな感じで可視化してくれました。

f:id:kotakotaking:20160418124450p:plain

GOの開発の時にはIntelliJを使うので、同様にIntelliJにも入れてみました。 Gitのリポジトリ単位で集計してくれるので、各プロジェクトのアクティブ状態が分かります。

f:id:kotakotaking:20160418155442p:plain f:id:kotakotaking:20160418155456p:plain

GitHubと連携してリポジトリ毎の作業状況を可視化したり、Slack連携して専用Botにする事が出来たりするので、 プロジェクト全体の作業時間などを可視化出来て面白そうです。

GOルーチンおさらい

基本的なGOルーチン

sample.go

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(1)
    go routine()
    fmt.Println(3)
}

func routine() {
    time.Sleep(1 * time.Second)
    fmt.Println(2)
}

実行結果

[vagrant@localhost 20160404]$ go run sample.go
1
3

main()が終了すると共に終了となる。 GOルーチンの処理を最後まで実行させる為、main側で処理が終わった事の通知を受け取るまで待機

package main

import (
    "fmt"
    "time"
    "log"
)

func main() {
    // チャンネルの作成
    ch := make(chan string)
    fmt.Println(1)
    go routine(ch)

    // チャンネルのメッセージを受信(ここで受信するまで待ちます)
    res, ok := <- ch
    log.Printf("res -> %v, ok -> %v", res, ok) //  
    fmt.Println(3)
}

func routine(ch chan string) {
    time.Sleep(2 * time.Second)
    fmt.Println(2)
    // チャンネルにメッセージ"finish"を送信
    ch <- "finish"
}

実行結果

[vagrant@localhost 20160404]$ go run sample.go 
1
2
2016/04/04 14:39:52 res -> finish, ok -> true
3

メッセージを受信するまでWaitし、順番に処理される事が確認できました。<-ch で受信結果を受け取ると、第一引数にメッセージ、第二引数に受信結果を受け取れます。

バッファリング

package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

実行結果

1
2

バッファを超える数を指定するとFatal Error

package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    ch <- 3
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /home/vagrant/work/20160404/buff.go:9 +0xc4
exit status 2

Channelクローズ

v, ok := <-ch

で第二引数でチャンネルクローズしたかどうかを受け取れる

package main

import (
    "fmt"
)

func routine(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go routine(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

実行結果

0
1
1
2
3
5
8
13
21
34

Select

package main

import "fmt"

func routine(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y 
        case <-quit:
            fmt.Println("quit")
            return
        }   
    }   
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }   
        quit <- 0
    }() 
    routine(c, quit)
}

実行結果

0
1
1
2
3
5
8
13
21
34
quit

GO TOURからの拝借コードとなりますが、自分自身の備忘という事で。今後は実用例などもアップしていこうと思います

Elixir vs Golang スループット対決

Elixirとは

読み方はエリクサー。

Erlangという言語で実装されている。Rubyライクな文法で書ける。

Elixir (日本語) http://elixir-ja.sena-net.works

Elixir(本家)http://elixir-lang.org

Erlangとは

読み方はアーラン。Elixirの元になった言語。

Erlangは1986年にJoe Armstrong, Robert Virding, Mike Williams の3人によって最初のバージョンが作られた

「ジョー」と「ロバート」と「マイク」が俺らで時代変えてやろうぜ!AhHa?っていって(私の勝手なイメージ)、一念発起して開発した言語がErlang

モノはいいのに流行らなかった。ある日、デミアン(Damien Katz)が、

ErlangJavaよりも速くならない理由はどこにもありません。しかし、その奇妙な構文が人々を遠ざけ、普及と商用投資を妨げています。でも私はErlangが好きです。性能が重要なコンポーネントでの利用は少なくしますが、今後も致命的に重要なコンポーネントに使うつもりです。

とコメントした。Erlangが流行らないのは要は分かりにくいからとの事。

書き方にクセがあり難易度高めだけど、並行処理とか分散処理に向いていて、

これをRubyライクに分かり易くしたのがElixirだ。

Erlangが使われているところ
  • 電気通信企業
  • 広告ビッティングシステム
  • 金融機関

リライアビリティーは年間稼働率99.9999999%との事で、 年に31.54ミリ秒しかダウンしないようです。これは凄いですね。 元々電気通信系のシステムから発したという事で、 かけた相手にきちんと電話が繋がるっていうのは当たり前のように感じてますが、 裏でこういうシステムでしっかり構築されてるんですね。

Erlang http://www.erlang.org

Elixir vs Golang 並列処理対決

Elixirも並列処理が得意という事で、 GolangにもGOルーチンという並列処理を容易に行う仕組みがあります。 今回はこの2つの言語で並列処理をさせ、どちらがスループットが出るか検証してみたいと思います。

Erlangインストール

まずは、インストールするところから始めます

[vagrant@localhost ~]$ sudo yum install gcc glibc-devel make ncurses-devel openssl-devel autoconf
[vagrant@localhost ~]$ wget http://www.erlang.org/download/otp_src_18.0.tar.gz
[vagrant@localhost ~]$ tar zxvf otp_src_18.0.tar.gz
[vagrant@localhost ~]$ cd otp_src_18.0
[vagrant@localhost ~]$ ./configure
[vagrant@localhost ~]$ make
[vagrant@localhost ~]$ sudo make install

動作確認

[vagrant@localhost otp_src_18.0]$ erl
Erlang/OTP 18 [erts-7.0] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V7.0  (abort with ^G)
1> 

無事インストールできたようです

Elixirインストール

[vagrant@localhost otp_src_18.0]$ wget https://github.com/elixir-lang/elixir/releases/download/v1.2.3/Precompiled.zip
[vagrant@localhost otp_src_18.0]$ sudo unzip Precompiled.zip -d /opt/elixir
[vagrant@localhost otp_src_18.0]$ export PATH=/opt/elixir/bin:$PATH

動作確認

[vagrant@localhost otp_src_18.0]$ iex 
Erlang/OTP 18 [erts-7.0] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.2.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> IO.puts "hello"
hello
:ok
iex(2)>

Elixirもインストール出来ました。

環境

言語 バージョン
Elixir 1.2.3
Golang 1.6

検証方法

サブルーチンで10000回ループするだけの処理を10000回並走させ、スループットを計測したいと思います。

サンプルコード

  loop.go

package main

func main() {
    for i := 0; i < 10000; i++ {
        go func() {
            run()
        }() 
    }   
}

func run() {
    for i := 0; i < 10000; i++ {
    }   
}

loop.exs

アップデート中。しばしお待ち下さい

検証

loop.go

[vagrant@localhost work]$ time go run loop.go

real    0m0.182s
user    0m0.148s
sys 0m0.026s
[vagrant@localhost work]$ time go run loop.go

real    0m0.177s
user    0m0.138s
sys 0m0.037s
[vagrant@localhost work]$ time go run loop.go

real    0m0.164s
user    0m0.129s
sys 0m0.031s
[vagrant@localhost work]$ time go run loop.go

real    0m0.155s
user    0m0.116s
sys 0m0.035s
[vagrant@localhost work]$ time go run loop.go

real    0m0.171s
user    0m0.142s
sys 0m0.025s

loop.exs

アップデート中。しばしお待ち下さい

Kafka + Vagrant でメッセージ送受信(PHP + Golang)

職場にKafkaが導入されました。

今後触れる機会がどんどん増えてくるので、実際にVagrant環境にKafkaを入れて使ってみました。

Vagrant起動

$ vagrant up
The provider 'virtualbox' that was requested to back the machine
'default' is reporting that it isn't usable on this system. The
reason is shown below:

Vagrant has detected that you have a version of VirtualBox installed
that is not supported. Please install one of the supported versions
listed below to use Vagrant:

4.0, 4.1, 4.2, 4.3

VirtualBoxのVersionが5.0にあがったようだが、インストールされているVagrantバージョンが対応していない模様。

バージョンを確認してみる

$ vagrant version
Installed Version: 1.7.2
Latest Version: 1.8.1
 
To upgrade to the latest version, visit the downloads page and
download and install the latest version of Vagrant from the URL
below:

  http://www.vagrantup.com/downloads.html

If you're curious what changed in the latest release, view the
CHANGELOG below:

  https://github.com/mitchellh/vagrant/blob/v1.8.1/CHANGELOG.md

最新が1.8.1という事で、

上記に記載されているダウンロードURLから最新版を落としてダウンロード&インストール

 

インストール後のバージョン確認

$ vagrant version
Installed Version: 1.8.1
Latest Version: 1.8.1
 
You're running an up-to-date version of Vagrant!

バージョンアップに成功したので、気を取り直して改めて。

$ vagrant up
$ vagrant ssh
Last login: Tue Feb 23 05:11:42 2016 from 10.0.2.2
Welcome to your Vagrant-built virtual machine.
[vagrant@localhost ~]$

Kafka入手

下記サイトから最新版をダウンロード(2016.2.24時点の最新バージョン:9.0.1)

Apache Download Mirrors

[vagrant@localhost ~]$ wget http://ftp.tsukuba.wide.ad.jp/software/apache/kafka/0.9.0.1/kafka_2.11-0.9.0.1.tgz
[vagrant@localhost ~]$ tar xzvf kafka_2.11-0.9.0.1.tgz

ZooKeeper起動

[vagrant@localhost ~]$ cd kafka_2.11-0.9.0.1
[vagrant@localhost kafka_2.11-0.9.0.1]$ bin/zookeeper-server-start.sh config/zookeeper.properties 
/home/vagrant/kafka_2.11-0.9.0.1/bin/kafka-run-class.sh: line 167: exec: java: not found

と、ここでJavaのRuntimeエラー。

インストール可能なリスト確認

[vagrant@localhost kafka_2.11-0.9.0.1]$ yum search openjdk
読み込んだプラグイン:fastestmirror
Determining fastest mirrors
 * base: www.ftp.ne.jp
 * extras: www.ftp.ne.jp
 * updates: www.ftp.ne.jp
base                                                                                                                    | 3.7 kB     00:00     
base/primary_db                                                                                                         | 4.6 MB     00:00     
extras                                                                                                                  | 3.4 kB     00:00     
extras/primary_db                                                                                                       |  34 kB     00:00     
updates                                                                                                                 | 3.4 kB     00:00     
updates/primary_db                                                                                                      | 3.9 MB     00:00     
============================================================ N/S Matched: openjdk =============================================================
java-1.6.0-openjdk.x86_64 : OpenJDK Runtime Environment
java-1.6.0-openjdk-demo.x86_64 : OpenJDK Demos
java-1.6.0-openjdk-devel.x86_64 : OpenJDK Development Environment
・
・
・
java-1.8.0-openjdk-src.x86_64 : OpenJDK Source Bundle
java-1.8.0-openjdk-src-debug.x86_64 : OpenJDK Source Bundle for packages with debug on
icedtea-web.x86_64 : Additional Java components for OpenJDK - Java browser plug-in and Web Start implementation

  Name and summary matches only, use "search all" for everything.

SDKは不要でRuntimeだけ欲しいので、

最新版の java-1.8.0-openjdk.x86_64 をインストール

[vagrant@localhost kafka_2.11-0.9.0.1]$ sudo yum -y install java-1.8.0-openjdk.x86_64

再度ZooKeeper起動

[vagrant@localhost kafka_2.11-0.9.0.1]$ bin/zookeeper-server-start.sh config/zookeeper.properties
[2016-02-23 05:23:36,296] INFO Reading configuration from: config/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig)
[2016-02-23 05:23:36,299] INFO autopurge.snapRetainCount set to 3 (org.apache.zookeeper.server.DatadirCleanupManager)
・
・
・

起動しました

Kafka起動

[vagrant@localhost kafka_2.11-0.9.0.1]$ bin/kafka-server-start.sh config/server.properties
[2016-02-23 05:29:17,557] INFO KafkaConfig values:
・
・
・

Kafkaも問題なく起動

Topicの作成

Topicは送信側と受信側がやり取りするメッセージボックス名称。今回は「sample」としました。

[vagrant@localhost kafka_2.11-0.9.0.1]$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic sample
Created topic "sample".

topicが作成され、送受信の為の必要最低限の準備が整いました。

送受信

受信用のターミナルを開きながら、別ターミナルで送信コマンドを叩いてみます

送信側(Producer)
[vagrant@localhost kafka_2.11-0.9.0.1]bin/kafka-console-producer.sh --broker-list localhost:9092 --topic sample
send sample message

「send sample message」と送ると

受信側(Consumer)
[vagrant@localhost kafka_2.11-0.9.0.1]$ bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic sample --from-beginning
send sample message

受信側にも即時に反映されました!(テンションあがるところ)

PHP(送信)+ GO(受信)で実装してみる

今の職場の環境がこれに近いので、実際にPHP側でメッセージを送り、

GO側でメッセージを受け取る処理を実装してみました。

PHP extension:

PHPからKafkaを扱えるようにする為にextensionのインストール

こちらのマニュアルに沿ってインストールしました

https://github.com/EVODelavega/phpkafka

[vagrant@localhost]$ git clone https://github.com/EVODelavega/phpkafka.git
[vagrant@localhost phpkafka]$ cd phpkafka
[vagrant@localhost phpkafka]$ phpize
[vagrant@localhost phpkafka]$ ./configure --enable-kafka
[vagrant@localhost phpkafka]$ make
[vagrant@localhost phpkafka]$ sudo make install
[vagrant@localhost phpkafka]$ sudo sh -c 'echo "extension=kafka.so" >> /etc/php5/conf.d/kafka.ini'
[vagrant@localhost phpkafka]$ sudo sh -c 'echo "extension=kafka.so" >> /etc/php5/cli/conf.d/20-kafka.ini'
[vagrant@localhost phpkafka]$ php -m | grep kafka
kafka
[vagrant@localhost phpkafka]$

Kafkaのモジュールが追加されました。

サンプルプログラム

メッセージを送信する超シンプルなスクリプト

10回メッセージを送信しています

produce.php
<?php
$kafka = new Kafka("localhost:9092");
for ($i=0; $i<10; $i++) {
    $kafka->produce("sample", "message send test.".$i);
}
GOクライアントライブラリ

いくつかあるようですが、今回はこちらを使ってみました。

https://github.com/optiopay/kafka

[vagrant@localhost kafka_2.11-0.9.0.1]$ go get github.com/optiopay/kafka
consume.go
package main

import (
    "log"

    "github.com/optiopay/kafka"
)

const (
    topic     = "sample"
    partition = 0 
)

var kafkaAddrs = []string{"localhost:9092"}

func printConsumed(broker kafka.Client) {
    conf := kafka.NewConsumerConf(topic, partition)
    conf.StartOffset = kafka.StartOffsetNewest
    consumer, err := broker.Consumer(conf)
    if err != nil {
        log.Fatalf("cannot create kafka consumer for %s:%d: %s", topic, partition, err)
    }   

    for {
        msg, err := consumer.Consume()
        if err != nil {
            if err != kafka.ErrNoData {
                log.Printf("cannot consume %q topic message: %s", topic, err)
            }   
            break
        }   
        log.Printf("message %d: %s", msg.Offset, msg.Value)
    }   
    log.Print("consumer quit")
}

func main() {
    broker, err := kafka.Dial(kafkaAddrs, kafka.NewBrokerConf("test-client"))
    if err != nil {
        log.Fatalf("cannot connect to kafka cluster: %s", err)
    }   
    defer broker.Close()
    printConsumed(broker)
}

試してみよう

まずはGO側でメッセージ待機

f:id:kotakotaking:20160224181856p:plain

PHPを動かしてメッセージ送信

f:id:kotakotaking:20160224181900p:plain

結果

f:id:kotakotaking:20160224181911p:plain

だんヾ(*ΦωΦ)ノ ヒャッホゥ

Go x Hit&Blow

第3回 Goもくもく会

2/15(月)、株式会社エウレカさんが主催するGoもくもく会に参加してきました。

会場は明るい雰囲気でとても清潔感があり、エンジニアにとっては開発し易い環境だなぁと思いました。

こういった環境作りって大事ですよね。

eure.connpass.com

プログラム

最初の2時間がGOをもくもくといじる。後半1時間がLT&懇親会というプログラムで、

前半2時間は、もくもくするか、特定のお題目に沿ったプログラムを作るといった内容でした。

今回のお題目は「hit and blow」のプログラムを組むというもので、

駆け出しGoプレイヤーの私は勉強がてらこのお題目にチャレンジしてみる事にしました。

四苦八苦しながらでしたが、どのように実装したのか紹介したいと思います

Hit andBlow

hit and blowとは

4つの数字を互いに当てあうゲーム

ルール

  • お互いランダムな4桁の数値を用意
  • 数値は0〜9までの数値で同じ数値は使えない
  • 先攻の人が適当な数字を4つ言う
  • 後攻の人は○個の数字の位置があっていたら○ヒットという
  • 位置は違うけど△個数字が入っていたら△ブローという

(例)自分の数字が「1830」で「1248」と言われたら、「1」は場所が合ってるので1ヒット、場所は違うけど8が入っているので1ブローなので、「1ヒット1ブロー」という

実装よりも最適なアルゴリズムを考えるのが肝かな?と思ってましたが、GO初心者の私にとっては実装だけで数時間かかってしまいました汗

実装すべき機能要件は上記。1つずつ紐解いて実装していこうと思います。まずは、、、

  • 0〜9のランダムな4桁の数値
  • 同じ数値は使えない

この要件を満たす部分を実装しました。

package main

import (
    "fmt"
    "math/rand"
    "sort"
    "time"
)

func main() {
    answer := randGenerate(4)
    fmt.Println(answer)
}

func randGenerate(numLen int) []int {

    // システムが自動発行する数値
    var num int
    // 発番したn桁の乱数配列
    var nums []int

    rand.Seed(time.Now().UnixNano())
    for len(nums) < numLen {
        if len(nums) == 0 {
            nums = append(nums, rand.Intn(10))
        } else {
            num = rand.Intn(10)
            sort.Ints(nums)

            i := sort.Search(len(nums), func(i int) bool { return nums[i] >= num })
            if i < len(nums) && nums[i] == num {
                // numが既にnumsにあればリトライ
                continue
            }
            nums = append(nums, num)
        }
    }

    // 並び替え(シャッフル)
    n := len(nums)
    for i := n - 1; i >= 0; i-- {
        j := rand.Intn(i + 1)
        nums[i], nums[j] = nums[j], nums[i]
    }

    return nums
}

PHPが長かった私にとっては「ランダムな一意な4桁数値を発番」を実装するのにひと苦労。よく使う関数やアルゴリズムはライブラリ化していくなり何なりで足元固めていく感じかなーっていう印象でした。 とにもかくにも乱数ジェネレータが出来たのでここまでで一旦動作確認

$ go run main.go 
[1 5 6 9]
$ go run main.go 
[1 0 8 7]
$ go run main.go 
[6 9 1 2]
$ go run main.go 
[6 9 0 5]
$ go run main.go 
[6 7 8 4]
  • ランダムな4桁の数値
  • 数値は0〜9までの数値で同じ数値は使えない

問題なく動作しました。ここで一気に作り込み。

残りの入力値チェックと、ヒット&ブロウの突き合わせ部分を実装してみました。

package main

import (
    "bufio"
    "fmt"
    "math/rand"
    "os"
    "sort"
    "strconv"
    "time"
)

// 当てる数値の桁数
const NumLength = 4 

func main() {
    // トライ数
    var tryCnt int = 1 

    // ランダムな数値4桁発行
    answer := randGenerate(NumLength)

    // プロンプト表示
    q := fmt.Sprintf("%d桁の数値を入力してね", NumLength)
    fmt.Println(q)

    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {

        // システムが発行した乱数に合わせてユーザー入力値を配列に変換
        runes := []rune(scanner.Text())
        var arr []int

        for i := 0; i < len(runes); i++ {
            if j, err := strconv.Atoi(string(runes[i:(i + 1)])); err != nil {
                fmt.Println("予期せぬエラーが発生しました")
                break
            } else {
                arr = append(arr, j)
            }   
        }

        // 桁数チェック
        if len(arr) != NumLength {
            fmt.Println("入力された数値の桁数が不正です")
            fmt.Println(q)
            continue
        }

        // 重複チェック
        isDup := func(arr []int) bool {
            var a map[int]int = make(map[int]int)
            for _, v := range arr {
                a[v]++
                if a[v] > 1 {
                    return true
                }
            }
            return false
        }
        if isDup(arr) {
            fmt.Println("入力された値に同じ数値が複数回使われています")
            fmt.Println(q)
            continue
        }

        var hit, blow int
        for pos, val := range arr {
            if ok := isHit(answer, pos, val); ok {
                hit++
            }
            if ok := isBlow(answer, pos, val); ok {
                blow++
            }
        }

        // トライ回数インクリメント
        tryCnt++

        // ヒット&ブロウした数を表示
        fmt.Printf("%d Hit %d Blow\n", hit, blow)

        // 全部ヒットしたら勝ち
        if hit == NumLength {
            fmt.Printf("おめでとうございます!%d回目で全てヒットしました!", tryCnt)
            break
        }

        // 全部ヒットしなかった場合は再度ユーザープロンプトを表示
        fmt.Printf("%s(%d回目)", q, tryCnt)
    }
}

// ヒットしたかどうか
func isHit(arr []int, pos int, val int) bool {
    if arr[pos] == val {
        return true
    } else {
        return false
    }
}

// ブロウしたかどうか
func isBlow(arr []int, pos int, val int) bool {
    inArr := func(num int, arr []int) bool {
        for _, v := range arr {
            if v == num {
                return true
            }
        }
        return false
    }

    // ヒットしているものはブロウとしてカウントしない
    return inArr(val, arr) && !isHit(arr, pos, val)
}

func randGenerate(numLen int) []int {

    // システムが自動発行する数値
    var num int
    // 発番したn桁の乱数配列
    var nums []int

    rand.Seed(time.Now().UnixNano())
    for len(nums) < numLen {
        if len(nums) == 0 {
            nums = append(nums, rand.Intn(10))
        } else {
            num = rand.Intn(10)
            sort.Ints(nums)

            i := sort.Search(len(nums), func(i int) bool { return nums[i] >= num })
            if i < len(nums) && nums[i] == num {
                // numが既にnumsにあればリトライ
                continue
            }
            nums = append(nums, num)
        }
    }

    // 並び替え(シャッフル)
    n := len(nums)
    for i := n - 1; i >= 0; i-- {
        j := rand.Intn(i + 1)
        nums[i], nums[j] = nums[j], nums[i]
    }

    return nums
}

四苦八苦しながらでしたが何とかできました!

という事で、実際に遊んでみたいと思います

f:id:kotakotaking:20160217131246p:plain

#### 14回目でクリア出来ました〜

と、結果の画像間違って消してしまいました↑↑↑

という事で改めてチャレンジ!

f:id:kotakotaking:20160225164609p:plain    

8回目でクリア!!!

   

コードの善し悪しについては置いておいて、

作るものの目的があるとないとでは学習スピードが大きく変わってくると思うので、

是非みなさんもチャレンジしてみてくださいね。

最後に

10名くらいの方と名刺交換を行い、導入事例などを聞いてきました。

実際のサービスでGOを動かしているプロダクトはまだあまり無いようで、

お話しさせていただいた中では、これから導入を検討しているという方が9割程占めていました。

あと、新しいシステムをGOで作るというよりも既存システムをGOリプレイスしたいという声が多かったですね。

導入実績はどんどん増えてくると思うので、これからの可能性を存分に秘めてるなと感じました。