使用 SDK

SQL API

上一篇稍微看過了 Azure Cosmos DB 的簡單操作後,這一篇會來講解它的大致結構,可參考下圖:

每個 Azure 帳號可以創建多個 database,每個 database 可以包含多個 collections,在 collections 中包含多個 documents。而在 collection 中可以加入 stored procedures、triggers、User Defined Functions(UDFs) 等等。

插入資料到資料庫

接著我們使用程式插入一筆資料試試:

yarn add @azure/cosmos
const cosmos = require('@azure/cosmos');
const CosmosClient = cosmos.CosmosClient;

const endpoint = "填上 443 port 的 DB Endpoint";
const masterKey = "填上金鑰";
const client = new CosmosClient({ endpoint, auth: { masterKey } });

const databaseDefinition = { id: 'TestDB' };
const collectionDefinition = { id: 'Fruits' };
const documentDefinition = { name: 'Green Apple', category: 'Apple', data: Date.now() };

async function createFruit() {
  const { body } = await client.database(databaseDefinition.id).container(collectionDefinition.id).items.create(documentDefinition);
  console.log('Created item with content');
}

createFruit().catch(err => {
  console.error(err);
});

點進去 Data explorer 的 Document 後可以看到剛才新增的資料如下:

document 結構

接著我們要來講一下每個欄位的意思:

_rid 由系統產生的唯一值,並且有順序性,例如上一筆插入的資料此欄位值為 `SvJDANLJWUiBhB4AAAAAAA==` 則下一筆會是 `SvJDANLJWUiChB4AAAAAAA==`

_etag 由系統產生, 用來優化 concurrency

_ts 由系統產生,當作最後一次更新的 timestamp

_self 由系統產生,代表資源的一個 URI,通常為此種格式 `/dbs/{dbName}/users/{userId}/permissions/{permissionId}`

id 可以由使用者自行設定,如果沒有設定系統會自動產生。

我們使用 Node.js SDK 時可以如下使用:

初始化連線:

const client = new CosmosClient({ endpoint: endpoint, auth: { masterKey: masterKey } });

創建資料庫:

const { database } = await client.databases.createIfNotExists({ id: databaseId });

建立 collection:

const { container } = await client.database(databaseId).containers.createIfNotExists({ id: containerId });

執行 SQL

const querySpec = {
    query: "SELECT VALUE r.children FROM root r WHERE r.lastName = @lastName",
    parameters: [
        {
            name: "@lastName",
            value: "Andersen"
        }
    ]
};

const { result: results } = await client.database(databaseId).container(containerId).items.query(querySpec).toArray();
for (var queryResult of results) {
    let resultString = JSON.stringify(queryResult);
    console.log(`\tQuery returned ${resultString}\n`);
}

參考資料:https://docs.microsoft.com/en-us/azure/cosmos-db/sql-api-resources

Cassandra API

Cassandra DB 簡介

相信已經許多人先前已經搭配 Spark 使用過,不過因為本賽季後續會使用,所以還是簡介一下。Cassandra 本身是 2008 由 Facebook 開源的一個 NoSQL DB,轉眼間已經十年,與 IT 鐵人賽一樣。Cassandra 的資料是以 Key-Value 的方式儲存,資料模型與 Google BigTableHBase 類似,但 Cassandra 在建立 cluster 中不會有 master 節點,每個節點都是平等的,這樣可以避免單一節點有問題時造成整個系統故障。

Cassandra 的查詢語言 CQLSQL 類似,可參考官方文件:https://cassandra.apache.org/doc/latest/cql/index.html

使用 Cassandra 一開始要先建立一個 KEYSPACE,同時設定 replication 的方式。

CREATE  KEYSPACE [IF NOT EXISTS] keyspace_name 
   WITH REPLICATION = { 
      'class' : 'SimpleStrategy', 'replication_factor' : N } 
     | 'class' : 'NetworkTopologyStrategy', 
       'dc1_name' : N [, ...] 
   }
   [AND DURABLE_WRITES =  true|false] ;

replication 策略包含以下兩種: 1.SimpleStrategy 只有一個datacenters 和一個 rack 時使用,通常用作測試環境,會設定 replication_factor 代表同步儲存資料的節點數目。 2.NetworkTopologyStrategy 通常用在生產正式環境,設定 datacenters 的各個名稱,與分別要同步的節點數目。 https://docs.datastax.com/en/cassandra/3.0/cassandra/architecture/archDataDistributeReplication.html

相關名詞介紹:https://docs.datastax.com/en/cassandra/3.0/cassandra/architecture/archIntro.html

CAP

SaaStwelve-factor,分散式系統有 CAPCassandra 屬於比較偏向 AP 的部分。

來源:http://digbigdata.com/wp-content/uploads/2013/05/media_httpfarm5static_mevIk.png

Azure Cosmos DB 的 Cassandra API

Azure Cosmos DBCassandra APIApache Cassandra 可以無縫的整合,因為使用相同的 CQLv4 ( Cassandra Query Language),只需要改一下連線的字串,即可將原本連線到自行架設的 Cassandra DB 轉為連線到 Azure Cosmos DB

無痛轉移到 Azure Cosmos DBCassandra ,以後再也不用去監控 DB 機器的狀態,以及 CPU、 RAM 使用量突然飆高等問題,也不用管理一堆 yaml 等等的配置檔案以及煩惱配置跨地區 cluster 與設定網路等等,通通都交給 Azure Cosmos DB 幫你處理即可。throughputlatency 出問題時再也不用 Dear DevOps 然後 Dear SysOps,只要於 portal 面板按個按鈕即可解決。

下一篇將介紹如何實際使用 Azure Cosmos DB 的 Cassandra API

