使用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 } ]