ポン酢ブログ(β)

The harder you work, the luckier you get. - Gary Player

GolangでMemcached(を喋るサーバ)を書こうと思った話

某Advent Calendar 8日目の記事です。

途中で止まってしまったので、そのうち再開するかも...ということでここで一回供養する話です。

Memcacheプロトコルを喋るサーバを勉強と検証ということで作ろうと思い、色々とやっておりました。

モチベーションとしてはMemcacheプロトコルをしゃべるが、データを永続化しながら色々できるようにするという目的です。決してMemcachedのクローンを作る話ではなありません。(速い要素を実装する力がないため)

一応こちらに置いてありますが、作りかけのまま終わってしまいました。

できること

  • Get
  • Set
  • Delete
  • Incr/Decr
  • Flush

ちなみに若干バグがありますが、そこは🙈

大変だったこと

  • 変なクライアントに当たるとつらい
    • コネクションを持ったままのクライアントだとたまに落ちるということがあったので、そのパケットを飛ばすと言うところを実装するのが結構つらかった
  • CAS対応
    • まだちゃんと実装しないまま終わってしまいました

実装について

今回はテキストプロトコルではなくバイナリプロトコルで実装を行いました。バイナリプロトコルBinaryProtocolRevamped · memcached/memcached Wiki · GitHubが参考になりました。あとはひたすらパースする感じです。今回はストレージにSQLite/LRUなインメモリストレージをが使えるようにしてあります。

基本的な実装はserverでパケットを受け取り、handlerで各タイプを処理していきます。storageパッケージでストレージは抽象化していて、そこからSQLiteやLRUなストレージを操作するという感じです。Memcachedのバイナリプロトコルは結構わかりやすく、Opcodeを見てそれぞれ処理していく、あとはQuietlyに気をつければなんとかなります。たまに本物のMemcachedにリクエストを投げながらつじつまを合わせたりしました。(本当はソースを理解できればよかったけど)

一応、ベンチマークを取ってみましょう。ストレージをSQLiteにした結果はこちらです。とりあえずベンチが回るところまで作れて良かったなあという感想です。

ALL STATS
=========================================================================
Type         Ops/sec     Hits/sec   Misses/sec      Latency       KB/sec
-------------------------------------------------------------------------
Sets         1330.79          ---          ---     25.04900        98.88
Gets        13293.28      4505.96      8787.32     13.13200       466.84
Waits           0.00          ---          ---      0.00000          ---
Totals      14624.07      4505.96      8787.32     14.21600       565.73

遅すぎますが、一応使えることが分かります。LRUにすると、こうなります。

ALL STATS
=========================================================================
Type         Ops/sec     Hits/sec   Misses/sec      Latency       KB/sec
-------------------------------------------------------------------------
Sets         1524.89          ---          ---     12.94800       113.30
Gets        15232.17     15230.34         1.83     12.65700       534.94
Waits           0.00          ---          ---      0.00000          ---
Totals      16757.06     15230.34         1.83     12.68400       648.24

実際のmemcachedはこちら。比べものにならないことがわかります...。(早いモノを作るのが目的じゃなかったので良いのですが)

ALL STATS
=========================================================================
Type         Ops/sec     Hits/sec   Misses/sec      Latency       KB/sec
-------------------------------------------------------------------------
Sets        12417.96          ---          ---      1.45500       922.69
Gets       124043.18    123961.91        81.26      1.46400      4356.26
Waits           0.00          ---          ---      0.00000          ---
Totals     136461.14    123961.91        81.26      1.46300      5278.94

ちなみに、RubyのMemcacheクライアントのDalliから使って見ましょう。

irb(main):003:0> d = Dalli::Client.new("localhost:11211")
=> #<Dalli::Client:0x00007f92d2046d90 @servers=["localhost:11211"], @options={}, @ring=nil>
irb(main):004:0> d.set("test",1)
=> 15988849166174051443
irb(main):006:1" ^C
irb(main):006:0> d.get("test")
=> 1
irb(main):007:0> d.version
=> {"localhost:11211"=>"0.0.1"}

きちんと値がセットされていることが分かります。

今回のこれで具体的なサーバの書き方が若干分かったり(今見ると直したいところが一杯ありますが)、バイナリと仲良くなれたのは良い経験だったなと思いました。ちゃんと書き直すのはいつになるか分かりませんが、とりあえずこれで供養をしておきます。

