2019年3月3日日曜日

3台構成のApache Cassandraを試してみる(CentOS7)

とても久しぶりにCassandraを試したのでメモします。以前試したのは、HadoopやHBaseの記事を書いた10年近い昔のことでした。(残念ながら当時Cassadraの記事は書いてませんが。)

概要

・3台のLinux環境(CentOS7)で、Apache Cassandraを試してみます。
・クライアントソフトから、SQL風にデータを置いてみたり
・node.jsのプログラムからデータを使ってみたりします。

(参考)
Apache Cassandraのサイト: http://cassandra.apache.org/doc/latest/
NTTPC社のブログ: https://www.nttpc.co.jp/technology/cassandra.html

必要なもの

・CentOS7のLinux環境4台 (最小インストールの標準的な設定で試しています)
・インターネット接続
・Apache Cassandraが配布するCassandraパッケージ(リポジトリ)
・(Javaも必要ですが、自動で(=依存関係で)OpenJDKが入ります)

構成

3台のCassandraクラスターです。ここでは、
・10.0.0.1
・10.0.0.2
・10.0.0.3
とします。IPアドレスのみで構成しホスト名は利用しません。
3台に違いはありませんが、seedを10.0.0.0.1としました。

1. インストール: 3台共通


作業は、
・リポジトリ追加
・yumでパッケージインストール
・設定ファイルを一つ(3つの値)変更
です。

Linux環境にて、rootで作業します。

1-0. 準備


1-0-1. Firewall設定

・Firewallで、tcpの7000,7001,7199,9042の利用を許可する設定


firewall-cmd --zone=public --add-port=7000/tcp --add-port=7001/tcp --add-port=7199/tcp  --add-port=9042/tcp --permanent

firewall-cmd --reload

・確認
firewall-cmd --list-all --permanent

以下が追加されていることを確認。
  ports: 7000/tcp 7001/tcp 7199/tcp 9042/tcp

1-0-2. SELinux設定

