使用Node.js连接到运行在Docker上的Cassandra数据库

使用node.js访问Docker上的Cassandra。

我听说KVS的一种是Cassandra,于是我用node.js创建了一个typescript应用程序,并尝试访问Cassandra。
以下是我为了不忘记这个过程中所经历的困难而做的备忘录。

环境


    • Cassandra環境(3.10) : Docker on Linux(CentOS7)上に構築

 

    Node環境(v8.1.2) : OSX 10.11.6 上に構築

安装Cassandra


如果不知道自己使用的版本会让我很烦恼,所以我选择在Linux上构建了一个Docker环境并安装了TAG指定的版本。
如果有时间,我想挑战一下Cassandra的集群配置。

$ docker pull cassandra:3.10
$ docker volume create --name main-cassandra-db
$ docker run -d --name main-cassandra -v main-cassandra-db:/var/lib/cassandra -p 9160:9160 -p 9042:9042 -m 1G cassandra:3.10

由于在Linux上没有安装cqlsh,使用Docker中的cqlsh进行访问。确保它已经启动。

$ docker exec -it main-cassandra cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.10 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
cqlsh>

为了方便批量处理keyspace等操作,因此建议在OSX上安装cqlsh以进行访问。由于cqlsh是用Python编写的,因此可以使用pip命令进行安装。

$ python --version
Python 2.7.12
$ pip install cqlsh
$ cqlsh --version
cqlsh 5.0.1

当在OSX上尝试访问Linux上的Cassandra时会失败。(192.168.45.131是Linux的IP地址)

$ cqlsh 192.168.45.131
Connection error: ('Unable to connect to any servers', {'192.168.45.131': ProtocolError("cql_version '3.3.1' is not supported by remote (w/ native protocol). Supported versions: [u'3.4.4']",)})

尽管原因不太清楚,但可以参考这里并避免问题。既然Docker和cqlsh的版本相同,希望服务器和CQL版本能够得到合理的安排。

通过在cqlsh的配置文件中添加这些内容,似乎无需指定版本即可运行。请参考这里,创建一个名为~/.cassandra/cqlshrc的文件。
起初,我创建了一个~/.cqlshrc文件,但是当发现~/.cassandra/cqlshrc不存在时,我把它重命名为~/.cassandra/cqlshrc。

[cql]
version = 3.4.4
$ cqlsh 192.168.45.131
Connected to Test Cluster at 192.168.45.131:9042.
[cqlsh 5.0.1 | Cassandra 3.10 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
cqlsh>

我试着将Python版本升级到3.5.2,但它根本无法运行。
通过研究发现,它可能只支持Python2.7。
我检查了cqlsh的源代码,它严格检查了Python的版本。

if sys.version_info[:2] != (2, 7):
    sys.exit("\nCQL Shell supports only Python 2.7\n")

安装Node.js


安装Node.js。在四月份玩Node.js的时候,我用的是v6版本,但最近好像出了v8,所以想试试v8版本。

$ nodebrew install-binary v8.1.2
$ nodebrew use v8.1.2
use v8.1.2
$ node --version
v8.1.2

我正在使用Typescript来创建Cassandra的客户端,因此需要创建必要的定义文件。

    package.json
{
  "name": "cassandra",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "./node_modules/.bin/tsc -w -d --sourceMap"
  }
}
    tsconfig.json
{
  "compilerOptions": {
    /* Basic Options */
    "target": "es2015",                /* Specify ECMAScript target version. */
    "module": "commonjs",              /* Specify module code generation. */
    "declaration": true,               /* Generates corresponding '.d.ts' file. */
    "sourceMap": true,                 /* Generates corresponding '.map' file. */

    /* Strict Type-Checking Options */
    "strict": true                     /* Enable all strict type-checking options. */
  },

  "exclude": [
   "node_modules"
  ]
}

安装所需的软件包。

$ npm install --save-dev typescript
$ npm install --save cassandra-driver
$ npm install --save-dev @types/cassandra-driver

创建KeySpace表格和用于插入数据的脚本。


创建关键空间表和数据插入脚本。

create keyspace IF NOT EXISTS Sample
       with replication = {'class':'SimpleStrategy','replication_factor':1};

create table IF NOT EXISTS Sample.Cookie (
  id text primary key,     // Primary key SHA256 hash
  cookie text,             // Cookie Text
  update_time timestamp,   // Update Time
  create_time timestamp    // Create Time
) WITH comment='Cookie management table.';

begin batch

insert into Sample.Cookie ( id, create_time ) VALUES ( '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', toTimestamp(now()) );
insert into Sample.Cookie ( id, create_time ) VALUES ( 'd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35', toTimestamp(now()) );

apply batch;

将数据注入到Cassandra


使用之前创建的脚本,在Docker上构建的Cassandra数据库中,进行键空间、表和数据的插入。

