Azure Cosmos DB 建立與使用

什麼是非關聯式資料庫?

最近這幾年,非關聯式資料庫開始成為許多大型系統的架構考慮之一,原因有很多,但歸根究底核心原因我覺得是『全球化』。

過去企業(即便是大型的全球化企業)在建立資訊系統的時候,大部分都是以集中式為主的架構,也就是說,核心的資料庫只會有一份,頂多加上異地備援機制。

在傳統的企業營運需求上,這樣的設計完全沒問題,而且關聯式資料庫的特性就是ACID,有著結構清晰的資料表,經過正規化,存入的資料欄位都是固定的,這確保了資料的高可靠性與正確性,對企業應用系統來說,恰是所需。

但對於全球化的網際網路需求,可就不一定了。

當前的關聯式資料庫儲存資料有幾種類型,像是底下這樣:
圖片

你發現,這些資料的結構跟過去方方正正的資料表很是不同,現實生活中,有很多類似這樣的資料,例如:
圖片

若我們想表達一位用戶的朋友、資源、行事曆、檔案…等資訊,結構上就很可能不是傳統關聯式資料表所能表達的,這時候非關連式資料庫就開始派上用場。

除此之外,另外一個主因是 --『同步一致性』。

資料的同步問題

在企業內,資料的一致性似乎很重要,但一致性並非沒有代價,假設資料庫採分散式架構,在全球3-5個點都有資料庫,彼此之間進行同步抄寫。

這樣的架構底下,要維持一致性是很困難的,這也是大部分企業應用都採集中式架構的原因,但集中式架構最大的缺點是,如果總部斷線,就意味著全球各地區都別運作了。

但真實世界中,我們真的需要在全球有數個彼此同步抄寫的資料庫嗎? 過去沒有,但現在可能到處都是,最明顯的例子就是像FB之類的社群網站。

社群網站每秒中有數以萬計的留言對話,可能同時需要存入資料庫,如果採用傳統的關聯式資料庫,又是集中式設計,很可能根本無法運作,因為資料庫的loading太大了,光寫入資料就沒時間了,更何況還要承擔全球性的讀取。

所以非常有可能採用全球數個資料庫彼此抄寫的狀況,但這時候,『資料同步』將是個問題。

我鍵入一筆留言,在台灣的資料中心可以即時看到並回應,但在地球另一端的資料庫中卻可能還沒出現,因此我在國外的友人會隔一段時間才看到(需要等資料抄寫過去),然後他才能回應,同樣的當國外的友人回應,在台灣的我可能也不會立刻看到,而會延遲一段時間。

這就是分散式資料庫可能會碰到的問題。

這對於開發人員來說,是兩難。因為集中式效率會變低,分散式會導致資料的精確性或即時性降低

所幸,目前大部分需要分散式資料庫的場景中,對於精確性或即時性的要求並不高,這時NoSQL Database正好符合需求。

而Azure 上的Cosmos DB,正是這樣的資料庫。

當你建立好Cosmos DB後,可以設定多個自動抄寫的全球同步:
圖片

如此一來,你的資料庫可在一個地點寫入,多個區域讀出,以達到最佳的使用效能。而這仰賴高效的一致性抄寫,Cosmos DB具備五種內建的自動化一致性設定,也可以從後台進行設定:

透過Cosmos DB,我們可以在資訊系統設計時,同時兼顧效率與資訊的即時性,以因應全球化高併發流量的需求。

使用Cosmos DB

您可以透過Azure Portal建立Cosmic DB,我們先以SQL API為範例:
圖片

操作的過程可參考底下影片:

完成後,我們可以先建立資料庫與container(collection):

上面影片中,展示了如何建立一個ToDoList DB,並建立三筆以JSON為格式的資料庫,接著透過 SQL API進行搜尋。

你會發現,如果採用SQL API,資料庫的使用與搜尋,其實和過去傳統的關聯式資料庫很接近。

透過SDK操作Cosmos DB

我們當然也可以透過C# SDK,對 Cosmos DB 進行 database、record item的建立,並且透過程式碼呼叫SQL API進行查詢。

使用到的套件是 Microsoft.Azure.Cosmos,您可以在專案中透過 CLI 安裝:

dotnet add package Microsoft.Azure.Cosmos 

我在github上,撰寫了一個AZ-204課程中,給學員的範例程式碼,位於:
https://github.com/isdaviddong/az204-cosmicdemo.git

您可以透過 git clone 指令下載:

git clone https://github.com/isdaviddong/az204-cosmicdemo.git

你會看到該程式是 console app的形式,建立資料庫與Container的程式碼如下:

        private async Task CreateDatabaseAsync()
        {
            // Create a new database
            this.database = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(databaseId);
            Console.WriteLine("建立 Database: {0}\n", this.database.Id);
        }

        private async Task CreateContainerAsync()
        {
            // Create a new container
            this.container = await this.database.CreateContainerIfNotExistsAsync(containerId, "/LastName");
            Console.WriteLine("建立 Container: {0}\n", this.container.Id);
        }

底下這段程式新增JSON data record(個人通訊錄),每一筆資料錄都是透過呼叫網路上的假資料產生器 getFakeData() 建立的:

       private async Task InsertData()
        {
             Console.Write("\n請輸入要動態產生的資料筆數(ex. 30): ");
            var tmp=Console.ReadLine();
            var n=int.Parse(tmp);
            for (int i = 0; i < n; i++)
            {
                var person = getFakeData();
                var name = person.name.ToString().Split(" ");
                var lastName = name[name.Length - 1];
                var firstName = name[name.Length - 2];
                var rec = new DataRecord()
                {
                    id = Guid.NewGuid().ToString(),
                    FirstName = firstName,
                    LastName = lastName,
                    Address = person.address,
                    FullName = person.name.ToString()
                };
                var item = await this.container.CreateItemAsync<DataRecord>(rec, new PartitionKey(lastName));
                Console.WriteLine("Created item {0}: \n{1}\n", i, rec.FirstName );
            }
            Console.WriteLine($"\n{n} items has been added...");
        }

建立好資料之後,您可以從 cosmos db 的 explorer 中看到每一筆資料:
圖片

底下這段程式碼則是查詢的部分:

        private async Task QueryData()
        {
            Console.Write("\n請輸入查詢關鍵字(例如. ab): ");
            var key=Console.ReadLine();
            //查詢
            var sqlQueryText = "SELECT * FROM c WHERE CONTAINS(c.FullName,'"+key+"') ";
            Console.WriteLine("執行查詢: {0}\n", sqlQueryText);

            QueryDefinition queryDefinition = new QueryDefinition(sqlQueryText);
            FeedIterator<DataRecord> queryResultSetIterator =
            this.container.GetItemQueryIterator<DataRecord>(queryDefinition);

            while (queryResultSetIterator.HasMoreResults)
            {
                FeedResponse<DataRecord> currentResultSet = queryResultSetIterator.ReadNextAsync().Result;
                foreach (DataRecord DataRecord in currentResultSet)
                {
                    Console.WriteLine("\n符合的項目 : {0}", DataRecord.FullName);
                }
            }
        }

底下這段影片,展示了整個操作過程:

留言

這個網誌中的熱門文章

在POC或迷你專案中使用 LiteDB

使用Qdrant向量資料庫實作語意相似度比對

專業的價值...

使用 Airtable 在小型需求上取代傳統資料庫

讓 LINE Bot 對談機器人顯示 "Loading..." 動畫