軟弱なことにOFFにします。
(参考: http://cassandra.apache.org/doc/4.0/faq/#selinux )

ファイル/etc/selinux/config を以下の一行を編集
SELINUX=disabled
(または、SELINUX=permissive)

とりあえずの作業のために、
setenforce 0

1-1. Apache Cassandraのリポジトリ追加

cat << EOF > /etc/yum.repos.d/cassandra.repo
[cassandra]
name=Apache Cassandra
baseurl=https://www.apache.org/dist/cassandra/redhat/311x/
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://www.apache.org/dist/cassandra/KEYS
EOF


1-2. インストール

yum install cassandra

1-3. 設定ファイル変更


1ファイルで3箇所(or 4箇所)を変更するだけ。
vi /etc/cassandra/conf/cassandra.yaml

1-3-1. seeds設定

「- seeds: "127.0.0.1" 」を
10.0.0.0.1では「- seeds: "10.0.0.0.1" 」
10.0.0.0.2では「- seeds: "10.0.0.0.1,10.0.0.0.2" 」
10.0.0.0.3では「- seeds: "10.0.0.0.1,10.0.0.0.3" 」


1-3-2. listen_address設定

「listen_address: localhost」を、
10.0.0.0.1では「listen_address: "10.0.0.0.1"」
10.0.0.0.2では「listen_address: "10.0.0.0.2"」
10.0.0.0.3では「listen_address: "10.0.0.0.3"」


1-3-3. rpc_address設定

「rpc_address: localhost」を、
10.0.0.0.1では「rpc_address: "10.0.0.0.1"」
10.0.0.0.2では「rpc_address: "10.0.0.0.2"」
10.0.0.0.3では「rpc_address: "10.0.0.0.3"」


1-3-4. cluster_nameの設定 (おまけ: 変更しなくても大丈夫ですが、お好みで。)

すべてのサーバで同じ「cluster_name: 'Railway Data Cluster'」

1-4. cassandraサービスの起動と自動起動設定


service cassandra start
chkconfig cassandra on


1-5. 確認


[root@host1 ~]# nodetool status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address          Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.0.0.1  335.18 KiB  256          69.1%             04128023-9ef2-411b-92f8-528278cf54bb  rack1
UN  10.0.0.2  294.92 KiB  256          64.6%             de517d21-a465-4737-9f35-4ac44884a835  rack1
UN  10.0.0.3  229.48 KiB  256          66.3%             ce3013a9-4eab-4c42-812b-e64afcfe05de  rack1


期待するIPアドレスの3行の表示が出て、3行の頭が「UN」であること。

2. cqlshクライアントでSQL風の命令でデータ操作してみる。


3台のうちのいずれかの上のcqlshクライアントアプリから、3台のいずれかへ接続

cqlsh 10.0.0.1
(10.0.0.2でも10.0.0.3でもOK)

操作例
[root@host1 ~]# cqlsh 192.168.100.101
Connected to Railway Data Cluster at 192.168.100.101:9042.
[cqlsh 5.0.1 | Cassandra 3.11.4 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
cqlsh>


・railwayというkeyspaceを作成
CREATE KEYSPACE IF NOT EXISTS railway WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 2 };

・railwayというkeyspaceを指定
USE railway;

・yamanotelineというtableを作成
CREATE TABLE yamanoteline ( stationname text PRIMARY KEY, location text, jreast int, subway int, other int);

・yamanotelineというtableにデータを3行入れてみる
INSERT INTO yamanoteline ( stationname, location, jreast, subway, other ) VALUES ('Shinagawa', 'Minato', 3, 0, 2);
INSERT INTO yamanoteline ( stationname, location, jreast, subway, other ) VALUES ('Osaki', 'Shinagawa', 2, 0, 1);
INSERT INTO yamanoteline ( stationname, location, jreast, subway, other ) VALUES ('Gotanda', 'Shinagawa', 0, 1, 1);


・yamanotelineというtableのデータを閲覧する
SELECT * FROM yamanoteline;

操作例
cqlsh> use railway;
cqlsh:railway> select * from yamanoteline;

 stationname | jreast | location  | other | subway
-------------+--------+-----------+-------+--------
     Gotanda |      0 | Shinagawa |     1 |      1
       Osaki |      2 | Shinagawa |     1 |      0
   Shinagawa |      3 |    Minato |     2 |      0

(3 rows)
cqlsh:railway>


3. node.jsからcassandraクラスタに格納したデータを利用してみる。


クラスタへ接続できる適当なLinuxサーバにて(もちろん3台のうちのどれかでもOK)

3-1. node.jsをインストール


yum install -y epel-release
yum install -y nodejs npm


3-2. node.js用のcassandra接続ライブラリをインストール


npm install cassandra-driver

3-3. node.jsでcassandraを操作する簡単なプログラムを作って動かしてみる。


プログラム例: nodecas.js

const cassandra = require('cassandra-driver');
const client = new cassandra.Client({ contactPoints: ['192.168.100.101', '192.168.100.102'], keyspace: 'railway', localDataCenter: 'datacenter1' });

client.execute('SELECT stationname, jreast, subway FROM yamanoteline WHERE stationname = ?', [ 'Shinagawa' ])
  .then(result => console.log('station=%s, jreast=%s, subway=%s', result.rows[0].stationname, result.rows[0].jreast, result.rows[0].subway))
  .then(() => client.shutdown());


実行例
[root@host0 ~]# node nodecas.js
station=Shinagawa, jreast=3, subway=0
[root@host0 ~]# 



以上

2015年3月24日火曜日

画面サイズ別の軽いノートパソコン一覧 (2015-3)

画面サイズ別の、なるべく軽いノートパソコン(PC)の一覧のつもりです。
参考として、すごく安いものや、Apple社のものも混ぜています。
参考価格は、2015年3月に価格サイトの情報をもとに、ざっくり。

画面サイズ製品重さ[kg]参考価格[万円]
11.6VAIO Pro 110.7711.3
11.6ASUS EeeBook X205TA0.983.1
11.6Apple MacBook Air 1300/11.61.089.2
12Apple MacBook 1100/120.9215
12.5Panasonic Let’s note MX31.0120
12.5Panasonic Let’s note MX31.0717
13.3NEC LaVie Direct HA0.7815.5
13.3NEC LaVie Z LZ5500.8010.5
13.3VAIO Pro 130.9611
13.3Apple MacBook Air 1600/13.31.3511
13.3Apple MacBook Pro Retina2400/13.31.5711.9
14Panasonic Let’s note LX41.1526.5
14Lenovo ThinkPad X1 Carbon1.3418.6
15.4Apple MacBok Pro Retina 2000/15.42.0218
15.6Panasonic Let’s note B111.7410.8
15.6IIYAMA 15P12202.15.8
17.3IIYAMA 17P11002.55.4

2014年11月18日火曜日

JavaのゆるいFast CGI Client

JavaのFast CGI Clientを探してたのですが、意外といいのが見つからなかったのでゆるく実装。(fastcgi.comはJavaはServer (=Application) 側だけかも、fcgi4jはエラーもIssuesは放置みたい、jfastcgiはJava(servlet)べったりの実装でコア機能だけを使いづらく。)

なぜいまさらFast CGI?というのは、レガシー接続に便利かなというのと、まあ軽そうだし。SCGIなんかに比べると、無駄にややこしいけど、まあ古いものなので。

なるべくFastCGI Specificationに沿ったお勉強的な。本気で使っちゃいけません。

利用イメージ


int requestID = 123;

FastCGIConnection fcgic = new FastCGIConnection("localhost", 9000);

Map params = new HashMap() {{
    put("QUERY_STRING", "qstr1=hello&qstr2=world");
    put("REQUEST_METHOD", "POST");
    put("SCRIPT_FILENAME", "/usr/share/nginx/html/test.php");
  }};
fcgic.sendParams(requestID, params);

fcgic.sendStdin(requestID, "post1=hello&post2=world".getBytes());

ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream();
ByteArrayOutputStream stderrStream = new ByteArrayOutputStream();
fcgic.recvStdoutStderrAndWaitEndRequest(stdoutStream, stderrStream);
System.out.println("STDOUT: [" + stdoutStream.toString("UTF-8") + "]");
System.out.println("STDERR: [" + stderrStream.toString("UTF-8") + "]");

requestIDは、一連のやりとりの識別子。multiplex (一つのコネクションで複数のやりとりをする) のときに使われる。

FastCGIConnectionの中身

なかなか古風な実装で、遅そうですが。(以下は、Bloggerさんのおかげで、ソースは崩れててコピペでは使えません。)
import java.net.*;
import java.io.*;
import java.util.*;

/**
 * simple fastCGI client
 *  - RESPONDER only
 *  - no multiplex
 *  - sync
 *  - no charset consideration
 * 
 * FastCGI Specification
 * http://www.fastcgi.com/devkit/doc/fcgi-spec.html
 *
 * ver 0.5
 */

public class FastCGIConnection{

  //
  // 8. Types and Constants
  //

  final static int FCGI_VERSION_1 = 1;

  final static int FCGI_BEGIN_REQUEST = 1;
  final static int FCGI_ABORT_REQUEST = 2;
  final static int FCGI_END_REQUEST = 3;
  final static int FCGI_PARAMS = 4;
  final static int FCGI_STDIN = 5;
  final static int FCGI_STDOUT = 6;
  final static int FCGI_STDERR = 7;
  final static int FCGI_DATA = 8;
  final static int FCGI_GET_VALUES = 9;
  final static int FCGI_GET_VALUES_RESULT = 10;
  final static int FCGI_UNKNOWN_TYPE = 11;
  final static int FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE;

  final static int FCGI_KEEP_CONN = 1;

  final static int FCGI_RESPONDER = 1;
  final static int FCGI_AUTHORIZER = 2;
  final static int FCGI_FILTER = 3;

  public static void main(String[] args){
    System.out.println("FastCGI client sample");

    try{
      FastCGIConnection fcgic = new FastCGIConnection("localhost", 9000);

      final String method = "POST";
      final String scriptFileName = "/usr/share/nginx/html/test.php";
      final String queryString = "get1=hello&get2=world";
      final String requestBodyString = "post1=hello&post2=world";
      final byte[] requestBody = requestBodyString.getBytes();
      final String requestBodyLength = String.valueOf(requestBody.length);

      int requestID = 1;

      // B. Typical Protocol Message Flow

      // {FCGI_BEGIN_REQUEST,   1, {FCGI_RESPONDER, 0}}
      fcgic.sendBeginRequest(requestID, false);

      // {FCGI_PARAMS,          1, "..."}
      Map params = new HashMap() {{
          put("QUERY_STRING", queryString);
          put("REQUEST_METHOD", method);
          put("SCRIPT_FILENAME", scriptFileName);
          put("CONTENT_TYPE", "application/x-www-form-urlencoded");
          put("CONTENT_LENGTH", requestBodyLength);
        }};

      fcgic.sendParams(requestID, params);

      if(params.size() > 0){
        // {FCGI_PARAMS,          1, ""}
        params = new HashMap();
        fcgic.sendParams(requestID, params);
      }

      // {FCGI_STDIN,           1, "..."}
      fcgic.sendStdin(requestID, requestBody);

      // {FCGI_STDIN,           1, ""}
      fcgic.sendStdin(requestID, "".getBytes());

      // {FCGI_STDOUT,      1, "Content-type: text/html\r\n\r\n\n ... "}
      // {FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}

      ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream();
      ByteArrayOutputStream stderrStream = new ByteArrayOutputStream();

      fcgic.recvStdoutStderrAndWaitEndRequest(stdoutStream, stderrStream);

      System.out.println("STDOUT: [" + stdoutStream.toString("UTF-8") + "]");
      System.out.println("STDERR: [" + stderrStream.toString("UTF-8") + "]");

    }catch(Exception e){
       System.err.println("error: " + e.toString());
    }
  }

  Socket socket;
  BufferedOutputStream ostream;
  InputStream istream;

  public FastCGIConnection(String host, int port) throws IOException{
    socket = new Socket(host, port);
    ostream = new BufferedOutputStream(socket.getOutputStream());
    istream = socket.getInputStream();
  }

  //
  // 3. Protocol Basics
  //

  //
  // 3.3 Records (All data is carried in "records")
  //
  int sendRecordHeader(int requestID, int recordType, int contentLength)
      throws IOException{

    int intPaddingLength = contentLength % 8;
    if(intPaddingLength != 0) intPaddingLength = (8 - intPaddingLength);

    System.out.println(" Record Header: " + recordType + ", pad=" + intPaddingLength);

    ostream.write(FCGI_VERSION_1);
    ostream.write(recordType);
    ostream.write(requestID >> 8);
    ostream.write(requestID);
    ostream.write(contentLength >> 8);
    ostream.write(contentLength);
    ostream.write(intPaddingLength); // paddingLength
    ostream.write(0); // reserved

    return intPaddingLength;
  }

  //
  // 3.4 Name-Value Pairs
  //
  int calcParamLength(String name, String value){
    if(name == null || value == null) return 0;

    int nameLength = name.length();
    int valueLength = value.length();

    if(nameLength < 0x80){
      if(valueLength < 0x80){
        // FCGI_NameValuePair11
        return nameLength + valueLength + 2;
      }else{
        // FCGI_NameValuePair14
        return nameLength + valueLength + 5;
      }
    }else{
      if(valueLength < 0x80){
        // FCGI_NameValuePair41
        return nameLength + valueLength + 5;
      }else{
        // FCGI_NameValuePair44
        return nameLength + valueLength + 8;
      }
    }
  }
  void sendParam(int requestID, String name, String value) throws IOException{
    if(name == null || value == null) return;

    int nameLength = name.length();
    int valueLength = value.length();

    if(nameLength < 0x80){
      if(valueLength < 0x80){
        // FCGI_NameValuePair11
        ostream.write(nameLength);
        ostream.write(valueLength);
      }else{
        // FCGI_NameValuePair14
        ostream.write(nameLength);
        ostream.write(0x80 | valueLength >> 24);
        ostream.write(valueLength >> 16);
        ostream.write(valueLength >> 8);
        ostream.write(valueLength);
      }
    }else{
      if(valueLength < 0x80){
        // FCGI_NameValuePair41
        ostream.write(0x80 | nameLength >> 24);
        ostream.write(nameLength >> 16);
        ostream.write(nameLength >> 8);
        ostream.write(nameLength);
        ostream.write(valueLength);
      }else{
        // FCGI_NameValuePair44
        ostream.write(0x80 | nameLength >> 24);
        ostream.write(nameLength >> 16);
        ostream.write(nameLength >> 8);
        ostream.write(nameLength);
        ostream.write(0x80 | valueLength >> 24);
        ostream.write(valueLength >> 16);
        ostream.write(valueLength >> 8);
        ostream.write(valueLength);
      }
    }
    ostream.write(name.getBytes());
    ostream.write(value.getBytes());
  }

  //
  // 5. Application Record Types
  //

  //
  // 5.1 FCGI_BEGIN_REQUEST
  //
  public void sendBeginRequest(int requestID, boolean keepalive) throws IOException{

    sendRecordHeader(requestID, FCGI_BEGIN_REQUEST, 8);

    final int role = FCGI_RESPONDER;
    ostream.write(role >> 8);
    ostream.write(role);
    ostream.write(keepalive ? FCGI_KEEP_CONN : 0);

    for(int i = 0; i < 5; i++) ostream.write(0); // padding = 5
  }

  //
  // 5.2 Name-Value Pair Stream: FCGI_PARAMS
  //
  public void sendParams(int requestID, Map params) throws IOException{
    int intParamsLength = 0;

    for(Map.Entry entry : params.entrySet()){
      intParamsLength += calcParamLength(entry.getKey(), entry.getValue());
    }

    System.out.println("FCGI_PARAMS: length=" + intParamsLength);

    int pad = sendRecordHeader(requestID, FCGI_PARAMS, intParamsLength);

    for(Map.Entry entry : params.entrySet()){
      sendParam(requestID, entry.getKey(), entry.getValue());
    }

    for(int i = 0; i < pad; i++) ostream.write(0); // padding
    ostream.flush();
  }

  //
  // 5.3 Byte Streams: FCGI_STDIN
  //
  public void sendStdin(int requestID, byte[] body) throws IOException{
    System.out.println("FCGI_STDIN: length=" + body.length);

    int pad = sendRecordHeader(requestID, FCGI_STDIN, body.length);

    ostream.write(body, 0, body.length);

    for(int i = 0; i < pad; i++) ostream.write(0); // padding
    ostream.flush();
  }

  //
  // 5.3 Byte Streams: FCGI_STDOUT, FCGI_STDERR, 5.5 FCGI_END_REQUEST
  //
  public int[] recvStdoutStderrAndWaitEndRequest(OutputStream stdoutStream, OutputStream stderrStream)
      throws IOException{

    int[] appStatAndProtStat= new int[2];

    int version, a;
    long b;
    int recordType = -1;
    int requestID = -1;
    int contentLength = 0;
    int paddingLength = 0;
    while(true){
      if(recordType < 0){
        version = istream.read();
        if(version < 0) break;
        if(version != FCGI_VERSION_1){
          System.err.println("recv record version error: " + version);
          break;
        }
        recordType = istream.read();
        requestID = (istream.read() << 8) + istream.read();
        contentLength = (istream.read() << 8) + istream.read();
        paddingLength = istream.read();
        istream.read(); // reserved
      }

      switch (recordType) {
      case FCGI_STDOUT:
        System.out.println("FCGI_STDOUT: requestID=" + requestID +
            ", contentLength=" + contentLength + ", paddingLength=" + paddingLength);
        byte[] bufstdout = new byte[contentLength];
        a = istream.read(bufstdout, 0, contentLength);
        stdoutStream.write(bufstdout, 0, a);
        contentLength -= a;
        if(contentLength == 0){
          b = istream.skip(paddingLength);
          paddingLength -= b;
          if(paddingLength == 0){
            recordType = -1;
            System.out.println("FCGI_STDOUT: done");
          }
        }
        break;
      case FCGI_STDERR:
        System.out.println("FCGI_STDERR: requestID=" + requestID +
            ", contentLength=" + contentLength + ", paddingLength=" + paddingLength);
        byte[] bufstderr = new byte[contentLength];
        a = istream.read(bufstderr, 0, contentLength);
        stderrStream.write(bufstderr, 0, a);
        contentLength -= a;
        if(contentLength == 0){
          b = istream.skip(paddingLength);
          paddingLength -= b;
          if(paddingLength == 0){
            recordType = -1;
            System.out.println("FCGI_STDERR: done");
          }
        }
        break;
      case FCGI_END_REQUEST:
        appStatAndProtStat[0] = (istream.read() << 24) + (istream.read() << 16) +
            (istream.read() << 8) + istream.read(); // appStatus
        appStatAndProtStat[1] = istream.read();  // protocolStatus
        istream.skip(3);
        recordType = -1;
        System.out.println("FCGI_END_REQUEST: requestID=" + requestID +
            ", appStatus=" + appStatAndProtStat[0] + ", protocolStatus=" + appStatAndProtStat[1]);
        break;
      default:
        System.err.println("recv record type error: " + recordType);
        break;
      }
    }
    return appStatAndProtStat;
  }
}


ubuntu 14.04LTSで試す


ubuntu 14.04LTSでのお試しは、FastCGI Server (Application) にPHP-FPM、それが動いているかの確認のためのFast CGI Client (Web Server) にnginxで。

1. PHP-fpmとnginxをインストール

sudo apt-get install nginx php5-fpm

2. 気にするファイルは

2-1. PHP-FPMの設定から、/etc/php5/fpm/pool.d/www.conf
listen = /var/run/php5-fpm.sock 
↓
listen = 127.0.0.1:9000
こんな感じ で、TCP Socketに変更。JavaはUnix Socket不得意なので。

2-2. nginxの設定から、/etc/nginx/sites-available/defaultの、PHPっぽいところを、
        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass 127.0.0.1:9000;
        }