$ cqlsh 192.168.45.131 < sample.cql

如果使用-f选项指定脚本,并在数据插入脚本中使用了”// Cookie Text”这样的注释或者comment属性中使用了日语,则会出现以下错误消息。看起来,似乎在create table命令中存在日语是不可以的,限定在冒号之前。

sample.cql:11:'ascii' codec can't encode characters in position 134-137: ordinal not in range(128)
sample.cql:26:InvalidRequest: Error from server: code=2200 [Invalid query] message="unconfigured table cookie"

然而,在使用docker中的cqlsh时,可以成功创建表而不会出现错误。难道不是因为Python版本的差异吗?我试过在OSX上安装了2.7.9,但错误并没有改变。

$ docker exec -it main-cassandra bash
root@32e0a9c14cc7:/# cqlsh -f /tmp/sample.cql
root@32e0a9c14cc7:/# python --version
Python 2.7.9

使用Node.js和Typescript应用程序访问Cassandra。


cassandra-driver的使用方式有两种,如下所示,一种是Promise风格,另一种是回调函数风格。为了跟上时代的潮流,我们决定采用Promise风格。

然而,npm上注册的@types/cassandra-driver只定义了回调函数风格,因此如果采用Promise风格进行编写,会出现以下错误。

$ npm start
> tsc -w -d --sourceMap
src/client.ts(17,44): error TS2339: Property 'then' does not exist on type 'void'.
src/client.ts(24,52): error TS2339: Property 'then' does not exist on type 'void'.

在github上存在一个定义了Promise风格的类的文件,通过覆盖它可以使Typescript源代码能够进行编译。

$ wget https://raw.githubusercontent.com/aliem/DefinitelyTyped/feature/cassandra-driver-promises/cassandra-driver/index.d.ts \
         -O node_modules/@types/cassandra-driver/index.d.ts
    client.ts Cassandraにアクセスするクライアントモジュール
// このソースをコンパイルするには、npm --save-dev @types/cassandra-driver を実行した後、
// node_modules/@types/cassandra-driver/index.d.ts を以下のファイルと置き換える必要がある。
// master には、callback形式の型は登録されているが、Promise形式の型が登録されていないため、コンパイルエラーとなる。
// なぜか、masterにマージされていない。おそらく、lintエラー取るためにかなり手を入れているようだから、その影響かもしれない。
// wget https://raw.githubusercontent.com/aliem/DefinitelyTyped/feature/cassandra-driver-promises/cassandra-driver/index.d.ts \
//         -O node_modules/@types/cassandra-driver/index.d.ts
//
import {types, Client, QueryOptions} from 'cassandra-driver';

export class client extends Client {
    constructor(hosts: string) {
        super({ contactPoints: [ hosts ] });
    }

    public select(id: string): Promise<any> {
        const query: string = "select * from Sample.Cookie where id = ?";
        return this.execute(query, [ id ]).then((result: types.ResultSet) => {
            return (result.rows);
        });
    }

    public update(id: string, cookie: string ): Promise<any> {
        const query: string = "update Sample.Cookie set cookie = ?, update_time = toTimestamp(now()) where id = ? IF EXISTS";
        return this.execute(query, [ cookie, id ]).then((result: types.ResultSet) => {
            return (result);
        });
    }
}
    main.ts クライアントモジュールを使用して、SELECTとUPDATEを実行
//
import {client} from './client';

var c: client;

new Promise((resolve) => {
    c = new client(process.argv[2]);
    resolve();
}).then(async () => {
    const result = await c.select('6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b');
    console.log(result);
}).then(async () => {
    const result = await c.update('6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', 'cookie string');
    if (result.rows && result.rows[0]['[applied]']) {
        console.log('更新成功');
    } else {
        console.log('更新失敗');
    }
    console.log(result);
}).then(async () => {
    const result = await c.select('6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b');
    console.log(result);
}).then(async () => {
    await c.shutdown();
}, async (err) => {
    console.log(err);
    await c.shutdown();
});

运行结果


$ node src/main.js 192.168.45.131
[ Row {
    id: '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b',
    cookie: 'cookie string',
    create_time: 2017-06-25T11:32:43.529Z,
    update_time: 2017-06-25T11:41:40.859Z } ]
更新成功
ResultSet {
  info: 
   { queriedHost: '192.168.45.131:9042',
     triedHosts: {},
     achievedConsistency: 10,
     traceId: undefined,
     warnings: undefined,
     customPayload: undefined },
  rows: [ Row { '[applied]': true } ],
  rowLength: 1,
  columns: [ { name: '[applied]', type: [Object] } ],
  pageState: null,
  nextPage: undefined }
[ Row {
    id: '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b',
    cookie: 'cookie string',
    create_time: 2017-06-25T11:32:43.529Z,
    update_time: 2017-06-26T10:37:42.001Z } ]
广告
将在 10 秒后关闭
bannerAds