pcapをパースする(その3・IP/ICMP編)

これまで以下の記事でEthernetフレームやARPのパケットをpcapファイルからパースするために必要なフォーマットについて解説していきました.

pcapをパースする(その2・ARP編) - ポン酢ブログ(β)

pcapをパースする(その1・ヘッダ編) - ポン酢ブログ(β)

引き続き,IPやICMPをパースしてみたいと思います.

IPパケット

IPパケットはRFC 791で策定されています.

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

今回のパースは,Golangの準標準パッケージであるnetを使ってみる練習がてら,パースをそれに任せてみました.

実際にパースをする場合は,Parseを呼び,そこに[]byteでパケットを入れてあげれば大丈夫です.

気づきとしては,socket.NativeEndianには動作するOSのbinary.ByteOrderが入っており,それを使って処理していました.

実装はこちらになっていて,パースしたあとにその後ろの[]byteを返すようになっています.

ICMPパケット

IPパケットまでパースできたので,関連するICMPパケットについてもパースしていきたいと思います.ICMPはRFC 792で策定されています.普段pingコマンド等で馴染みのある人もいるのではないでしょうか.

ICMPのメッセージには多くの種類があり,こちらが参考になります.

今回のパースも,Golangの準標準パッケージであるnetを使ってみる練習がてら,パースをそれに任せてみました.実際にパースと言っても,structに値をマッピングしていく形になります.パッケージでの実装を参考にしながら使っていきますが,internalでメッセージのタイプが隠れていたため,仕方なく同じものを自分て定義しそれを使っています.

実際の実装部分です.

自作のものとつなげていく

自作しているpcapのパーサーでは,Packetインターフェースを作り,それを満たすように各データを格納しています.そして,実際にに読み込んでいる部分はこちらになります.

実際にファイルを読み込ませてみると,パースできていることがわかります.

f:id:atpons:20191120135452p:plain

実はこの他にも追加した機能があるのですが,それはまた別の機会に説明します.

pcapをパースする(その2・ARP編)

pcapをパースする(その1・ヘッダ編) - ポン酢ブログ(β)で、pcapのパースをしてみたので今回はARPのパースをやってみます。

実はARPのパケットをパースするためには、まずEthernetをパースする必要があります。今後パケットをパースする際にEthernetが読めないとなんともならないので、今回はまずEthernetフレームを読んでいきます。

続きを読む

pcapをパースする(その1・ヘッダ編)

libpcapで使われる、pcapファイルのパースにチャレンジしてみる。 今回はヘッダと生のパケットを取り出すところまでです。

パケットの構造

このブログの情報を参考にさせていただいた。この先にリンクされている、日立の資料の図がわかりやすい。

sorenuts.hatenablog.com

Global Header

マジックナンバーやタイムスタンプが格納されている。

パケット

Record(Packet) Header

incl_lenが実際のパケット長なので、これの長さ分を取り出してくれば良い。

Packet Data

incl_len分のパケットが入っているので、これを取り出せば良い。

実際にパースする

Golangで実際に読み込んでパースしてみる。 https://github.com/atpons/39ac/blob/ef55112/pcaptool/pkg/pcap/read.go で実装している。

これをWiresharkのSampleCapturesで配布されているipv4frags.pcapで、実行してみると、このような結果になり、実際にWiresharkで見比べても正しく並んでいることが分かる。

おしまい

  • スライス作りすぎなのでどうにかしていきたい
    • 今後は消しても問題なくなるように作るのでsync.Poolをちゃんと使っていくか再利用するなどをしたい

2018年に寄せて

この記事は群馬大学電子計算機研究会 IGGG Advent Calendar 2018 1日目の記事です

2018年あと一ヶ月!のまとめです.

  • セキュリティ・ミニキャンプで講師をしました.

    • 教えるという貴重な経験をしました.これを起点として,コミュニティにも関わっていく経験を今でも続けています..
  • B3になる.

    • 真面目に進路とか考えはじめる.
  • IGGG 部長へ・・・

  • 夏コミ

    • みなさまありがとうございすした
  • はじめてのインターン

    • 研鑽を積んでいきたいと思いました.ポン酢はおいしかったです.

  • いろいろと挑戦する.
  • 後期暇すぎじゃね・・・?
    • 忙しいのだけど

  • 自覚生える・・・

