Contents...Redisサーバ機能のみを計算機に設置したい場合は、下記のようにサーバ計算機の上でインストールする。...
Transcript of Contents...Redisサーバ機能のみを計算機に設置したい場合は、下記のようにサーバ計算機の上でインストールする。...
2
Contents
1 はじめに 5
2 RedisPubSubを用いたアプリケーションの可能性 7
3 インストール 11
3.1 Redis関連パッケージ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 Redisサーバの設定 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.3 RedisPubSubパッケージ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4 例題 15
4.1 publisherとsyncSubscriberの例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5 DAQMWコンポーネント 21
5.1 PublisherMlf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.2 T0EventPublisherMlf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.3 DaqInfoPublisher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.4 GathererPsdPubコンポーネント . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.5 DAQMWのPublisherMlfとwriteDataToDiskの例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6 Doxygenで内部仕様を見る 25
7 RedisPubSubの性能評価 27
7.1 性能評価のための計算機構成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
7.2 Redisサーバの設定パラメータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
7.3 性能評価に使用したPublisher/Subscriberプログラムとその評価結果 . . . . . . . . . . . . . . . . . 27
7.4 DAQMWコンポーネント、PublisherMlfを利用したケーススタディー1 . . . . . . . . . . . . . . . . . . 29
7.5 DAQMWコンポーネント、PublisherMlfを利用したケーススタディー2 . . . . . . . . . . . . . . . . . . 30
7.6 Redisサーバのバッファメモリ使用量 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3
4 CONTENTS
8 参考文献 31
Chapter 1
はじめに
RedisPubSubはJ-PARC/MLFの計算環境の中で生成されるさまざまな情報、例えば中性子検出器からの生データ
やタイミング情報やステータス情報を効率良く利用する仕組みを提供する。
RedisPubSubで利用する情報モデルは、近年盛んに計算機科学分野で研究されてきたいわゆる出版・購読型モデ
ル(Publish/Subscribe model)である。 このモデルの特長は、空間的な疎結合・時間的な疎結合・同期的な疎結合
を実現させるところにある。 空間的な疎結合とは、情報を発信する者は情報を受け取る者がどこにいるかを知る必要が
なく、情報を受け取る者は情報発信者が誰であるかを心配する必要がない。 必要なのは情報のキー名(チャネル名)で
あり、接続するサーバ名だけである。 時間的な疎結合とは、情報の発信(出版)と受け取り(購読)が同時であってもい
いし同時でなくてもいいことを意味する。 同期的な疎結合とは、購読者は非同期に読むことができることである。 非同
期にできるとは、購読予約を行い情報が発信された時に読むことができることである。
空間的な密結合・時間的な密結合・同期的な密結合でありDAQ-Middlewareでのデータ転送に利用している、リ
モートプロシージャコール(RPC)と対比してみるとその違いがわかる。 RPCは、リモートサーバにあるメソッドを呼び出
す仕組みだが、呼び出す方も呼び出される方も、わかった相手と交信し同時に交信するという密結合である。もちろん
購読予約もできない。 出版・購読型モデルが提供するそれらの疎結合は、さまざまな利点をもたらす。 情報のキー名さ
え知っていれば、誰に送るべきか、その情報がどこにあるか、を知る必要がないため、情報の発信・取得のメタ情報は少
ない。 また、いつでも情報を出版・購読できるため、それぞれの都合のいいタイミングで出版・購読できる。 さらに、非同
期であるため、ブロックされたりポーリングするなどのよけいな処理が不要で、効率的である。
このように優れた特徴を持つ出版・購読型モデルを持つソフトウエアパッケージの中で、Redisを選択した。
Redisは、いくつかの異なるデータ型をサポートするメッセージブローカー(サーバ)であり、メモリにデータを格納するた
め高速な処理を実現させる、オープンソースのソフトウエアである。
RedisPubSubでは、Redisの出版・購読型モデルを実現するAPIを利用し、J-PARC/MLFに利用しやすいように
Redisの複雑さを隠蔽し簡単なAPIにラップした。 当面提供するAPIはC++だが、Python版は検討中である。
このマニュアルは、RedisPubSubの外部仕様を解説するものである。
5
6 CHAPTER 1. はじめに
Chapter 2
RedisPubSubを用いたアプリケーションの可
能性
RedisPubSubを用いてすでにJ-PARC/MLFではLive Data Reduction(実時間・オンライン解析)の利用が始まっ
ている。
Figure 2.1: 空蝉Live Data Reduction
今までもデータ測定中にDAQ-Middleware(DAQMW)コンポーネントとしてオンライン解析を走らせていた。 ただ、す
でに述べたようにDAQMWコンポーネントはデータ収集(Gathererコンポーネント)と密に結合しているため、オンライ
ン解析のちょっとしたミスがデータ収集システムをダウンさせる。 そのため、DAQMWコンポーネントによる解析プログ
ラムの開発は進まなかった。
そこで、データ収集から切り離し(疎結合にして)データファイルからデータを読み出すという方法を取ることで、たとえ
解析プログラムがこけてもデータ収集システムがダウンしないような仕組みが出来上がった。 大きな進歩であり、これ
により開発された解析プログラムを気兼ねなく試せるようになった。
7
8 CHAPTER 2. REDISPUBSUBを用いたアプリケーションの可能性
Figure 2.2: BL21オンラインモニター
Figure 2.3: BL17用2次元検出器オンラインモニター
9
RedisPubSubを用いたアプリケーションも「解析プログラムがこけてもデータ収集システムがダウンしないような仕組
み」になっている点では同じである。 それでは何が新しいのか?1つはその仕組みを一般化したことである。 情報を発
信することもその情報を利用することも自由になり(疎結合になり)、生データに限らずあらゆるデータをこの仕組みで
扱えるようになった点である。 しかもその仕様がシンプルである点も強調したい。
生データファイルからデータを読み出す方法でも、1つのファイルを複数の解析プログラムが利用することは可能だ。 読
み出しを開始するタイミングも読み出しを終了させるタイミングも、この方法でも自由に得られる。 しかし、書き込みと読
み込みが同時に行われるため、性能や安全の視点で問題があった。 また実際には生データは1つの計算機やNFSに
集中管理されているため、1点にアクセスが集中するという問題もあった。 RedisPubSubは複数のRedisサーバに分
散させることが容易なため、それらの問題を解決することができる。
RedisPubSubによって発信された生データの利用は、オンライン解析に止まらずに、さらに広げることが可能だ。 1つ
の例は、DAQMWコンポーネントLoggerMlfが行なっている機能をSubscriberで行うことである。 全装置グループの
生データを一括管理して利用するとき、生データの一元管理が容易になる仕組みが作りやすくなる。
生データに限らず、例えば中性子位置検出器(PSD)からのデータのオーバーフローをリアルタイムに検出する仕組み
をGathererPsdPubで試みている。 定期的にオーバーフロー情報が発信できれば、オーバーフローが起きた時点で、
担当者に電子メールを送るという仕組みを作ることができる。
新しいIROHA2にもRedisが実装され、デバイス情報も発信可能となった。モニタすべきデバイス情報を集め、デバイ
ス情報の相関を見るのも容易になる。
RedisPubSubのもとになっているRedisはPublish/Subscribeという優れた機能を持っていることはすでに述べた
が、情報の取り扱い方法にはさらに異なる形態がある。 分散キュー機能は、データをキューに入れて必要な全てのデー
タを溜めておくことで、後から参入するオンライン解析でも全てのデータを扱う仕組みを容易に実現させることができ
る。 また、分散共有メモリ機能も有用な1つである。Pub/Subと異なり、情報は上書きされる。 常に最新情報を保持する
という機能は、Pub/Subにはない利用が可能である。 Redisはこれらの全ての機能を実現させることができるので、必
要に応じて、RedisPubSubの機能を補う形で発展させることが可能だ。
10 CHAPTER 2. REDISPUBSUBを用いたアプリケーションの可能性
Chapter 3
インストール
RedisPubSubはMLF計算環境プロジェクト管理サーバのSVNから提供される。
https://mlfdevsv.j-parc.jp/MlfSoftware/rep/redispubsub
RedisPubSubのインストールの前にRedis関連パッケージのインストールや設定を行う。
3.1 Redis関連パッケージ
下記のパッケージをインストールする。
sudo yum -y install redis hiredis-devel python-redis libevent-devel
Redisサーバ機能のみを計算機に設置したい場合は、下記のようにサーバ計算機の上でインストールする。
sudo yum -y install redis
CentOS7におけるRedisの自動立ち上げの設定と立ち上げの方法は下記の通り。
sudo systemctl enable redis
sudo systemctl start redis
CentOS6におけるRedisの自動立ち上げの設定と立ち上げの方法はCentOS7と異なり、下記の通り。
sudo chkconfig redis on
sudo service redis start
3.2 Redisサーバの設定
いくつか設定パラメータがある。
1つ目は、/etc/redis.confの編集で、このredisサーバがどこからでも接続できるようにするには、下記のように設定す
る。
bind 0.0.0.0
11
12 CHAPTER 3. インストール
2つ目は、同じファイル/etc/redis.confの編集で、下記の設定はコメントアウトする。 デフォルトでは、データを定期的
にディスクに書き込むことになっているが、高速性を追求するため、コメントアウトする。
#save 900 1
#save 300 10
#save 60 10000
3つ目は、redisのバッファ管理、つまり計算機の中で利用できるメモリに関するパラメータである。
client-output-buffer-limit pubsub 50gb 40gb 60
最初の50gb(デフォルト値は30mb)はhard limitと呼ばれ、バッファが50GBに達したら、その時点で接続が切れる。
次の40gb(デフォルト値は8mb) 60(デフォルト値も60)はsoft limitと呼ばれ、バッファが40GBを超えた時間が連続
60秒を超えたら、接続が切れる。 接続が切れると、データの送受信ができなくなるので、このパラメータは可能な限り大
きくすることが望ましい。
3.3 RedisPubSubパッケージ
パッケージを取得し展開する。
svn export https://mlfdevsv.j-parc.jp/MlfSoftware/rep/redispubsub/0.9/trunk redispubsub
さらに、makeする。
cd redispubsub/cpp
make
基本機能のテストのためにUnit Testを実施する。Unit Testに使うフレームワークはGoogle Testである。 パッケージ
のインストールは下記の通り。
sudo yum -y install gtest-devel
まずRedisサーバを再度立ちあげる。
sudo systemctl restart redis ( for CentOS7)
sudo service redis restart ( for CentOS6)
次にUnit Testをコンパイルしてから実行させる。
cd redispubsub/cpp/unittest
make
./gtest
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from RedisPubSub
[ RUN ] RedisPubSub.TestTextData
[ OK ] RedisPubSub.TestTextData (1 ms)
3.3. REDISPUBSUBパッケージ 13
[ RUN ] RedisPubSub.TestBinaryData
[ OK ] RedisPubSub.TestBinaryData (1 ms)
[----------] 2 tests from RedisPubSub (2 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (3 ms total)
[ PASSED ] 2 tests.
このようになればOK。
14 CHAPTER 3. インストール
Chapter 4
例題
例題を動かしてみよう。RedisPubSubパッケージにはC++版のPublisherとSubscriberの例題が含まれている。
4.1 publisherとsyncSubscriberの例
このpublisherはテキストデータとバイナリーデータを発信することができる。 それぞれ、チャネル名はmlftext、
mlfdataである。 また、syncSubscriberはそれらのデータを受け取り表示する。 ソースコードを眺めながら、その
使い方について解説する。 RedisPubSubパッケージを利用する場合は、下記の通りRedisPubSub.hファイルを
includeする。
#include "RedisPubSub.h"
4.1.1 クラスRedisPublisherの使い方
クラスRedisPublisherはRedisPubSubのpublish機能を提供する。 initメソッドで行うことは、Redisサーバへの接
続であり、それが成功すると返り値として0を返す。 なんらかのエラーとなると、負の値を返すので、必ずチェックする必
要がある。 Redisサーバのホスト名(IPアドレスでも良い)とポート番号はデフォルトで、それぞれlocalhost、6379と
なっている。 下記の例はデフォルト値を使った宣言である。
RedisPublisher pub;
status = pub.init();
Redisサーバのホスト名(IPアドレス)とポート番号の変更をしたい場合は、下記のように指定する。
RedisPublisher pub("192.168.100.10", 6666);
上記のような宣言の他に、スマートポインタを利用した方法がある。 スマートポインタは動的なメモリ確保、この場合
はnew RedisPublisher("127.0.0.1")で生成されたRedisPublisher用のポインタを確保し、使用済みの時に、自動
的に開放してくれる。 メモリーリークの心配から開放してくれるので有用である。
std::unique_ptr<RedisPublisher> pub;
pub.reset(new RedisPublisher("127.0.0.1"));
status = pub->init();
15
16 CHAPTER 4. 例題
一度上記のように、接続が確立すると、RedisPublisherはテキストデータやバイナリーデータをpublishメソッドで発信
できるようになる。 下記の例は、テキストデータをmlftextというチャネル名で発信する。
int32_t loop = 0;
string s = "test data";
stringstream ss;
ss << s << loop;
status = pub.publish("mlftext", (char*)ss.str().c_str(), (int32_t)ss.str().size());
publishメソッドの引数は、publish(string, char*, int32_t)である。返り値は、負の値はエラーとなり、0以上で、この発
信されたデータを受け取ったSubscriberの数を返す。 バイナリーデータを送る場合も、同様にpublishメソッドを下記
のように利用出来る。
char buf[1000];
for (int i=0; i<1000; i++) {
buf[i] = i+1;
}
buf[0] = (loop+1)%256;
status = pub.publish("mlfdata", (char*)buf, (int32_t)sizeof(buf));
4.1.2 クラスRedisSyncSubscriberの使い方
クラスRedisSyncSubscriberは スーパークラスとしてRedisSubscriberを持つ。 RedisSubscriberは、共通した
Subscribe機能を提供する。 RedisSyncSubscriberはRedisSubscriberを継承し、同期型のsubscribe機能を提供
する。 まだ非同期型のsubscribe機能は実装されていないが、早々に実装する予定である。
initメソッドで行うことは、redisサーバへの接続であり、それが成功すると返り値として0を返す。 なんらかのエラーとな
ると、負の値を返すので、必ずチェックする必要がある。
Redisサーバのホスト名(IPアドレス)とポート番号はデフォルトで、それぞれlocalhost、6379となっている。 下記の例
はデフォルト値を使った宣言である。
すでに述べたようにスマートポインタを利用した場合と利用しない場合があるが、ここでは利用する例を示す。 少し分
かりにくくなっているが、resetメソッドを呼び出す際、RedisSyncSubscriberのホスト名とポート番号の設定方法のい
ろいろを示している。
unique_ptr<RedisSyncSubscriber> sub;
//sub.reset(new RedisSyncSubscriber);
sub.reset(new RedisSyncSubscriber(host_name));
//sub.reset(new RedisSyncSubscriber(host_name, 6379));
//sub->setHost(host_name, 6379);
status = sub->init();
下記の例は、すでにsubscribeしてあるチャネル名がmlf*(mlftextとかmlfdataとか)であるチャネル名の全てを表示
するものである。 getChannelsメソッドの返り値が0の場合は成功であり、負の値はエラーを意味する。 このメソッドは
subscribeメソッドを呼び出す前か、unsubscribeメソッドの後から呼び出し可能である。
4.1. PUBLISHERとSYNCSUBSCRIBERの例 17
vector<string> vec;
status = sub->getChannels("mlf*", vec); // all of channels
cout << "status = " << status << endl;
cout << "list of already subscribed channels : ";
for (int32_t i=0; i< (int32_t)vec.size(); i++)
cout << vec[i] << " ";
cout << endl;
subscribeメソッドは複数のチャネル名をsubscribe出来る。
string name_text = "mlftext";
string name_data = "mlfdata";
status = sub->subscribe(name_text);
cout << "status = " << status << endl;
status = sub->subscribe(name_data);
cout << "status = " << status << endl;
subscribeメソッドの返り値は、成功すると0、エラーが起こると負の値が返る。
次は、getメソッドでデータを読む方法を示す。同期型のメソッドなので、一定時間データがpublishされていない場合
は、タイムアウトして返り値1が返る。 デフォルトのタイムアウト時間は2000ミリ秒(2秒)であるが、指定することも可能
である。 返り値は、成功すると0であり、エラーが起こると負の値である。 成功しても、lengthが0の場合データは入って
いないので、処理をしない。
for (int32_t i=0; i<loop; i++) {
status = sub->get(name, (char*)buf, length);
//status = sub.get(name, (char*)buf, length, (int32_t)2000);
if (status != 0) {
if (status == 1) {
cout << "time out..." << endl;
continue;
}
cout << "error..." << endl;
break;
}
cout << "name = " << name << " : ";
if (name == "mlftext") { // text message
if (length != 0) {
message = string(buf,length);
cout << "Length of text data = " << length << " : message = " << message << endl
}
} else { // binary
if (length != 0) {
cout << "Length of binary data = " << length;
18 CHAPTER 4. 例題
cout << " : first data = " << (int32_t)buf[0] << endl;
}
}
}
sub->unsubscribe(name_text);
sub->unsubscribe(name_data);
getメソッドの呼び出しは、get(string, char*, int32_t[, int32_t])である。unsubscribeメソッドの呼び出しは、unsubscribe(const
string& name)である。
4.1.3 動かしてみよう
2つの端末を用意する。1つにはsyncSubscriber、もう1つにはpublisherを走らせる。
まずはmlftextデータを発信してそれを読み出す。syncSubscriberはpublisherが走る前に走らせた。
./syncSubscriber
status = 0
list of already subscribed channels :
status = 0
status = 0
type any char : 1
time out...
name = mlftext : Length of text data = 10 : message = test data0
name = mlftext : Length of text data = 10 : message = test data1
name = mlftext : Length of text data = 10 : message = test data2
name = mlftext : Length of text data = 10 : message = test data3
name = mlftext : Length of text data = 10 : message = test data4
name = mlftext : Length of text data = 10 : message = test data5
name = mlftext : Length of text data = 10 : message = test data6
name = mlftext : Length of text data = 10 : message = test data7
name = mlftext : Length of text data = 10 : message = test data8
./publisher
Text data will be published.
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
4.1. PUBLISHERとSYNCSUBSCRIBERの例 19
num of subscribers = 1
num of subscribers = 0
num of subscribers = 0
number of subscribersが1から0に変化しているのは、subscriberが終了したから、subscribeしているものがなく
なったため。
次はmlfdataデータを発信してそれを読み出す。syncSubscriberはpublisherが走ってから走らせた。
./syncSubscriber
status = 0
list of already subscribed channels :
status = 0
status = 0
type any char : 1
name = mlfdata : Length of binary data = 1000 : first data = 5
name = mlfdata : Length of binary data = 1000 : first data = 6
name = mlfdata : Length of binary data = 1000 : first data = 7
name = mlfdata : Length of binary data = 1000 : first data = 8
name = mlfdata : Length of binary data = 1000 : first data = 9
name = mlfdata : Length of binary data = 1000 : first data = 10
name = mlfdata : Length of binary data = 1000 : first data = 11
name = mlfdata : Length of binary data = 1000 : first data = 12
name = mlfdata : Length of binary data = 1000 : first data = 13
name = mlfdata : Length of binary data = 1000 : first data = 14
./publisher 1
Binary data will be published.
num of subscribers = 0
num of subscribers = 0
num of subscribers = 0
num of subscribers = 0
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 1
num of subscribers = 0
num of subscribers = 0
20 CHAPTER 4. 例題
最初のnum of subscribersが0なのは、まだsubscriberがsubscribeしていないからである。
Chapter 5
DAQMWコンポーネント
RedisPubSubパッケージを利用するDAQMWコンポーネントは幾つかあるが、生データをpublishするPublisherMlf
をまず紹介する。
5.1 PublisherMlf
このコンポーネントはDispatcherMlf等前段のDAQMWコンポーネントからデータを受け取り、Redisサーバに
チャネル名(キー名)を持ってそのままデータをpublishする。 publishされたデータは、すでにsubscribeしている
Subscirberに送られる。 生データの命名規則は下記の通り。Module Number単位でキー名が必要である。
AAA:edb:AAABBBBBB_CC_DDD_edb
AAAは、装置名で3文字、例えばNVA
BBBBBBはラン番号で6文字の数字、例えば012345
CCはDAQ IDで2文字の数字、例えば00
DDDはModule Numberで3文字の数字、例えば000
このコンポーネントに与えるパラメータは下記の通り。
<params>
<param pid="daqId">0</param>
<param pid="infoServerHost">localhost</param>
<param pid="infoServerPort">6379</param>
<param pid="isPublishing">yes</param>
<param pid="srcAddr">192.168.0.16</param>
<param pid="instId">NVA</param>
<param pid="eventByteSize">8</param>
</params>
infoServerHostは、Redisサーバのホスト名
infoServerPortは、Redisサーバの使用するポート番号
21
22 CHAPTER 5. DAQMWコンポーネント
Redisにエラーが起こらない限り、データはpublishされるが、エラーが起こるとpublishはその後行われなくなる。 上
流(DispatcherMlfコンポーネント)から来るデータはその後読み飛ばされる。
#define END_FLAG_という選択肢がある。 これが定義されると、データにエンドマークが実装されているとみなさ
れ、そのデータをチェックする。
エンドマークが実装されている場合、最後のデータが来るとm_end_flagがtrueとなり、 Endコマンドを受けると直ちに
以後daq_run()を抜け出し、以後daq_run()には戻らない。 Endコマンドが来ない時は、単に読み出しのタイムアウトを
繰り返す。
エンドマークが実装されていない場合あるいはエンドマークをつけたデータが失われた場合は、m_timeoutCountで
制御されている。 20を超えない間は、Endコマンドを受けていても、daq_run()を抜け出した後再びdaq_run()に戻り、
m_timeoutCountをカウントアップする。 20を超えた時、以後daq_run()を抜け出し以後daq_run()には戻らなくな
る。
さて20という値だが、DAQ-Middlewareの入力ポートのデフォルトタイムアウトは5ミリ秒であるので、Endコマンドが
到着して以降、20回タイムアウトし100(5x20)ミリ秒を超えてもタイムアウトを起こす場合は、Gathererからのデータ
はすべて処理されたものとみなす。 この値は確定的な数値ではなく、経験的なものである。 いずれにしても確実に終了
を認識するためのアルゴリズムとはならない。
5.2 T0EventPublisherMlf
このコンポーネントは、T0EventLoggerMlfがT0インデックスをファイルに書く機能を持つのに対して、Redisサーバに
T0インデックスをpublishする機能を持つ。 T0データの命名規則は下記の通り。生データのedbという文字をt0bに替
えたものである。 また、PublisherMlfコンポーネント同様に、END_FLAG_ の選択が可能である。
AAA:t0b:AAABBBBBB_CC_DDD_t0b
このコンポーネントに与えるパラメータは下記の通り。
<params>
<param pid="daqId">0</param>
<param pid="infoServerHost">localhost</param>
<param pid="infoServerPort">6379</param>
<param pid="srcAddr">192.168.0.16</param>
<param pid="isPublishing">yes</param>
<param pid="instId">NVA</param>
<param pid="eventByteSize">8</param>
</params>
infoServerHostは、Redisサーバのホスト名
infoServerPortは、Redisサーバの使用するポート番号
5.3. DAQINFOPUBLISHER 23
5.3 DaqInfoPublisher
このコンポーネントは、SubscriberがDaqOperatorからのBegin/Endのタイミングを取得するために作られた。 Sub-
scriberはデータを読むためにBeginの情報やデータ処理を終えるためにEndの情報が必要になる場合がある。 この
コンポーネントはBeginやEndの情報をRedisサーバに投げることで、Subscriberがそのタイミングを取得できるように
した。 例えば、下記のようにチャネル名(キー名)を宣言する。
<param pid="keyNameDaqCommand">NVA:DaqInfo:string:DaqCommand</param>
このキー名で送られるメッセージ内容は当面beginやendなどの簡単なものである。
5.4 GathererPsdPubコンポーネント
このコンポーネントは、GathererPsdコンポーネントにFIFOオーバーフロー情報をRedisサーバに投げるコードが追加
されたものである。 publishされるメッセージのフォーマットはjson。 Redisサーバから取り出して処理するプロセスが
Pythonなら、jsonモジュールのloadsメソッドでそれらのテキストをオブジェクトに簡単に変換できる。 GathererPsdに
は、すでにFIFO読み出しのスレッドを走らせてFIFOオーバーフローなどの情報を定期的に読み出せる仕組みがある。
FIFOオーバーフローなどの値を常時モニターするためには1分に1回とかに情報を読み出す必要がある。 Stopwatch
メソッドで、そのタイミングを知り送り出すことが出来る。
MakeJson.hは、簡単なjsonストリングを作成するためのクラスである。それを使って、下記のようなjsonストリングを作
ることができ、Redisサーバに送る。
{"time":"2017-01-20T10:11:43JST","daq_id":0,"data":[["192.168.0.16",444444,0],["192.168.0.17",
33333,0],["192.168.0.18",22222,0]]}
5.5 DAQMWのPublisherMlfとwriteDataToDiskの例
DAQMWのPublisherMlfを走らせて、生データを受けたらLoggerMlfと同じフォーマットでデータを書き込む
writeDataToDiskというSubscriberを走らせる。 このプログラムはRedisPubSubパッケージに含まれている。
利用するGathererはGathererPushで中性子モニタ用検出器(nGEM)のデータを使用する。 DAQMWの構成は下
記の通り。
DaqInfoPublisherは独立に走る。
GathererPush -> DispatcherMlf -> PublisherMlf, LoggerMlf
Subscriberを走らせる。この後はDAQMW Web GUIを用いてconfigure->beginを続ける。 そうすると、下記のよう
にSubscriber::The file is created!と、ファイルを生成し、記録を始める。
./writeDataToDisk 000024 20171206 05 000
The directory,./tmp/NVA000024_20171206/ is created!
Subscriber::init(): status = 0
Subscriber::subsucribe:key-name = NVA:DaqInfo:string:DaqCommand status = 0
24 CHAPTER 5. DAQMWコンポーネント
Subscriber::subsucribe:key-name = NVA:edb:NVA000024_05_000_edb status = 0
Subscriber::get():time out...
Subscriber::get():time out...
Subscriber::outFile:path-name = ./tmp/NVA000024_20171206/NVA000024_05_000_000.edb
Subscriber::The file is created!
Subscriber::The file is closed!
Subscriber::outFile:path-name = ./tmp/NVA000024_20171206/NVA000024_05_000_001.edb
Subscriber::The file is created!
Subscriber::outFile:path-name = ./tmp/NVA000024_20171206/NVA000024_05_000_002.edb
Subscriber::The file is created!
Subscriber::The file is closed!
Subscriber::get():time out...
Subscriber::get():time out...
Subscriber::finish command has arrived.
Subscriber::The file is closed!
下記のwatchDAQEndAndSendFinish.pyは、DAQの終了をwriteDataToDiskに知らせるpythonプログラムであ
る。
python watchDAQEndAndSendFinish.py
Watch DAQ End and send finish command to subscriber.
Arrived command = begin
Arrived command = end
end command has arrived! Now check the state of DAQMW component.
The state of PubisherMlf0 is now CONFIGURED.
finish command has been sent to Subscriber.
Arrived command = finish
DAQを終了させるために、endコマンドをDAQMWWebGUIを介して発行する。データ収集は終了する。DaqInfoPublisher
はその際、endコマンドをpublishするので、 watchDAQEndAndSendFinish.pyは、それを受けたら、PublisherMlf
のstateを読み行き、CONFIGUREDになっていたら、writeDataToDiskにfinishコマンドをpublishする。 このような
仕組みを作ることで、writeDataToDiskは確実にPublisherMlfが終了したことを受け自分を終了させることができ
る。
Chapter 6
Doxygenで内部仕様を見る
Doxygenはソースコードにコメントを上手に組み込むことで、プログラムの内部仕様をWeb表示させることができる。
下記のURLから見ることができる。
http://nova-info.intra.j-parc.jp/mlfsoft/redispubsub/doxygen/index.html
Figure 6.1: Doxygen main page
25
26 CHAPTER 6. DOXYGENで内部仕様を見る
Chapter 7
RedisPubSubの性能評価
RedisPubSubの性能を評価するため、通信のオーバーヘッドを見積もり、データサイズに対する転送スピードの相関
を評価した。 そして、RedisPubSubの利用に役立てるための情報を提供する。
7.1 性能評価のための計算機構成
下記のように構成した。
PublisherMlf on
For nGEM, DELL PowerEdge R620 Intel Xeon E5-2650 0 2.00GHz
For PSD, VT64 Server9500 X-1S Intel Xeon X5690 3.47GHz
Redis Server, Publisher and Subscriber on
DELL PowerEdge R710 Intel Xeon X5560 2.80GHz
Ethernet:Gigabitを利用した。
7.2 Redisサーバの設定パラメータ
デフォルトで決まっている値はここでは触れず、変更したパラメータのみ示す。/etc/redis.confの変更は下記の通り。
bind 0.0.0.0
#save 900 1
#save 300 10
#save 60 10000
client-output-buffer-limit pubsub 100gb 100gb 60
7.3 性能評価に使用したPublisher/Subscriberプログラムとその評価結果
使用したプログラムは大変シンプルなもので下記の通り。
27
28 CHAPTER 7. REDISPUBSUBの性能評価
Publisherは、単に指定された長さのデータを送るだけのプログラム(C++)
Subscriberは、単にデータを読み込んでスピードを計算させるだけのプログラム(C++)
比較のために、クライアント・サーバモデルでの簡単なTCPソケット通信プログラムも作成した。 そのプログラムは、
Publisher/Subscriber同様に単に指定された長さのデータを可能な限り速やかに送るだけのプログラム(C++)と単
にデータを読み込んでスピードを計算させるだけのプログラム(C++)である。
下記は、その結果を数値で表したものである。
Figure 7.1: 転送スピードの比較
結果から、転送バイト数が短い場合はRedisのオーバーヘッドのために転送レートは一定であり、約10kHz程度の性
能となっている。 しかし、転送バイト数が増加するに連れて徐々に転送スピードを上げ、ネットワーク最大性能に近づい
ている。
上記のテーブルのデータをグラフ化したものを下記に示す。 縦軸が転送スピード(Mbits/sec)で、横軸は転送バイト数
で、共にログスケールで表示されている。
Figure 7.2: 転送スピードの比較図
見てわかるように、TCPソケット通信のオーバーヘッドはRedisPubSubのオーバーヘッドと比べて大変小さい。 Redis
の場合、様々な機能を実現させているためオーバーヘッドが大きいものの、転送バイト数が大きくなるとネットワーク最
大性能に近づいてるのが見て取れる。
以上の結果から、Redisは小さなデータ長の場合約10kHzの転送性能を持っているものの、できるだけ大きな長さで
データを転送することでネットワーク最大限のスピードに近づくことが示された。
7.4. DAQMWコンポーネント、PUBLISHERMLFを利用したケーススタディー1 29
7.4 DAQMWコンポーネント、PublisherMlfを利用したケーススタディー1
Redisサーバが受け取るデータ長の分布を表示するSubscriberを使って、DAQMWコンポーネント間を流れるデータ
長を調べた。
BL21での結果は下記の図の通り。PSDとnGEMの場合について調べた。 縦軸はイベントの数で、横軸はデータ長で
ある。 PSD(読み出しモジュールはNeuNET)の場合、比較的小さなサイズのデータが多い。nGEMは一定長のデータ
であった。
Figure 7.3: NeuNETのデータ長
Figure 7.4: nGEMのデータ長
30 CHAPTER 7. REDISPUBSUBの性能評価
7.5 DAQMWコンポーネント、PublisherMlfを利用したケーススタディー2
単に読み込んでスピードを計算させるだけのSubscriber(BL21のPSDとnGEM読み込み)で実際にどれだけのス
ピードでデータが流れているかを知る。
nGEM(nGEM:1台)
speed = 13.241 Mbits/sec.(1.6MB/sec)
データ長:1.5kB ->データレート:1kHz@300kW
PSD(NeuNET:22枚)
speed = 18.811 Mbits/sec.(2.3MB/sec)
データ長:~680B? ->データレート: 3.3kHz? @300kW
7.6 Redisサーバのバッファメモリ使用量
Redisサーバでは、Publisherから送られてくるデータがメモリに格納され、全てのSubscriberが読み終えるとメモリか
ら消える。 ケーススタディー2のような簡単なSubscriberであれば、Redisサーバ上のデータはすぐに処理されるため
メモリ使用量は小さい。 Subscriberがsubscribeしてから読み続けるとき、仮に読み込みが遅れるとデータはRedis
サーバ上に保持されるため増加する。 一例が下記の図である。Subscriberの処理が間に合わずRedisサーバ上に
データが増え続けている様子をZabbixと呼ばれる監視ツールで監視した結果である。 当然の事ながら、これを放置し
続けると、計算機のメモリは使い尽くされ、データは失われることになる。
Figure 7.5: Redisサーバのバッファメモリ使用量
従って、Subscriberの性能に応じRedisサーバのメモリ使用量は大きく依存するので、Zabbix等のツールで監視し、
Redisサーバの構成を決める。 必要ならRedisサーバを増やして負荷分散させる。
Chapter 8
参考文献
RedisPubSubパッケージに関連する参考文献の一覧は下記の通り。
1. Redisホームページ:https://redis.io/
2. hiredisのホームページ:https://github.com/redis/hiredis
3. redis-pyのホームページ:https://github.com/andymccurdy/redis-py
4. doxygenのホームページ:http://www.doxygen.org/ , http://www.doxygen.jp/
5. J-PARC/MLFのためのdaqmwmlfパッケージとユーザガイド
• package : https://mlfdevsv.j-parc.jp/MlfSoftware/rep/daqmwmlf
• PDF : http://nova-info.intra.j-parc.jp/mlfsoft/daqmwmlf/usersguide/DAQMWforMlfUsersGuideA4.pdf
• Web : http://nova-info.intra.j-parc.jp/mlfsoft/daqmwmlf/usersguide/index.html
6. RedisPubSubユーザガイド(外部仕様)
• package : https://mlfdevsv.j-parc.jp/MlfSoftware/rep/redispubsub
• PDF:http://nova-info.intra.j-parc.jp/mlfsoft/redispubsub/usersguide/RedisPubSubUsersGuideA4.pdf
• Web:http://nova-info.intra.j-parc.jp/mlfsoft/redispubsub/usersguide/index.html
7. RedisPubSubのdoxygen(内部仕様)
• Web:http://nova-info.intra.j-parc.jp/mlfsoft/redispubsub/doxygen/index.html
31