Norikra でライブ配信サービスのなんちゃって同時視聴者数カウンタを作った話


スマホ動画サービスなどのライブストリーミング配信とかやってると、NOW ON AIR なユーザ番組のリアルタイム視聴数の確認・ロギングを行いたかったりする。
しかしながら視聴側が CloudFront などの CDN 業者を介した配信だったりする場合、配信番組ごとの視聴数を直接取得するのが難しかったりするので（簡単に取れたりするのかな・・汗）、視聴数取得用に別の常時接続システムを入れたりすることになるのではと思う。

なぜ Norikra
===========

今回は配信に紐づくチャットルームはあるものの、WebSocket や MQTT などのナウいものではなく愚直な 5 秒間隔での HTTP ポーリングで更新チェックを行っているシステムが対象のため、常時接続環境下では容易に行えるであろう現在の接続数のカウントアップが少し難しい。
シンプルなソケット通信で常時接続を張るようなサーバを新たに構築してもよいのだけれど、クライアントアプリ側での追加実装・リリースも必要だったりするため今あるシステムでなんとかできないかと考えたところ Norikra に行き着いた。

Norikra はログなどのイベントストリームに対し、専用クエリをショートスパンでバッチ処理する JRuby 製のツール。
巷では各アプリサーバに配置した fluentd と組み合わせて異常値検知や監視を自前で組み立てる際などによく使われてるみたいですね。
前々から気になってはいたのだけれどなかなか使う機会がなかったため、ここぞとばかりに取り入れてみることに。


構成
===

シンプルにアプリサーバに配置された fluentd から Norikra サーバにアクセスログを集約し、さらにそこからクエリ結果をフェッチし続けることで直近の同時接続数を確認できるようにする。

Norikra サーバ側設定 1
------------------

### Norikra インストール

当たり前だけど [Norikra](http://norikra.github.io/) をインストールしておく。
さらに下記のように起動してデーモナイズしておく。

```
$ norikra start -d 
```

これで公式ページに紹介されているような WebUI や `norikra-client` コマンドが利用できるようになるので軽く確認しておくと良いかもしれない。


アプリサーバ側設定
---------------

ログ転送元のアプリサーバには予め [fluentd](http://www.fluentd.org/) および [fluent-plugin-norikra](https://github.com/norikra/fluent-plugin-norikra) をインストールしておく。
fluentd に下記のような設定を追記することでアプリサーバが吐いたログを Norikra に転送できる。

```
<source>
  type     tail
  format   json
  path     /var/log/api/access.log # アクセスログファイル
  tag      api_access.server01
  pos_file /var/log/td-agent/tmp/tail_api_access.pos
</source>

<match api_access.**>
  type           norikra
  norikra        norikra.local:26571
  target_string  api_access
  target_map_tag false
</match>
```

もちろん設定後には fluentd の再起動を忘れずに。

Norikra サーバ側設定 2
--------------------

上記設定でアプリサーバのアクセスログが正常に Norikra に送られてきている場合、自動的に `api_access` という target が作成されているはず。

```
$ norikra-client target list
TARGET	AUTO_FIELD
api_access	true
1 targets found.
```

この target に対し query を追加する。
WebUI から下記のような EPL を `count_audience` のような名前で登録すれば簡単。 

```
SELECT
  uri.substring(xx, xx) AS stream_id,  -- IDだけ抜き出し
  COUNT(*) AS cnt                      -- 5 秒間に各配信番組に対してリクエストされたポーリング回数
FROM
  api_access.win:time_batch(5 sec)     -- 5 秒の間隔でクエリを実行する
WHERE
  method = 'GET' AND
  uri like '/streams/%/messages%'      -- メッセージポーリングを識別できるパス
GROUP BY
  uri.substring(xx, xx)
```

このクエリの実行結果は WebUI からもサンプルとして確認できるけど

```
$ norikra-client event fetch count_audience
```

とすることで

```
{"time":"2015/09/27 00:12:08","stream_id":"[stream_id.001]","cnt":35}
{"time":"2015/09/27 00:12:08","stream_id":"[stream_id.002]","cnt":201}
{"time":"2015/09/27 00:12:08","stream_id":"[stream_id.003]","cnt":170}
{"time":"2015/09/27 00:12:13","stream_id":"[stream_id.001]","cnt":40}
{"time":"2015/09/27 00:12:13","stream_id":"[stream_id.002]","cnt":199}
{"time":"2015/09/27 00:12:13","stream_id":"[stream_id.003]","cnt":171}
{"time":"2015/09/27 00:12:18","stream_id":"[stream_id.001]","cnt":39}
   .
   .
   .
```

のような結果セットをすべて得ることが出来、単位時間あたりのなんちゃって同時視聴者数をカウントすることができる。
もちろんポーリングされてる間クエリ結果もどんどん溜まっていくので定期的に `event fetch` することで最新の結果が得られるようになる。

まとめ
=====

正直後ろ向きなアプローチではあるけどこんなこともできるよ、的な事例レベルで Norikra の紹介してみた。
各種インストールとか公式ページでわかることはあえて端折ったけど、fluent-plugin-norikra の NorikraInput とか使って結果を他のデータソースにさらに横流しすることでよりシームレスなシステムになると思う。