来年は研究室にも配属されて本当に大学生っぽいことになりそうです.やったね.

2017年はこれ: atpons.hateblo.jp

2018夏、ミクシィ(XFLAG・モンスト)でインターンしてきました

f:id:atpons:20180930181409j:plain 大学3年の夏休み、いろいろとイベントがありましたがインターンシップに参加してみました。 あまりインターンシップとか、実務したりというのがなかったのですが、今回はミクシィというところでインターンをして来ました。

どう選んだか

大きいサービスを触ってみたいというのがありました。その運用とかにも興味があるけれど、まずは開発もできないといけないワケで、そうなってくると、いろいろと限定されていきました。あと、Rubyを書きたかった。Rubyを使って、大規模なサービスを回しているというのにスゴく興味をそそられるからだ。 ミクシィは、オンプレ環境も昔から運用されていましたし、まさにフルクラウド、というワケでもないのが個人的に良いなと思いました。もちろんこれからフルクラウド、サーバレスの開発や運用も出てくるわけですが、どっちかに強くないと、片方も強くなれないんじゃないかという危惧感が自分の中にはあります。(もっともらしいことを書いてみるテスト)

↑(追記:「というワケでもない」というと語弊がありそうですが、もちろんクラウドだって活用されています!)

なにをしたか

XFLAGスタジオのなかで、モンスターストライクというゲームのサーバサイド開発を行っていました。開発だけじゃなくて、その周りの整備もいろいろとさせていただいた。開発だけじゃなくて、シャカイの立ち回りとかも勉強になった。もちろん、メンター氏がついてくれるのでそこらへんはイロイロ教えてくれるので安心できる。

インターン前、何回か面談をしたりしたが、「強すぎる、ヤバい」という感情になり自信を喪失していたけれど、なんとか頑張ろ、と思ってがっつりイロイロと経験した。やる気大事。

xflag.com

そうそう、モンスターストライクというゲームは、ワタクシが高校生の時にサービスインして、当時よく遊んでいたのを覚えている。さっき昔のスマホを出してみたら、まだmixiロゴのまま、しかもXFLAGロゴなしのアプリアイコンが出てきて、タップしたら「お使いのバージョンには対応していません」だった。もちろん、今は別のアカウントで遊んでいる。

話は変わったが、中がどういう雰囲気なのか、というのはオウンドメディアを参考にすると良いとおもいます。↓とか。(話したらマズいこと話せないし。) career.xflag.com

チームで開発するので、そのときのフローとかも体験できたし、雰囲気というのもかなりつかめたと思う。

なにを得られたか

開発がどうとかいうより、まず、実際にサーバの台数を見るとスゴかった。この規模で運用回してるのか、と思ってやっぱりびっくりしたし、そのための体制づくりも見れたと思う。

codezine.jp

上の記事では、モンスターストライクのサーバサイドのアーキテクチャについて書かれているが、規模感とかそういうのはそれで想像できると思う。素直なので、そのおかげでインターン中スムーズに色々できたのかな、というのがある。

しかもタイミングよくモンスターストライクが5周年を迎えるので、いろいろとアツい時期に入れさせてもらったので、わくわくするような体験ができた。

いろいろ

インターン期間の話

インターン期間は他のインターンしている人たちとかとも気軽に話せるし、全然不安になったりすることはないと思う。イロイロとサポートもある。

ランチとか

いろいろと渋谷のランチを食べることができました。よかったし、話せるのいい。

もらった

オラゴンもらった。かわいい。 なぜかポン酢をインターン期間中に計3本手に入れた。たぶん高血圧で死ぬ。 あとじゃがりこも結構食べたし持って帰ってきた。諸事情でハコで所有している方から頂いた。。。 f:id:atpons:20180930181644j:plain f:id:atpons:20180930181315j:plain

さいごに

一ヶ月ちょっととワタクシを世話していただいたメンター氏はじめ、チームの方々本当にありがとうございました!間違いなく「ケタハズレな冒険」ができました。

あと、もっと自信もっていきたいなと思った。萎縮しないでいろいろと話しかけたり、体験することの重要さを身を以て実感することができた。