要高效地使用 Aerospike,必須深入理解其核心的資料操作 (CRUD) 流程,以及如何利用其豐富的資料型別和策略 (Policy) 來管理資料的完整生命週期。本文將引導您走過一筆記錄從「誕生」到「消亡」的全過程,並在其中詳細解析資料型別的應用、寫入策略的控制,以及如何實現安全的併發更新。

第一部分:資料的誕生 - 建立 (Create)

在 Aerospike 中建立一筆記錄,就是使用 put 方法將一或多個「Bin」寫入一個指定的「Key」。

1. 豐富的資料型別

Aerospike 的 Bin 支援多種資料型別,且客戶端驅動程式會自動進行轉換,實現語言無關的資料儲存。

資料型別描述與範例
Integer64 位元有符號整數。42, -12
Double64 位元浮點數。3.14159
StringUTF-8 編碼字串。"Hello, Aerospike!"
Booleantruefalse
List有序集合,可包含不同型別的元素。[1, "apple", true]
Map鍵值對集合,鍵必須是純量型別。{"name": "John", "age": 30}
Blob二進位原始位元組,可用於儲存序列化物件或圖片。
GeoJSON用於地理空間查詢的幾何物件。
HyperLogLog用於高效計算集合基數的機率性資料結構。

2. 寫入資料

Java 範例:建立一個包含多種資料型別的記錄

// 定義記錄的唯一 Key
Key key = new Key("test", "demo", "user123");

// 建立多個不同型別的 Bin
Bin nameBin = new Bin("name", "玄貓");
Bin ageBin = new Bin("age", 5);
Bin skillsBin = new Bin("skills", Arrays.asList("Python", "Aerospike")); // List
Map<String, Object> metadataMap = new HashMap<>();
metadataMap.put("isActive", true);
Bin metadataBin = new Bin("metadata", metadataMap); // Map

// 使用 put 方法寫入
client.put(null, key, nameBin, ageBin, skillsBin, metadataBin);

第二部分:資料的檢索 - 讀取 (Read)

使用 get 方法可以根據 Key 讀取記錄。為了提升效能,我們可以只讀取需要的 Bins。

Java 範例:只讀取 nameskills 兩個 Bin

// 第二個之後的參數為要讀取的 Bin 名稱列表
Record record = client.get(null, key, "name", "skills");

String name = record.getString("name");
List<Object> skills = (List<Object>) record.getList("skills");

System.out.println("Name: " + name);   // -> Name: 玄貓
System.out.println("Skills: " + skills); // -> Skills: [Python, Aerospike]

第三部分:資料的演變 - 更新 (Update)

Aerospike 的 put 操作預設行為是「更新或插入」(Upsert),即如果記錄存在,則更新指定的 Bins;如果不存在,則建立新記錄。然而,在多執行緒環境下,直接的「讀取-修改-寫入」操作可能導致競爭條件 (Race Condition)。

安全更新:檢查並設定 (Check-And-Set, CAS)

為了解決這個問題,Aerospike 提供了基於 generation (世代) 計數的 CAS 機制。

  1. 讀取: 首先 get 記錄,並獲取其中繼資料中的 generation 值。
  2. 寫入: 在執行 put 操作時,設定 WritePolicy,要求伺服器只有在資料庫中記錄的 generation 與我們提供的值相同時,才執行寫入。如果在此期間有其他執行緒修改了該記錄,其 generation 會增加,導致我們的寫入操作失敗。

Java 範例:安全地為 age 加 1

// 1. 讀取記錄及其 generation
Record record = client.get(null, key);
int currentGeneration = record.generation;

// 2. 在客戶端計算新值
int newAge = record.getInt("age") + 1;

// 3. 設定 CAS 寫入策略
WritePolicy casPolicy = new WritePolicy();
casPolicy.generationPolicy = GenerationPolicy.EXPECT_GEN_EQUAL;
casPolicy.generation = currentGeneration;

// 4. 執行帶有 CAS 策略的寫入
try {
    client.put(casPolicy, key, new Bin("age", newAge));
    System.out.println("年齡更新成功!");
} catch (AerospikeException e) {
    if (e.getResultCode() == ResultCode.GENERATION_ERROR) {
        System.out.println("寫入失敗:資料已被其他程序修改,請重試。");
    }
}

圖表解說:CAS 安全更新流程

此循序圖展示了如何利用 generation 值來防止更新衝突。

@startuml
!theme _none_
skinparam dpi auto
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam minClassWidth 100
skinparam defaultFontSize 16
title CAS (Check-And-Set) 安全更新流程

participant "Client" as C
participant "Aerospike Server" as S

C -> S : get(key)
S --> C : Record (..., generation=5)

C -> C : 計算新值 (age = age + 1)

C -> S : put(key, new_age, policy(EXPECT_GEN_EQUAL, generation=5))
S -> S : 比較客戶端 generation (5)\n與伺服器端 generation (5)
alt generation 相符
    S -> S : 執行寫入,並將 generation 更新為 6
    S --> C : 回應成功
else generation 不符 (例如已被更新為 6)
    S --> C : 回應失敗 (GENERATION_ERROR)
end
@enduml

第四部分:資料的消亡 - TTL 與刪除

  • TTL (Time-To-Live): 我們可以在寫入時透過 WritePolicy 設定 expiration 欄位,讓記錄在指定的秒數後自動被伺服器刪除。這對於管理快取或有時效性的資料非常有用。
    WritePolicy ttlPolicy = new WritePolicy();
    ttlPolicy.expiration = 3600; // 記錄將在 1 小時後過期
    client.put(ttlPolicy, key, ...);
    
  • 主動刪除: 也可以使用 client.delete(null, key) 來立即刪除一筆記錄。

透過掌握資料的完整生命週期操作,並善用 Aerospike 提供的策略控制,開發者可以建構出既高效又穩健的應用程式。