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
hazelcast_data_structures.htm
廣告