2-3. PHPのプログラムを、/usr/share/nginx/html/test.phpなどに、「<?php echo 'hello fastCGI PHP'; ?>」みたいな感じで。(なぜか半角で書くとblogger上で消えちゃうので全角で書いておきます。試す人は、もちろん半角で。)

3. ブラウザ経由、nginx経由で、確認
PHP-fpmとnginxを起動して、
/etc/init.d/php5-fpm start
/etc/init.d/nginx start
ブラウザで、http://サーバー/test.phpを見ると、「hello fastCGI PHP」 と表示されます。

4. ゆるいJavaのFastCGIクライアントプログラムで試す。 ここで、Javaはopenjdk-7を利用しました。(もちろんこっちはnginxは止めてもOK)
javac FastCGIConnection.java
java FastCGIConnection

期待される結果は、
FastCGI client sample
FCGI_STDOUT: requestID=123, contentLength=80, paddingLength=0
FCGI_STDERR: requestID=123,appStatus=0,protocolStatus=0
STDOUT: [X-Powered-By: PHP/5.5.9-1ubuntu4.5
Content-type: text/html

hello fastCGI PHP]
STDERR: []

mainがsampleなので、ハードコーディングされているのを変えて試したりして遊べるかも。

--
以上

2014年3月9日日曜日

