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 | 懇親会 |
勉強会の様子
それでは早速覗いてみましょう。どん!
Go勉強会!!!
勉強する意気込みがひしひしと伝わってきます。
Goを扱う猛者たちは今か今かと目を光らせ会場入りしてきます。
そんな中、Gopher君はいつもと変わらずゆる〜い感じで迎えてくれます。グッジョブGopher。
今回はCAグループ限定という事もあり、「事前予約なし、会場にきてね!」という謳い。
ここはGopher君が発する一言に模すなら「事前予約なし、会場にGoGo」だろ!と思う人も居たとか居ないとか。
そして、勉強会の時間がやってきたのですが・・・
なんと!当初の想定を大きく上回る100人以上のエンジニアが集まり、グループ内でもGoに対する関心が高い事が伺えました。
はい、その時の写真どん!!!
おーーーーーっ!
「Goをやってる」「興味がある」というエンジニアがグループ内でもこんなにいたんだ!とワクワクしてきます。
と、同時に、ワタクシも気分が高揚してまいりました。
そう!何を隠そう、今日はワタクシもスピーカーとして登壇する予定なのであります。
高揚しない訳がありません。
そして、、、いよいよ始まりました!Go勉強会
其の1(GoでつくるAbemaTV)
初めに登壇いただいたいのは、AbemaTVの開発エンジニア「西尾」さん
AbemaTVと言えば、皆さんご存知「無料でテレビ番組が見れるインターネットテレビ局」
そんな注目のプロダクトを1から開発してきた西尾さんに登壇いただきました。
日々のトラフィックをストレスなく捌く事はもちろんの事、
今後を見据え、柔軟にシステム拡張できるよう設計してるのはさすがの一言でした。
個人的に気になったのは、「外部ライブラリを使用する時には挙動を把握しましょう」という事。
内部のシステムをいかにしっかり作っても、外部ライブラリで思わぬPanicを発生させたりするものが結構あるそうです。
また、使っているライブラリ自体がそうでなくても、ライブラリが外部依存しているものだと、
ライブラリ自体は問題なくても、依存ライブラリがPanicを起こしたりなども多々あるそうで、
内製以外のライブラリを使用する際にはソースレベルで事前確認し、起こりえる症状をハンドルしないと危険だなと思いました。
其の2(Go言語とDockerでAIバトル基盤を書いたら便利だった話)
入社2年目にして堂々としたスピーチをしたのはアプリボットの「向井」さん
リッチなUI部分のみCocos2d-xを使い、それ以外はほぼGolangで書いたという「ボンバー猛者」
GoにAIの要素を盛り込み、4人対決のとあるゲームを作った話をしてくれました。
デモンストレーションが始まったと同時に、昔なつかしBGMが流れてきて会場が笑いの渦に包まれます笑
AIはアドテクスタジオも注目している分野なので、
4プレイヤー全てが独立したAIで、どのように学習していくのかとても興味が湧きました。
其の3(GAE/Goのススメ)
GAEについて登壇していただいたのは、同じアドテクスタジオに所属するLodeoの「池田」さん。
GAEとは、GoogleAppEnginenの略で、GoogleCloudで提供されるサービスの1つ。
みなさん耳にした事がある人もいるでしょう、SaaS(サーズ)ですね。
現在、GAEはJava、PHP、Pyrhon、Goをサポートしています(Standard Editionの場合)
動画元年という言葉が出たのも記憶に新しいですが、
SGAを活用しての動画生成サーバの話は未体験領域なだけに興味深かったですね。
其の4(10年物のレガシーPHPをGoにリプレイスしている話)
同じCAリワードに所属するスーパーエンジニアのDaichiです。
今回、彼は弊社のコアとなるシステムのGo化を推し進めている一人で、
現行のレガシーPHPをGoにリプレイスした際の進め方や設計方針などをスピーチしてくれました。
進行がとても上手で、しっかりと詰まった内容の中にも会場をホットにさせる笑いのトークがあり、
聞いている側はすっかり心を持っていかれてしまいました笑
其の5(クリラボにGo導入している話)
アドテクスタジオ iXam Creative Lab. に所属する「水沼」さんによる、インフィード広告でGoを採用した話。
元々Javaベースで開発していたものが一段落し、技術チャレンジとしてGoを取り入れたようです。
エンジニアもJava使いが殆どという中、敢えてGoでの開発に挑んだスピリッツ。素晴らしい!
チームとしてGoを習得するための取り組みで毎週Goの勉強会を実施したり、
「Go養成ギプス」と称した静的コード解析ツールを導入するなど、
これからGoに移行していく企業にとっては、チームの技術力底上げという観点で非常にためになる内容だったと思います。
其の6(優秀な若手に狂奔するオッサン劇場 〜生き残りをかけて臨んだGoの道〜)
はい。ワタクシ「小高」です。
PHPをメインとして主に管理画面の開発を行ってきましたが、このたびGoで配信システムの開発を行いました。
今までフロントからバッチまでPHP一辺倒で書き倒す事が多かったですが、
どうしてもバッチなどパフォーマンスが重要視される分野においては限界を感じていました。
優秀な若手に突き上げられながら、脱PHP → Goへの転身を果たし、配信サービスを作り切った話を発表させていただきました。
其の7(Goトラップ 〜中級者向けGo言語でよく引っかかる同期処理など周りの問題、分析と解決方法〜)
最後はAmeba技術本部の「Mario」さんが登壇
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(ガルプ)を使っていたので、触れた感想について書きたいと思います。
Node.jsのインストール
Gulpを使うにはNode.jsが必要です。公式サイトよりインストーラのダウンロードとインストールを行います。
インストール確認
$ 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を自動生成してくれるサイト。これは便利。
まとめ
今まで、JSとしっかり向き合う事があまりなかったです(反省)
Gulpに代表されるような便利な機能を使うか使わないかでは、開発効率に大きく差が出てきますね。
機械が出来る事は機械に任せて、人間にしか出来ない事をやる事でもっとラクしなければ!と思いました。
作業ログを収集して可視化する「WakaTime」
普段通りプログラムしているだけで、自動で作業ログを収集しグラフで可視化してくれるツール「WakaTime」を入れてみました。 これを入れると自分がどれくらいの時間、どの言語で開発しているかなどが可視化されて、プログラムに費やした時間などが分かるようになります。
サポートしているIDEも多数!普段使っているエディアがそのまま使えます。
自分はターミナル操作を行う事が多いので、今回はbash用のものをインストールしました。 画面左メニューのSupported IDEsから使用するエディタを選択します。
それぞれの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 に貼付けます
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
実際にターミナル上で何か作業をしてみると、こんな感じで可視化してくれました。
GOの開発の時にはIntelliJを使うので、同様にIntelliJにも入れてみました。 Gitのリポジトリ単位で集計してくれるので、各プロジェクトのアクティブ状態が分かります。
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)が、
ErlangがJavaよりも速くならない理由はどこにもありません。しかし、その奇妙な構文が人々を遠ざけ、普及と商用投資を妨げています。でも私はErlangが好きです。性能が重要なコンポーネントでの利用は少なくしますが、今後も致命的に重要なコンポーネントに使うつもりです。
とコメントした。Erlangが流行らないのは要は分かりにくいからとの事。
書き方にクセがあり難易度高めだけど、並行処理とか分散処理に向いていて、
これをRubyライクに分かり易くしたのがElixirだ。
Erlangが使われているところ
- 電気通信企業
- 広告ビッティングシステム
- 金融機関
リライアビリティーは年間稼働率99.9999999%との事で、 年に31.54ミリ秒しかダウンしないようです。これは凄いですね。 元々電気通信系のシステムから発したという事で、 かけた相手にきちんと電話が繋がるっていうのは当たり前のように感じてますが、 裏でこういうシステムでしっかり構築されてるんですね。
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)
[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側でメッセージ待機
PHPを動かしてメッセージ送信
結果
だんヾ(*ΦωΦ)ノ ヒャッホゥ
Go x Hit&Blow
第3回 Goもくもく会
2/15(月)、株式会社エウレカさんが主催するGoもくもく会に参加してきました。
会場は明るい雰囲気でとても清潔感があり、エンジニアにとっては開発し易い環境だなぁと思いました。
こういった環境作りって大事ですよね。
プログラム
最初の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 }
四苦八苦しながらでしたが何とかできました!
という事で、実際に遊んでみたいと思います
#### 14回目でクリア出来ました〜
と、結果の画像間違って消してしまいました↑↑↑
という事で改めてチャレンジ!
8回目でクリア!!!
コードの善し悪しについては置いておいて、
作るものの目的があるとないとでは学習スピードが大きく変わってくると思うので、
是非みなさんもチャレンジしてみてくださいね。
最後に
10名くらいの方と名刺交換を行い、導入事例などを聞いてきました。
実際のサービスでGOを動かしているプロダクトはまだあまり無いようで、
お話しさせていただいた中では、これから導入を検討しているという方が9割程占めていました。
あと、新しいシステムをGOで作るというよりも既存システムをGOリプレイスしたいという声が多かったですね。
導入実績はどんどん増えてくると思うので、これからの可能性を存分に秘めてるなと感じました。