這篇文章要來實際使用一下 Azure Cosmos DBCassandra API

如果已經安裝了 cqlsh 可以直接用如下方式連線:

set SSL_VERSION=TLSv1_2
set SSL_VALIDATE=false

cqlsh yichengcosmos.cassandra.cosmosdb.azure.com 10350 -u yichengcosmos -p <填上 PRIMARY PASSWORD> --ssl

使用官方範例

首先我們一樣先使用看看官方提供的範例:

git clone https://github.com/Azure-Samples/azure-cosmos-db-cassandra-nodejs-getting-started.git && cd  azure-cosmos-db-cassandra-nodejs-getting-started && npm install

config.contactPoint 記得在後面加上 PORT 號

產生憑證

因為在連線需要填入 sslOptions 所以要一張憑證,首先在此處官方提供的連結下載憑證:https://cacert.omniroot.com/bc2025.crt

然後把他移動到專案目錄下,並把檔案副檔名改為 cer

mkdir ./cert && mv ~/Downloads/bc2025.crt ./cert/bc2025.cer

之後執行程式: node uprofile.js

這時各位可能會產生 Error: error:0906D06C:PEM routines:PEM_read_bio:no start line 的錯誤,然後十分困惑。

不過不要緊,我們不要用官方的,自己來產生一張 X509 證書即可:

openssl req -newkey rsa:2048 -new -nodes -keyout key.pem -out csr.pem

輸入後填上相關資訊,也可以都按 Enter

然後從 pem 轉為 crt

openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out server.crt

最後再把 server.crt 改名字為 server.cer 即可

mv server.crt server.cer

之後執行範例程式:

node uprofile.js

這時會出現 { TypeError: Cannot read property 'type' of undefined 的錯誤,因為 Azure 目前的官方範例有點問題,在 batch 批量 Insert 的時候 params 多了一個參數。

這時把最後面的日期參數移除後再次執行:

沒想到又出現如下錯誤: { ResponseError: Logged batches are not supported by the service yet. Please use unlogged batches instead.

logged batches 代表該 batch 執行是原子性的,而 cosmos DB 目前不支援,只能用 unlogged batched。但是 cosmos DB 通常會有多個 partition 所以如果使用 unlogged batchedanti-pattern 的操作,可參考:https://stackoverflow.com/a/49471102 在 Azure 解決這個範例錯誤與 batch 的問題之前我們先將它改為一般的 Insert

範例:

我們接著不使用官方範例,把 uprofile.js 直接改為以下,自己來操作看看。

創建 DB(Keyspace):

const cassandra = require('cassandra-driver');
const tls = require("tls");
const fs = require("fs");

const config = require("./config");

const ssl_option = {
  cert: fs.readFileSync("./cert/server.cer"),
  secureProtocol: "TLSv1_2_method"
};

const authProviderLocalCassandra = 
    new cassandra.auth.PlainTextAuthProvider(config.username, config.password);
const client = new cassandra.Client({ 
  contactPoints: [config.contactPoint], 
  authProvider: authProviderLocalCassandra, 
  sslOptions:ssl_option
});

client.connect();

const query = "CREATE KEYSPACE IF NOT EXISTS Fruits WITH replication = {\'class\': \'NetworkTopologyStrategy\', \'datacenter\' : \'1\' }";

client.execute(query)
  .then(result => {
    console.log(result);
    client.shutdown();
  })
  .catch(err => console.log(err));

創建 TABLE

const cassandra = require("cassandra-driver");
const tls = require("tls");
const fs = require("fs");

const config = require("./config");

const ssl_option = {
  cert: fs.readFileSync("./cert/server.cer"),
  secureProtocol: "TLSv1_2_method"
};

const authProviderLocalCassandra = new cassandra.auth.PlainTextAuthProvider(
  config.username,
  config.password
);
const client = new cassandra.Client({
  contactPoints: [config.contactPoint],
  authProvider: authProviderLocalCassandra,
  sslOptions: ssl_option
});

client.connect()
  .then(function () {
    const query = "CREATE TABLE IF NOT EXISTS Fruits.banana " +
      "(_id int PRIMARY KEY, price int, name text)";
    return client.execute(query);
  })
  .catch(function (err) {
    console.error('There was an error', err);
    return client.shutdown();
  });

插入資料與查詢資料:

const cassandra = require("cassandra-driver");
const tls = require("tls");
const fs = require("fs");

const config = require("./config");

const ssl_option = {
  cert: fs.readFileSync("./cert/server.cer"),
  secureProtocol: "TLSv1_2_method"
};

const authProviderLocalCassandra = new cassandra.auth.PlainTextAuthProvider(
  config.username,
  config.password
);
const client = new cassandra.Client({
  contactPoints: [config.contactPoint],
  authProvider: authProviderLocalCassandra,
  sslOptions: ssl_option
});

client.connect()
  .then(function () {
    console.log('Inserting');
    const query = 'INSERT INTO Fruits.banana (_id, price, name) VALUES (?, ?, ?)';
    return client.execute(query, [1, 100, "Super banana"], { prepare: true});
  })
  .then(function () {
    const query = 'SELECT price, name FROM Fruits.banana WHERE _id = ?';
    return client.execute(query, [1], { prepare: true });
  })
  .then(function (result) {
    const row = result.first();
    console.log('Retrieved row: %j', row);
    return client.shutdown();
  })
  .catch(function (err) {
    console.error('There was an error', err);
    return client.shutdown();
  });

有時插入資料後按下 portal 頁面上的重新整理不會有反應,要把網頁重新整理一次才行。

那我們這篇介紹就到這邊為止,有興趣的朋友可以稍微玩一下。

Last updated