オーディオDACとDACチップのメモ (2014-3)

オーディオ用のDAC製品 (とデジタルプレーヤー) と、それに使われているDACチップの一覧です。昨年頃調べたのと似た話ですが、今年はDSD対応のもののみです。SACD PlayerはUSB接続でDSD対応のもののみです。チップだけですべてが決まるわけではないのでしょうが、どんなものか気になるので。あと、同じチップでも、製品によって複数搭載していたりしますが、仕様にきっちりかかれていることは少ないです。

昨年は見なかったPCM1975を使った製品がでてきました。PCM1975は32bit対応ですが、PCM1792A(24bitまで)のほうが高音質で高価(約2.4倍)みたいです。32bit音源がほとんど無いことを考えると...。(高音質とは、TI社ページで、PCM1792AとDSD1792Aには「132dB SNR Highest Performance Stereo DAC」の記載が。また、PCM1792Aは、PCM1795に対し「higher SNR」とも。)

オーディオ用DAC製品
種類 製品 価格 (円) DACチップ 入力(最高スペック)メモ
DAC TEAC UD-501約11万PCM1795 DSD 5.6MHz, PCM 384kHz/32bitPCM1795を2個 (PhileWeb記事より)。
TEAC UD-301 約6万PCM1795 DSD 5.6MHz, PCM 192kHz/32bitPCM1795を2個 (Webの製品情報に記載)。
LUXMAN DA-06 約30万PCM1792A DSD 5.6MHz, PCM 384kHz/32bit(?)PCM1792Aを2個 (Webの製品情報に記載)。
PCM1792A(24bit)で、DA-06が32bit対応とは?
DENON DA-300USB約6万 PCM1795 DSD 5.6MHz, PCM 384kHz/32bitPCM1795を1個 (PhileWeb記事より)。
KORG DS-DAC-100約5万CS4398 DSD 5.6MHz, PCM 192kHz/24bit
KORG DS-DAC-10約5万CS4398 DSD 5.6MHz, PCM 192kHz/24bit
Ratoc RAL-DSDHA1約7万 WM8742 DSD 2.8MHz, PCM 192kHz/24bit
Ratoc RAL-DSDHA2約12万 WM8741 DSD 2.8MHz, PCM 192kHz/24bit
Fostex HP-A4
約4万 PCM1792A DSD 5.6MHz, PCM 192kHz/24bit
Network Player Marantz NA-11S1約33万DSD1792A DSD 2.8MHz(?), PCM 192kHz/24bitDSD1792Aの搭載はWebの製品情報の特徴・機能に記載。
また、Impressの記事によるとDSD5.6MHzも大丈夫らしい。
SACD Player (USB-DAC)Marantz NA-14S1約24万DSD1792A DSD 5.6MHz, PCM 192kHz/24bitDSD1792Aの搭載はWebの製品情報の特徴・機能に記載。

ProJectlindemannOnkyoPioneerYamahaは、DSD対応が見つからず。LUXMANとRatocとEsotericは、昨年と変わらず。Esoteric D-05 (旭化成エレクトロニクス社製AK4397を左右独立で搭載) は、本家のWebの製品情報で「PCM/DSD入力対応」とだけあって、結局どういう信号を入力できるかが見当たらなかったので省略。Zodiacは、Platinum DSD DACというのがDSD対応らしいのですが、これも詳細が不明なので省略。

DACチップ
DACチップ 機能メモ価格メモ
TI社 PCM1795 (※1) 名前はPCMですが、PCMとDSDも受け付ける。PCMは32bitまで。Analog性能は1792Aより低いようです。TI社の参考価格は$2.9 | 1ku
TI社 PCM1792A (※1) 名前はPCMですが、PCMとDSDも受け付ける。PCMは24bitまで。Analog性能は1795より高いようです。TI社の参考価格は$7.08 | 1ku
TI社 DSD1792A (※1) 名前はDSDですが、PCMとDSDも受け付ける。PCMは24bitまで。PCM1792Aのバリエーションなんだそうです。TI社の参考価格は$11.18 | 1ku
CIRRUS LOGIC社 CS4398PCMもDSDも受け付ける。
Wolfson社 WM8741 PCMもDSDも受け付ける。8742より8741のほうがいいものらしい。
Wolfson社 WM8742 PCMもDSDも受け付ける。8741より少し劣るもので少し安いようです。でも、こっちを使う製品が多いみたいです。

※1 バーブラウン (Burr-Brown) 社製と書かれていることが多い
※2 TI社 = Texas Instruments社

2014年1月24日金曜日

3台構成のAkka Clusterを簡単に試してみる。

3台くらいのLinuxサーバで、Akka Clusterをちらっと試してみるメモです。

