
- Hazelcast 教程
- Hazelcast - 首頁
- Hazelcast - 簡介
- Hazelcast - 設定
- Hazelcast - 第一個應用程式
- Hazelcast - 配置
- 設定多節點例項
- Hazelcast - 資料結構
- Hazelcast - 客戶端
- Hazelcast - 序列化
- Hazelcast 高階
- Hazelcast - Spring 整合
- Hazelcast - 監控
- Map Reduce & 聚合
- Hazelcast - 集合監聽器
- 常見陷阱 & 效能技巧
- Hazelcast 有用資源
- Hazelcast - 快速指南
- Hazelcast - 有用資源
- Hazelcast - 討論
Hazelcast - IMap
java.util.concurrent.Map 提供了一個介面,支援在單個 JVM 中儲存鍵值對。而 java.util.concurrent.ConcurrentMap 擴充套件了它,以支援在單個 JVM 中使用多個執行緒時的執行緒安全。
類似地,IMap 擴充套件了 ConcurrentHashMap 並提供了一個介面,使對映在多個 JVM 之間執行緒安全。它提供類似的功能:put、get 等。
IMap 支援同步備份和非同步備份。同步備份確保即使儲存佇列的 JVM 宕機,所有元素都將被保留並可從備份中獲取。
讓我們來看一個有用函式的例子。
建立 & 讀/寫
新增元素和讀取元素。讓我們在兩個 JVM 上執行以下程式碼。一個 JVM 上執行生產者程式碼,另一個 JVM 上執行消費者程式碼。
示例
第一部分是生產者程式碼,它建立了一個對映並將專案新增到其中。
public static void main(String... args) throws IOException, InterruptedException { //initialize hazelcast instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); // create a map IMap<String, String> hzStock = hazelcast.getMap("stock"); hzStock.put("Mango", "4"); hzStock.put("Apple", "1"); hzStock.put("Banana", "7"); hzStock.put("Watermelon", "10"); Thread.sleep(5000); System.exit(0); }
第二部分是消費者程式碼,它讀取元素。
public static void main(String... args) throws IOException, InterruptedException { //initialize hazelcast instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); // create a map IMap<String, String> hzStock = hazelcast.getMap("stock"); for(Map.Entry<String, String> entry: hzStock.entrySet()){ System.out.println(entry.getKey() + ":" + entry.getValue()); } Thread.sleep(5000); System.exit(0); }
輸出
消費者程式碼的輸出 -
Mango:4 Apple:1 Banana:7 Watermelon:10
有用方法
序號 | 函式名稱 & 描述 |
---|---|
1 | put(K key, V value) 向對映中新增一個元素 |
2 | remove(K key) 從對映中刪除一個元素 |
3 | keySet() 返回對映中所有鍵的副本 |
4 | localKeySet() 返回本地分割槽中存在的所有鍵的副本 |
5 | values() 返回對映中所有值的副本 |
6 | size() 返回對映中元素的數量 |
7 | containsKey(K key) 如果鍵存在則返回 true |
8 | executeOnEnteries(EntryProcessor processor) 對對映的所有鍵應用處理器並返回此應用的輸出。我們將在下一節中檢視一個示例。 |
9 | addEntryListener(EntryListener listener, value) 在對映中新增/刪除/修改元素時通知訂閱者。 |
10 | addLocalEntryListener(EntryListener listener, value) 在本地分割槽中新增/刪除/修改元素時通知訂閱者 |
逐出
預設情況下,Hazelcast 中的鍵會無限期地保留在 IMap 中。如果我們有一組非常大的鍵,那麼我們需要確保與不常使用的鍵相比,經常使用的鍵儲存在 IMap 中,以獲得更好的效能和有效的記憶體使用。
為此,可以透過 remove()/evict() 函式手動刪除不常使用的鍵。但是,Hazelcast 還提供基於各種逐出演算法的鍵自動逐出。
可以透過 XML 或以程式設計方式設定此策略。讓我們來看一個例子 -
<map name="stock"> <max-size policy="FREE_HEAP_PERCENTAGE">30</max-size> <eviction-policy>LFU</eviction-policy> </map>
上述配置中有兩個屬性。
Max-size - 用於告知 Hazelcast 對映“stock”達到最大大小的限制的策略。
Eviction-policy - 一旦達到上述 max-size 策略,使用什麼演算法來刪除/逐出鍵。
以下是一些有用的 max_size 策略。
序號 | 最大大小策略 & 描述 |
---|---|
1 | PER_NODE 每個 JVM 對映的最大條目數,這是預設策略。 |
2 | FREE_HEAP 在 JVM 中保留的最小空閒堆記憶體(以 MB 為單位) |
3 | FREE_HEAP_PERCENTAGE 在 JVM 中保留的最小空閒堆記憶體(以百分比表示) |
4 | take() 返回佇列的頭或等待元素可用 |
5 | USED_HEAP JVM 中允許使用的最大堆記憶體(以 MB 為單位) |
6 | USED_HEAP_PERCENTAGE JVM 中允許使用的最大堆記憶體(以百分比表示) |
以下是一些有用的逐出策略 -
序號 | 逐出策略 & 描述 |
---|---|
1 | NONE 不會進行任何逐出,這是預設策略 |
2 | LFU 最不常使用將被逐出 |
3 | LRU 最近最少使用的鍵將被逐出 |
另一個有用的逐出引數是生存時間(秒),即 TTL。有了它,我們可以要求 Hazelcast 刪除任何超過 X 秒的鍵。這確保了我們在達到最大大小策略之前主動刪除舊鍵。
分割槽資料和高可用性
關於 IMap 需要注意的一點是,與其他集合不同,資料在多個 JVM 之間進行分割槽。所有資料都不需要儲存/存在於單個 JVM 上。所有 JVM 仍然可以訪問完整的資料。這使 Hazelcast 能夠線上性擴充套件到可用的 JVM,並且不受單個 JVM 記憶體的限制。
IMap 例項被分成多個分割槽。預設情況下,對映被分成 271 個分割槽。這些分割槽分佈在可用的 Hazelcast 成員中。新增到對映中的每個條目都儲存在一個分割槽中。
讓我們在兩個 JVM 上執行此程式碼。
public static void main(String... args) throws IOException, InterruptedException { //initialize hazelcast instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); // create a map IMap<String, String> hzStock = hazelcast.getMap("stock"); hzStock.put("Mango", "4"); hzStock.put("Apple", "1"); hzStock.put("Banana", "7"); hzStock.put("Watermelon", "10"); Thread.sleep(5000); // print the keys which are local to these instance hzStock.localKeySet().forEach(System.out::println); System.exit(0); }
輸出
如以下輸出所示,消費者 1 列印它自己的包含 2 個鍵的分割槽 -
Mango Watermelon
消費者 2 擁有包含其他 2 個鍵的分割槽 -
Banana Apple
預設情況下,IMap 有一個同步備份,這意味著即使一個節點/成員宕機,資料也不會丟失。備份有兩種型別。
同步 - 在鍵也在另一個節點/成員上備份之前,map.put(key, value) 不會成功。同步備份是阻塞的,因此會影響 put 呼叫的效能。
非同步 - 儲存鍵的備份最終會執行。非同步備份是非阻塞且快速的,但如果成員宕機,它們不能保證資料的永續性。
可以使用 XML 配置來配置值。例如,讓我們為我們的 stock 對映執行此操作 -
<map name="stock"> <backup-count>1</backup-count> <async-backup-count>1<async-backup-count> </map>
雜湊碼和相等
示例
在基於 Java 的 HashMap 中,鍵比較透過檢查 hashCode() 和 equals() 方法的相等性來進行。例如,車輛可能有序列號和型號以使其簡單。
public class Vehicle implements Serializable{ private static final long serialVersionUID = 1L; private int serialId; private String model; public Vehicle(int serialId, String model) { super(); this.serialId = serialId; this.model = model; } public int getId() { return serialId; } public String getModel() { return model; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + serialId; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Vehicle other = (Vehicle) obj; if (serialId != other.serialId) return false; return true; } }
當我們嘗試將上述類用作 HashMap 和 IMap 的鍵時,我們會看到比較中的差異。
public static void main(String... args) throws IOException, InterruptedException { // create a Java based hash map Map<Vehicle, String> vehicleOwner = new HashMap<>(); Vehicle v1 = new Vehicle(123, "Honda"); vehicleOwner.put(v1, "John"); Vehicle v2 = new Vehicle(123, null); System.out.println(vehicleOwner.containsKey(v2)); // create a hazelcast map HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); IMap<Vehicle, String> hzVehicleOwner = hazelcast.getMap("owner"); hzVehicleOwner.put(v1, "John"); System.out.println(hzVehicleOwner.containsKey(v2)); System.exit(0); }
現在,為什麼 Hazelcast 會給出 false 作為答案?
Hazelcast 序列化鍵並將其以二進位制格式儲存為位元組陣列。由於這些鍵被序列化,因此無法基於 equals() 和 hashcode() 進行比較。
在 Hazelcast 的情況下需要序列化和反序列化,因為 get()、containsKey() 等函式可能在不擁有鍵的節點上呼叫,因此需要遠端呼叫。
序列化和反序列化是昂貴的操作,因此,Hazelcast 不使用 equals() 方法,而是比較位元組陣列。
這意味著 Vehicle 類的所有屬性都必須匹配,而不僅僅是 id。因此,讓我們執行以下程式碼 -
示例
public static void main(String... args) throws IOException, InterruptedException { Vehicle v1 = new Vehicle(123, "Honda"); // create a hazelcast map HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); IMap<Vehicle, String> hzVehicleOwner = hazelcast.getMap("owner"); Vehicle v3 = new Vehicle(123, "Honda"); System.out.println(hzVehicleOwner.containsKey(v3)); System.exit(0); }
輸出
上述程式碼的輸出為 -
true
此輸出表示 Vehicle 的所有屬性都必須匹配才能相等。
EntryProcessor
EntryProcessor 是一種構造,支援將程式碼傳送到資料而不是將資料帶到程式碼。它支援序列化、傳輸和在擁有 IMap 鍵的節點上執行函式,而不是將資料帶到啟動函式執行的節點。
示例
讓我們用一個例子來理解這一點。假設我們建立了一個 Vehicle -> Owner 的 IMap。現在,我們想儲存所有者的“小寫”。那麼我們該如何做到這一點呢?
public static void main(String... args) throws IOException, InterruptedException { // create a hazelcast map HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); IMap<Vehicle, String> hzVehicleOwner = hazelcast.getMap("owner"); hzVehicleOwner.put(new Vehicle(123, "Honda"), "John"); hzVehicleOwner.put(new Vehicle(23, "Hyundai"), "Betty"); hzVehicleOwner.put(new Vehicle(103, "Mercedes"), "Jane"); for(Map.Entry<Vehicle, String> entry: hzVehicleOwner.entrySet()) hzVehicleOwner.put(entry.getKey(), entry.getValue().toLowerCase()); for(Map.Entry<Vehicle, String> entry: hzVehicleOwner.entrySet()) System.out.println(entry.getValue()); System.exit(0); }
輸出
上述程式碼的輸出為 -
john jane betty
雖然此程式碼看起來很簡單,但如果鍵的數量很大,它在可擴充套件性方面存在主要缺點 -
處理將在單個/呼叫者節點上發生,而不是分佈在各個節點上。
需要更多時間和記憶體才能在呼叫者節點上獲取鍵資訊。
這就是 EntryProcessor 發揮作用的地方。我們將轉換為小寫的函式傳送到每個擁有鍵的節點。這使得處理並行化並控制記憶體需求。
示例
public static void main(String... args) throws IOException, InterruptedException { // create a hazelcast map HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); IMap<Vehicle, String> hzVehicleOwner = hazelcast.getMap("owner"); hzVehicleOwner.put(new Vehicle(123, "Honda"), "John"); hzVehicleOwner.put(new Vehicle(23, "Hyundai"), "Betty"); hzVehicleOwner.put(new Vehicle(103, "Mercedes"), "Jane"); hzVehicleOwner.executeOnEntries(new OwnerToLowerCaseEntryProcessor()); for(Map.Entry<Vehicle, String> entry: hzVehicleOwner.entrySet()) System.out.println(entry.getValue()); System.exit(0); } static class OwnerToLowerCaseEntryProcessor extends AbstractEntryProcessor<Vehicle, String> { @Override public Object process(Map.Entry<Vehicle, String> entry) { String ownerName = entry.getValue(); entry.setValue(ownerName.toLowerCase()); return null; } }
輸出
上述程式碼的輸出為 -
john jane betty