Node.js 与 HBase 通信

1,014 阅读2分钟

Node.js 与 HBase 通信

最近在搞数据检索平台,其中一环用到了 HBase。与其他常见的数据库不同,当你安装了数据库,下载了Node.js 包,你会发现几乎所有的包都不可用,同时关于 Node.js 与 HBase 的话题又比较少,作为 HBase 初学者会很抓狂。

HBase 非集群环境搭建

下载HBase 2.4.4 bin 安装包

curl -o https://downloads.apache.org/hbase/2.4.4/hbase-2.4.4-bin.tar.gz

启动 HBase 服务

./bin/start-hbase.sh

启动 thrif2 server 服务

./bin/hbase thrift2

thrift2 服务器监听 9090 端口

HBase thrift2 客户端编译

下载,编译,安装 thrift

curl -o https://downloads.apache.org/thrift/0.14.2/thrift-0.14.2.tar.gz
tar -zxvf thrift-0.14.2.tar.gz
cd thrift-0.14.2
configure
make
make install

下载 HBase 的 thrift 文件

https://github.com/apache/hbase/blob/rel/2.4.4/hbase-thrift/src/main/resources/org/apache/hadoop/hbase/thrift2/hbase.thrift

注意: 不同 HBase 版本的 thrift 文件可能不一样,可能每个版本都需要重新编译,重新调整 Node.js 端的代码(这也是被诟病的地方)

编译 thrift, 生成 hbase_types.jsTHBaseService.js

thrift --gen js: node hbase.thrift

Node.js 与 HBase 通信

thrift2.jpg

Node.js 可以通过 REST 和 Thrift RPC 方式跟 HBase 通信,个人更倾向于 RPC 的方式,所以选用了 Thrift2 方案(Thrift2 是 Thrift 的升级版)

创建链接

const thrift = require("thrift");
const HBase = require("../gen-nodejs/THBaseService"); // 接口文件
const HBaseTypes = require("../gen-nodejs/hbase_types");

const transport = thrift.TBufferedTransport;
const protocol = thrift.TBinaryProtocol;

const connection = thrift.createConnection("localhost", 9090, {
  transport: transport,
  protocol: protocol,
});

connection.on("error", function (err) {
  console.log("error:", err);
});

connection.on("close", function (err) {
  console.log("close");
});

const client = thrift.createHttpClient(HBase, connection);

操作数据库

以创建命名空间,数据表,添加行数据,获取行数据为例

const namespace = "ns";
const tableName = "test";
const cf = "cf";

// create Namespace
client.createNamespace({ name: namespace }, function (err) {
  if (err) {
    console.log("create namespace error:", err);
  } else {
    console.log("create namespace success");
  }
});

// 创建表
client.createTable(
  {
    tableName: { ns: namespace, qualifier: tableName },
    columns: [{ name: cf }],
  },
  null,
  function (err) {
    if (err) {
      console.log("create table error:", err);
    } else {
      console.log("create table success");
    }
  }
);

// 单行新增数据, 更新数据 跟插入一样
client.put(
  `${namespace}:${tableName}`,
  {
    row: "192",
    columnValues: [
      {
        family: "cf",
        qualifier: "value",
        value: "hhhh",
      },
      {
        family: "cf",
        qualifier: "value1",
        value: "hhhh",
      },
      {
        family: "cf",
        qualifier: "value2",
        value: "hhhh",
      },
    ],
  },
  function (err) {
    if (err) {
      console.log("put column error", error);
    } else {
      console.log("put column success");
    }
  }
);

// 单条rowKey查询
client.get(`${namespace}:${tableName}`, { row: "192" }, function (err, data) {
  if (err) {
    console.log("Get error:", err);
  } else {
    console.log("Get result: " + JSON.stringify(data));
  }
});

简单封装

以获取单条数据为例子

// 格式化行数据
function formate(rowData) {
  const obj = {};
  obj.rowkey = rowData.row ? rowData.row.toString() : null;
  rowData.columnValues.forEach(column => {
      const family = column.family.toString();
      const qualName = column.qualifier.toString();
      obj[family] = obj[family] || {};
      obj[family][qualName] = column.value.toString()
  });

  return obj;
}

...
client.getAsync = promisify(client.get); // promise 化
const res = await client.getAsync(`${namespace}:${tableName}`, { row: "192" });
console.log('res', formate(res))

早期版本中,THBaseService.js 中的接口参数需要通过 hbase_types.js 中的类型进行约束,现在版本已经做了兼容处理,可以使用hbase_type进行传参数,也可以直接传 javascript 对象,例如下面两种写法是等价的。

const res = await client.createNamespaceAsync(new HBaseTypes.TNamespaceDescriptor({ name: "ns" }));
const res1 = await client.createNamespaceAsync({ name: "ns" })

待改进

  1. 引入连接池的概念
  2. 测试过程中发现,短时间内不通信链接会被断掉,可能需要引入心跳机制
  3. 部门成员主要对 mongo 结构熟悉,封装为 mongo 的操作形式

关注我的微信公众号"SUNTOPO WLOG",欢迎留言讨论,我会尽可能回复,感谢您的阅读。