akka (http://akka.io/) は、分散システムを作るためのツールキット(ライブラリと小さいプログラム)です。分散は、ロバストとかスケールとかのために。かけ声は、Let it crash (http://letitcrash.com/)。(1台のサーバでちょっとためす例は、こっちを。)

akka 2.2.3 + Java 7(OpenJDK) + SBT(scala 2.10)を使います。
OSは、ここでは、CentOS6かUbuntu12.04ですが、Javaさえ動けば、わりとなんでもよいはずです。

3台(4台)のクラスタを動かします。サンプルのプログラムは、モンテカルロ法での円周率の計算です。重めになるかなと思ってBigIntegerにしてみましたが、πの精度は目指していません。計算をスタートさせるノード(10.0.0.4)から、クラスタに適当にたくさんの計算ジョブ(ノード数より多いジョブ)をばらまきます。ばらまきはrouterに任せます。各ノードは、計算が出来上がったら、最初のノードに結果を返します。最初のノードは、返された結果を集計して円周率を表示します。返される結果が増えると、次第に円周率っぽくなっていく、という感じ。(絵は4台ですが、10.0.0.3は省略してもかまいません。)


0. 準備

0-1. Linuxサーバーを3つ以上用意します。

ほぼ最小インストールのLinuxを準備します。必要なのは、ネットワークと作業用のssh serverくらいです。
(ファイアウォールはtcpのポート2551を許可、SELinuxはOFF)

クラスタの構成は、3台または4台で、サーバーIP:利用ポートは、以下です。
・10.0.0.1:2551を、seedノードとして。
・10.0.0.2:2551を、seedノードとして。
・10.0.0.3:2551を、普通のノードとして。
・10.0.0.4:2551を、計算スタートをさせるノードとして。4台が面倒なら、10.0.0.3と10.0.0.4を兼用で、合計3台でも。

あとは、普通のノードは、お好みで、10.0.0.5, ...と、いくつでも。

seedは、AkkaのClusterの中の特別なノードですが、プログラムからみるとseedかどうかはあまり関係ないはずです。とりあえずは、きっかけのために2個ぐらいあればいい、程度かと。

0-2. すべてのサーバーにJava 7 (OpenJDK) を入れます。

CentOS6では、
yum -y install java-1.7.0-openjdk-devel

Ubuntu12.04では、
apt-get -y install openjdk-7-jdk

ここでは全サーバにJDKを入れていますが、開発機以外はJREだけでいいのかも。

0-3. プログラム開発用サーバー1台だけに、SBT(scala)を入れます。

SBTのパッッケージをダウンロードします。
パッケージは、ここ(http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html)の、 RPM package(CentOS)やDEB package(Ubuntu)のリンクから。
(RPM: http://repo.scala-sbt.org/scalasbt/sbt-native-packages/org/scala-sbt/sbt/0.13.1/sbt.rpm)
(DEB: http://repo.scala-sbt.org/scalasbt/sbt-native-packages/org/scala-sbt/sbt/0.13.1/sbt.deb)

CentOS6では、sbt.rpmをインストール
rpm -ivh sbt.rpm
Ubuntu12.04では、sbt.debをインストール
dpkg -i sbt.deb

そして、CentOSでもUbuntuでも、確認と初期設定をかねて、
sbt version
とし、実行結果は例えば、
...(省略: 一度目だけ時間がかかります)...
[info] 0.1-SNAPSHOT 

1. 開発機上で、プログラムを作成して、配布用にjarパッケージ化

1-1. プログラムやSBTプロジェクト設定ファイルを作成

SBTのしきたりにあわせて、プロジェクトのディレクトリを作り、4つのファイルを作成します。
ここでは、~/work内で作業します。

ディレクトリ作成
mkdir ~/work
cd ~/work
mkdir project
mkdir -p src/main/scala
mkdir -p src/main/resources

ファイル作成(4つ)
・build.sbt (SBTプロジェクトの設定。空白もこのままじゃないと駄目だそうです。)
import AssemblyKeys._

assemblySettings

name := "HeavyPiClusterProject"

version := "1.0"
     
scalaVersion := "2.10.3"
     
resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
     
libraryDependencies +=
    "com.typesafe.akka" %% "akka-actor" % "2.2.3"

libraryDependencies +=
    "com.typesafe.akka" %% "akka-cluster" % "2.2.3"

・project/assembly.sbt (SBTのjarパッケージプラグインの設定)
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.10.2")

・src/main/resources/application.conf (akkaの設定、主にCluster関連)
akka {
    actor {
        provider = "akka.cluster.ClusterActorRefProvider"
    }
    remote {
        log-remote-lifecycle-events = off
        netty.tcp {
            hostname = "127.0.0.1"
            port = 0
        }
    }
     
    cluster {
        seed-nodes = [
            "akka.tcp://HeavyPiCluster@10.0.0.1:2551",
            "akka.tcp://HeavyPiCluster@10.0.0.2:2551"]
     
        auto-down = on
    }
}

・src/main/scala/HeavyPiCluster.scala (akkaのプログラム)
import akka.actor._
import akka.cluster.Cluster
import akka.cluster.ClusterEvent._
import akka.cluster.routing.ClusterRouterConfig
import akka.cluster.routing.ClusterRouterSettings
import akka.routing.ConsistentHashingRouter
import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope

import java.math.BigInteger
import java.util.Random
 
object HeavyPiCluster {
    def main(args: Array[String]): Unit = {
        if (args.nonEmpty){
            System.setProperty("akka.remote.netty.tcp.hostname", args(0))
            System.setProperty("akka.remote.netty.tcp.port", args(1))
        }
 
        val system = ActorSystem("HeavyPiCluster")
        val clusterListener = system.actorOf(Props[HeavyPiListener],
          name = "clusterListener")
 
        Cluster(system).subscribe(clusterListener, classOf[ClusterDomainEvent])

        if(args.length > 2){
            Thread.sleep(3000)

            val router = system.actorOf(Props[HeavyPi].withRouter(
                ClusterRouterConfig(ConsistentHashingRouter(), ClusterRouterSettings(
                totalInstances = 100, maxInstancesPerNode = 3,
                allowLocalRoutees = false, useRole = None))),
                name = "clusterrouter")

            (1 to 100).foreach{ i =>
                router.tell(ConsistentHashableEnvelope(HeavyPi.Request(1000), i), clusterListener)
            }
        }
    }
}

class HeavyPiListener extends Actor with ActorLogging {
    var totalreq = 0
    var totaliter = 0
    var totalp = 0
    def receive = {
        case state: CurrentClusterState =>
            log.info("Current members: {}", state.members.mkString(", "))
        case MemberUp(member) =>
            log.info("Member is Up: {}", member.address)
        case UnreachableMember(member) =>
            log.info("Member detected as unreachable: {}", member)
        case MemberRemoved(member, previousStatus) =>
            log.info("Member is Removed: {} after {}",
            member.address, previousStatus)
        case HeavyPi.Response(i, p) =>
            totalreq += 1
            totaliter += i
            totalp += p
            val pi = 4D * totalp / totaliter
            log.info("Actor Response {}, pi={}", totaliter, pi)
        case _: ClusterDomainEvent => // ignore
    }
}

object HeavyPi {
    case class Request(iter: Integer)
    case class Response(iter: Integer, p: Integer)
}
     
class HeavyPi extends Actor with akka.actor.ActorLogging {
    val b = 256
    val m = new BigInteger("2").pow(b).subtract(new BigInteger("1"))
    val m2 = m.pow(2)

    def receive = {
        case HeavyPi.Request(iter) =>
            var p = 0

            (1 to iter).foreach{ i =>
                val rnd = new Random()
                val x = new BigInteger(b, rnd)
                val y = new BigInteger(b, rnd)
                val r2 = x.multiply(x).add(y.multiply(y))
                if(r2.compareTo(m2) <= 0) p += 1
            }
            sender ! HeavyPi.Response(iter, p)
    }
}

1-2. プログラムをコンパイルして、jarパッケージを作成し、各サーバーに配布

コンパイルしてjarファイルを作成。
sbt assembly
結果例
...(長いので省略)...
[info] SHA-1: f53f2a9fc0c122bfcea1620de9a2fdfb892403ca
[info] Packaging /home/test/work/target/scala-2.10/HeavyPiClusterProject-assembly-1.0.jar ...
[info] Done packaging.
[success] Total time: 30 s, completed 2014/01/01 20:12:34

できあがりは、HeavyPiClusterProject-assembly-1.0.jarです。

そして、jarパッケージファイルを各サーバーにコピーします。一応、コマンド例は、
scp target/scala-2.10/HeavyPiClusterProject-assembly-1.0.jar 10.0.0.2:/tmp/

2. 実行

一台目(seedノード)
java -cp HeavyPiClusterProject-assembly-1.0.jar HeavyPiCluster 10.0.0.1 2551

二台目(seedノード)
java -cp HeavyPiClusterProject-assembly-1.0.jar HeavyPiCluster 10.0.0.2 2551

三台目(普通のノード。同様に、いくつでも増やせます。0台でも。...4台目サーバーが無い人は、ここを省略。)
java -cp HeavyPiClusterProject-assembly-1.0.jar HeavyPiCluster 10.0.0.3 2551

このあたりで、各コンソールの表示を見ると、以下の感じの行が見えるはずです。
[INFO] ...(省略)... [HeavyPiCluster-akka.actor.default-dispatcher-4] [akka://HeavyPiCluster/user/clusterListener] Member is Up: akka.tcp://HeavyPiCluster@10.0.0.1:2551
[INFO] ...(省略)... [HeavyPiCluster-akka.actor.default-dispatcher-14] [akka://HeavyPiCluster/user/clusterListener] Member is Up: akka.tcp://HeavyPiCluster@10.0.0.2:2551
[INFO] ...(省略)... [HeavyPiCluster-akka.actor.default-dispatcher-12] [akka://HeavyPiCluster/user/clusterListener] Member is Up: akka.tcp://HeavyPiCluster@10.0.0.3:2551
この場合、この時点では、3台構成のAkkaのクラスターが動作していることになります。

四台目のノードで、π計算を開始。(routerを作って、各ノードに計算要求のmessageを投入。)
java -cp HeavyPiClusterProject-assembly-1.0.jar HeavyPiCluster 10.0.0.4 2551 pi

ここで、少しすると、各ノードから結果がかえってきて、以下のようになります。
 ...(省略)...
[INFO] ...(省略)... [HeavyPiCluster-akka.actor.default-dispatcher-2] [akka://HeavyPiCluster/user/clusterListener] Actor Response 98000, pi=3.1386938775510203
[INFO] ...(省略)... [HeavyPiCluster-akka.actor.default-dispatcher-2] [akka://HeavyPiCluster/user/clusterListener] Actor Response 99000, pi=3.1387878787878787
[INFO] ...(省略)... [HeavyPiCluster-akka.actor.default-dispatcher-16] [akka://HeavyPiCluster/user/clusterListener] Actor Response 100000, pi=3.13968
--
以上

2014年1月20日月曜日

Akka 2.2.3をちょっと試す

Akka 2.2.3を、1台のサーバーで簡単に試すメモです。(http://akka.io/)
akka 2.2.3 + Java 7(OpenJDK) + scala 2.10を、CentOS6上で。(SBTは、使わないつもりでしたが...。)
複数のサーバー(3,4台)でクラスタを簡単に試す話は、こっちです。

目標は、こういう感じです。

0. 準備

ディレクトリ ~/work で、試します。

0-1. Java

yum -y install java-1.7.0-openjdk-devel

確認例
> javac -version
javac 1.7.0_51

0-2. Scala

CentOS6なら、http://www.scala-lang.org/download/からrpmをダウンロードして、インストール
(http://www.scala-lang.org/files/archive/scala-2.10.3.rpm)
rpm -ivh scala-2.10.3.rpm

確認例
> scalac -version
Scala compiler version 2.10.3 -- Copyright 2002-2013, LAMP/EPFL

0-3. Akka

"Akka 2.2.3 distribution (Scala 2.10)"を、http://akka.io/downloads/からダウンロードする。
(http://downloads.typesafe.com/akka/akka-2.2.3.zip)

cd ~
mkdir work
cd work
unzip どこそこ/akka-2.2.3.zip

1. 簡単な例

1-1. (A) 1つのactorに、メッセージを送る。

HelloWorld.scala
import akka.actor.Actor
import akka.actor.Props
     
class HelloWorld extends Actor with akka.actor.ActorLogging {
     
    override def preStart(): Unit = {
        log.debug("Hello preStart")

        val greeter = context.actorOf(Props[Greeter], "greeter")

        greeter ! Greeter.Greet("alice")
        greeter ! Greeter.Greet("bob")
        greeter ! Greeter.Greet("carol")

        log.debug("Bye preStart")
    }
     
    def receive = {
        case Greeter.GreetBack(answer) =>
            log.info("GreetBack: " + answer)
    }
}

object Greeter {
    case class Greet(who: String)
    case class GreetBack(answer: String)
}
     
class Greeter extends Actor with akka.actor.ActorLogging {
    def receive = {
    case Greeter.Greet(who) =>
        log.info("Hello " + who)
        sender ! Greeter.GreetBack("Hello back from " + who)
    }
}

> scalac -cp akka-2.2.3/lib/akka/akka-actor_2.10-2.2.3.jar HelloWorld.scala 
> java -cp akka-2.2.3/lib/akka/akka-actor_2.10-2.2.3.jar:akka-2.2.3/lib/akka/config-1.0.2.jar:akka-2.2.3/lib/scala-library.jar:. akka.Main HelloWorld
[INFO] ...略... [Main-akka.actor.default-dispatcher-2] [akka://Main/user/app] Hello preStart
[INFO] ...略... [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app/greeter] Hello alice
[INFO] ...略... [Main-akka.actor.default-dispatcher-5] [akka://Main/user/app/greeter] Hello bob
[INFO] ...略... [Main-akka.actor.default-dispatcher-5] [akka://Main/user/app/greeter] Hello carol
[INFO] ...略... [Main-akka.actor.default-dispatcher-2] [akka://Main/user/app] Bye preStart
[INFO] ...略... [Main-akka.actor.default-dispatcher-2] [akka://Main/user/app] GreetBack: Hello back from alice
[INFO] ...略... [Main-akka.actor.default-dispatcher-2] [akka://Main/user/app] GreetBack: Hello back from bob
[INFO] ...略... [Main-akka.actor.default-dispatcher-2] [akka://Main/user/app] GreetBack: Hello back from carol
^C

1-2. (B) 3つのactorに、メッセージを送る。

HelloWorld2.scala
import akka.actor.Actor
import akka.actor.Props
     
class HelloWorld2 extends Actor with akka.actor.ActorLogging {
     
    override def preStart(): Unit = {
        log.debug("Hello preStart")

        val greeter1 = context.actorOf(Props[Greeter], "greeter1")
        val greeter2 = context.actorOf(Props[Greeter], "greeter2")
        val greeter3 = context.actorOf(Props[Greeter], "greeter3")

        greeter1 ! Greeter.Greet("alice")
        greeter2 ! Greeter.Greet("bob")
        greeter3 ! Greeter.Greet("carol")

        log.debug("Bye preStart")
    }
     
    def receive = {
        case Greeter.GreetBack(answer) =>
            log.info("GreetBack: " + answer)
    }
}

object Greeter {
    case class Greet(who: String)
    case class GreetBack(answer: String)
}
     
class Greeter extends Actor with akka.actor.ActorLogging {
    def receive = {
    case Greeter.Greet(who) =>
        log.info("Hello " + who)
        sender ! Greeter.GreetBack("Hello back from " + who)
    }
}

> # scalac -cp akka-2.2.3/lib/akka/akka-actor_2.10-2.2.3.jar HelloWorld2.scala
> java -cp akka-2.2.3/lib/akka/akka-actor_2.10-2.2.3.jar:akka-2.2.3/lib/akka/config-1.0.2.jar:akka-2.2.3/lib/scala-library.jar:. akka.Main HelloWorld2
[INFO] ...略... [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app] Hello preStart
[INFO] ...略... [Main-akka.actor.default-dispatcher-6] [akka://Main/user/app/greeter1] Hello alice
[INFO] ...略... [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app/greeter2] Hello bob
[INFO] ...略... [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app] Bye preStart
[INFO] ...略... [Main-akka.actor.default-dispatcher-6] [akka://Main/user/app/greeter3] Hello carol
[INFO] ...略... [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app] GreetBack: Hello back from alice
[INFO] ...略... [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app] GreetBack: Hello back from bob
[INFO] ...略... [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app] GreetBack: Hello back from carol
^C

1-3-1. (C) 手動生成の3つのactorに、router経由で、3つのメッセージを送る。

こんな感じかと思ったけど、うまく行かないから、省略。(sbt利用だと成功しますが。)
        val greeter1 = context.actorOf(Props[Greeter], "greeter1")
        val greeter2 = context.actorOf(Props[Greeter], "greeter2")
        val greeter3 = context.actorOf(Props[Greeter], "greeter3")

        val greeters = Vector[ActorRef](greeter1, greeter2, greeter3)
        val router = context.actorOf(Props.empty.withRouter(RoundRobinRouter(routees = greeters))))

        router ! Greeter.Greet("alice")

1-3-2. (C) 自動生成の3つのactorに、router経由で、3つのメッセージを送る。

HelloWorld5.scala
import akka.actor.Actor
import akka.actor.ActorRef
import akka.routing.FromConfig
import akka.actor.Props
     
class HelloWorld5 extends Actor with akka.actor.ActorLogging {
     
    override def preStart(): Unit = {
        log.debug("Hello preStart")

        val router = context.actorOf(Props[Greeter].withRouter(FromConfig), "router1")

        router ! Greeter.Greet("alice")
        router ! Greeter.Greet("bob")
        router ! Greeter.Greet("carol")

        log.debug("Bye preStart")
    }
     
    def receive = {
        case Greeter.GreetBack(answer) =>
            log.info("GreetBack: " + answer)
    }
}

object Greeter {
    case class Greet(who: String)
    case class GreetBack(answer: String)
}
     
class Greeter extends Actor with akka.actor.ActorLogging {
    def receive = {
    case Greeter.Greet(who) =>
        log.info("Hello " + who)
        sender ! Greeter.GreetBack("Hello back from " + who)
    }
}
application.conf
akka.actor.deployment {
    /app/myrouter1 {
          router = round-robin
        nr-of-instances = 5
    }
}
ここでround-robinをrandomにかえたり、いろいろ設定変更できます。
> java -cp akka-2.2.3/lib/akka/akka-actor_2.10-2.2.3.jar:akka-2.2.3/lib/akka/config-1.0.2.jar:akka-2.2.3/lib/scala-library.jar:. akka.Main HelloWorld5
[INFO] ...略... [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app] Hello preStart
[INFO] ...略... [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app/router1/$a] Hello alice
[INFO] ...略... [Main-akka.actor.default-dispatcher-6] [akka://Main/user/app/router1/$b] Hello bob
[INFO] ...略... [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app/router1/$c] Hello carol
[INFO] ...略... [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app] Bye preStart
[INFO] ...略... [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app] GreetBack: Hello back from alice
[INFO] ...略... [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app] GreetBack: Hello back from bob
[INFO] ...略... [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app] GreetBack: Hello back from carol
^C

2. Clusterで試す(1台のサーバー上の4ノード) = (D)

akka2.2.3のclusterの話(scala)は、このあたり
http://doc.akka.io/docs/akka/2.2.3/common/cluster.html
http://doc.akka.io/docs/akka/2.2.3/scala/cluster-usage.html です。

以下は、actorを自動生成する例です。上記ドキュメント(後者)上は、Router with Remote Deployed Routeesの名前になっています。 すでに別途作成したactorをrouterに関連づける方法はRouter Example with Lookup of Routeesです。バージョンがかわると、呼び方もあっさり変わるみたいです。

2-0. SBTを入れる

SBT無しだと、一部うまく動かなかったので。

SBTのrpmをここ(http://www.scala-sbt.org/release/docs/Getting-Started/Setup.htmlの「RPM package」)からダウンロード
(http://repo.scala-sbt.org/scalasbt/sbt-native-packages/org/scala-sbt/sbt/0.13.1/sbt.rpm)
> rpm -ivh sbt.rpm 
準備中...                ########################################### [100%]
   1:sbt                    ########################################### [100%]
> sbt version
...(省略: 一度目だけ時間がかかります)...
[info] Set current project to root (in build file:/root/)
[info] 0.1-SNAPSHOT

2-1. 4つのノード上の自動生成のactorに、メッセージを送る。 

2-1-1. SBTのしきたりにあわせてファイルを準備する。

ここでは~/work2ディレクトリで。
> mkdir ~/work2
> cd ~/work2
> mkdir -p src/main/scala
> mkdir -p src/main/resources

三つのファイルを準備。SBT上のプロジェクト設定ファイルbuild.sbtと、scalaのプログラムファイルHelloCluster.scala、akkaの設定ファイルapplication.confで、それぞれの場所に配置します。

・build.sbt
name := "Hello Cluster Project"

version := "1.0"
     
scalaVersion := "2.10.3"
     
resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
     
libraryDependencies +=
    "com.typesafe.akka" %% "akka-actor" % "2.2.3"

libraryDependencies +=
    "com.typesafe.akka" %% "akka-cluster" % "2.2.3"

・src/main/scala/HelloCluster.scala
import akka.actor._
import akka.cluster.Cluster
import akka.cluster.ClusterEvent._
import akka.cluster.routing.ClusterRouterConfig
import akka.cluster.routing.ClusterRouterSettings
import akka.routing.ConsistentHashingRouter
import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope
 
object HelloCluster {
    def main(args: Array[String]): Unit = {
        if (args.nonEmpty){
            System.setProperty("akka.remote.netty.tcp.hostname", args(0))
            System.setProperty("akka.remote.netty.tcp.port", args(1))
        }
 
        val system = ActorSystem("ClusterSystem")
        val clusterListener = system.actorOf(Props[HelloClusterListener],
          name = "clusterListener")
 
        Cluster(system).subscribe(clusterListener, classOf[ClusterDomainEvent])

        if(args.length > 2){
            Thread.sleep(3000)

            val router = system.actorOf(Props[Greeter].withRouter(
                ClusterRouterConfig(ConsistentHashingRouter(), ClusterRouterSettings(
                totalInstances = 100, maxInstancesPerNode = 3,
                allowLocalRoutees = false, useRole = None))),
                name = "clusterrouter")

            router.tell(ConsistentHashableEnvelope(Greeter.Greet("alice"), "alice"), clusterListener)
            router.tell(ConsistentHashableEnvelope(Greeter.Greet("bob"), "bob"), clusterListener)
            router.tell(ConsistentHashableEnvelope(Greeter.Greet("carol"), "carol"), clusterListener)
        }
    }
}
 
class HelloClusterListener extends Actor with ActorLogging {
    def receive = {
        case state: CurrentClusterState =>
            log.info("Current members: {}", state.members.mkString(", "))
        case MemberUp(member) =>
            log.info("Member is Up: {}", member.address)
        case UnreachableMember(member) =>
            log.info("Member detected as unreachable: {}", member)
        case MemberRemoved(member, previousStatus) =>
            log.info("Member is Removed: {} after {}",
            member.address, previousStatus)
        case Greeter.GreetBack(answer) =>
            log.info("GreetBack: " + answer)
        case _: ClusterDomainEvent => // ignore
    }
}

object Greeter {
    case class Greet(who: String)
    case class GreetBack(answer: String)
}
     
class Greeter extends Actor with akka.actor.ActorLogging {
    def receive = {
    case Greeter.Greet(who) =>
        log.info("Hello " + who)
        sender ! Greeter.GreetBack("Hello back from " + who)
    }
}

・src/main/resources/application.conf
akka {
    actor {
        provider = "akka.cluster.ClusterActorRefProvider"
    }
    remote {
        log-remote-lifecycle-events = off
        netty.tcp {
            hostname = "127.0.0.1"
            port = 0
        }
    }
     
    cluster {
        seed-nodes = [
            "akka.tcp://ClusterSystem@127.0.0.1:2551",
            "akka.tcp://ClusterSystem@127.0.0.1:2552"]
     
        auto-down = on
    }
}

2-1-2. 4つのノードを実行。

一つのサーバー上で4ノード実行させます。ポート違いで、127.0.0.1:2551, 127.0.0.1:2552, 127.0.0.1:2553, 127.0.0.1:2554の四つです。(このうち2551と2552は、クラスタの中の特別なノードで、application.confファイルのseed-nodesとして設定されています。)

作業は、4つのターミナルを開いて、それぞれ、~/work2ディレクトリで、 実行します。

ターミナル1 (seedノードの1つ目。port=2551は、confファイルのseed-nodesにて設定。)
sbt "run-main HelloCluster 127.0.0.1 2551"

ターミナル2 (seedノードの2つ目。port=2552は、confファイルのseed-nodesにて設定。)
sbt "run-main HelloCluster 127.0.0.1 2552"

ターミナル3 (普通のノード。portは2551,2552以外ならなんでもOK。同様にノードを増やせます。)
sbt "run-main HelloCluster 127.0.0.1 2553"

ここまでで、3ノードクラスタが動作して、それぞれで、おおむね以下の表示で、確認できます。
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-2] [akka://ClusterSystem/user/clusterListener] Member is Up: akka.tcp://ClusterSystem@127.0.0.1:2551
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-5] [akka://ClusterSystem/user/clusterListener] Member is Up: akka.tcp://ClusterSystem@127.0.0.1:2552
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-2] [akka://ClusterSystem/user/clusterListener] Member is Up: akka.tcp://ClusterSystem@127.0.0.1:2553

ターミナル4
ここでは、ついでに、routerを作って、messageを投入。
sbt "run-main HelloCluster 127.0.0.1 2554 go"
すると、以下が、どこかのノードに表示され、
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-13] [akka://ClusterSystem/remote/akka.tcp/ClusterSystem@127.0.0.1:2554/user/clusterrouter/c4] Hello alice
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-4] [akka://ClusterSystem/remote/akka.tcp/ClusterSystem@127.0.0.1:2554/user/clusterrouter/c1] Hello bob
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-4] [akka://ClusterSystem/remote/akka.tcp/ClusterSystem@127.0.0.1:2554/user/clusterrouter/c9] Hello carol
もとのノード(ターミナル4)に、以下が表示されます。
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-4] [akka://ClusterSystem/user/clusterListener] GreetBack: Hello back from alice
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-13] [akka://ClusterSystem/user/clusterListener] GreetBack: Hello back from bob
[INFO] ...略... [ClusterSystem-akka.actor.default-dispatcher-4] [akka://ClusterSystem/user/clusterListener] GreetBack: Hello back from carol

3.Clusterで試す(4台のサーバー上の4ノード)

3-1. プログラムをjarパッケージ化して他のサーバへ配布


SBTプロジェクトにsbt-assemblyを設定
sbt-assemblyは、https://github.com/sbt/sbt-assembly

・project/assembly.sbt として以下の内容のファイルを作成
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.10.2")

・build.bstに以下を追加(import行は、ファイルの先頭に)
import AssemblyKeys._

assemblySettings


jarパッケージを作成

ここで、4サーバーは、10.0.0.1:2551, 10.0.0.2:2551, 10.0.0.3:2551, 10.0.0.4:2551として、 seed-nodeを10.0.0.1:2551と10.0.0.2:2551とします。

・src/main/resources/application.confの以下の部分を変更します。
    cluster {
        seed-nodes = [
            "akka.tcp://ClusterSystem@10.0.0.1:2551",
            "akka.tcp://ClusterSystem@10.0.0.2:2551"]
     
        auto-down = on
    }

パッケージを作成します。
sbt package

できあがりは、 target/scala-2.10/hello-cluster-project_2.10-1.0.jar ファイルです。
これを、各サーバにコピーします。各サーバーでは、javaのみ必要です。

3-2. 各サーバ上で、ノードを実行

一つ目(seedノード)
java -cp hello-cluster-project_2.10-1.0.jar HelloCluster 10.0.0.1 2551

二つ目(seedノード)
java -cp hello-cluster-project_2.10-1.0.jar HelloCluster 10.0.0.2 2551

三つ目(普通のノード。同様に、いくつでも増やせます。)
java -cp hello-cluster-project_2.10-1.0.jar HelloCluster 10.0.0.3 2551

四つ目で、ついでに、routerを作って、messageを投入。
java -cp hello-cluster-project_2.10-1.0.jar HelloCluster 10.0.0.4 2551 go

結果は、2と同じです。

--
以上

2014年1月16日木曜日

コンパクトデジタルカメラの重量とセンサ面積

コンパクトデジカメの重量とセンサ面積を、なんとなく並べてみました。最近、コンパクトデジカメのセンサが大きくなって嬉しいです。レンズも気になりますし、センサさえ大きければいいというものではないでしょうが、同じ世代のものなら小さいセンサがいいということは無いと思うので、なんぞの参考にはなるかな、と。

スマホとの比較のためにiPhone 5sとXperia Z1、レンズ交換式カメラとの比較にEOS M2、息の長い少し重めのコンパクトデジカメとしてGR IVを、並べました。(カメラというならスマホよりは大きいセンサがいいなあ、と思ったり。まあ、最近のスマホは重すぎる感じはしますが)

メーカ 機種 重量 [g] (*1)センサ面積
[mm2]
センサ形状
[mm x mm]
有効画素数
[万画素]
センサ種類 画角 [mm]
(35mm判換算)
F値
NIKON COOLPIX A 299 368 23.6x15.6 1616 CMOS (DX) 28 2.8
RICHO GR 245 372 23.7x15.7 1620 CMOS (APS-C) 28 2.8–16
RICHO GR DIGITAL IV 219 43 7.6x5.7 1000 CCD (1/1.7) 28 1.9–9
CANON EOS M2 + EF-M22 379 332 22.3x14.9 1800 CMOS (APS-C) 35 2
CANON EOS M2 + EF-M18-55 484 332 22.3x14.9 1800 CMOS (APS-C) 29-88 3.5-5.6
CANON EOS M2 + EF-M11-22 495 332 22.3x14.9 1800 CMOS (APS-C) 18-35 4–5.6
CANON PowerShot G1 X 534 262 18.7x14 1430 CMOS (1.5) 28-112 2.8-5.8
SONY DSC-RX1 482 856 35.8x23.9 2430 CMOS (35mmフル) 35 2
SONY DSC-RX100 240 116 13.2x8.8 2020 CMOS (1.0) 28-100 1.8–4.9
SONY DSC-RX100M2 281 116 13.2x8.8 2020 CMOS (1.0) 28-100 1.8–4.9
SONY Xperia Z1 169 26 5.9x4.4 2070 CMOS (1/2.3)
2
Apple iPhone 5s (*2) 112 17 4.8x3.6 800 (1/3)
2.2
*1 バッテリー・メモリカード含む
*2 http://en.wikipedia.org/wiki/Image_sensor_format

グラフにもしてみました。印は、ズームレンズのものです。

コンパクトカメラは、左上の白い部分に入るくらいが嬉しいです。理由は、
・コンパクトというなら、重さ250g以下くらいにして欲しいなあ、
・カメラと言うなら、スマホよりはいいセンサにして欲しいなあ、
と。

GR(単焦点)とDSC-RX100(ズーム)が、やっと250gくらい。他のものも、どんどん左に寄る(=軽くなる)といいなあ。一方、フルサイズセンサのDSC-RX1、レンズ交換式のEOS M2(APS-Cサイズのセンサ)は、頑張ってる気が。逆に、PowerShot G1Xは、...EOS M2の登場で要改良かと。

--
以上