Hazelcast - IMap

java.util.concurrent.Map提供了一个接口,支持在单个JVM中存储键值对。 而 java.util.concurrent.ConcurrentMap 对此进行了扩展,以支持具有多个线程的单个 JVM 中的线程安全。

类似地,IMap 扩展了 ConcurrentHashMap 并提供了一个接口,使映射线程能够跨 JVM 安全。 它提供类似的功能:put、get 等。

IMap支持同步备份和异步备份。 同步备份可确保即使保存队列的 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

有用的方法

Sr.No 函数名称 & 描述
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 传达我们声明的映射"库存"最大尺寸已达到的限制的策略。

  • Eviction-policy − 一旦达到上述最大大小策略,使用什么算法来删除/逐出密钥。

以下是一些有用的 max_size 策略。

Sr.No 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 中允许使用的最大堆内存(百分比)

这里有一些有用的驱逐政策 −

Sr.No 驱逐政策和描述
1

NONE

默认政策不会驱逐

2

LFU

最不常用的将被驱逐

3

LRU

最近最少使用的密钥将被驱逐

驱逐的另一个有用参数也是生存时间秒,即 TTL。 有了这个,我们可以要求 Hazelcast 删除任何早于 X 秒的密钥。 这确保了我们在达到最大大小策略之前主动删除旧密钥。

分区数据和高可用性

关于 IMap 需要注意的一个要点是,与其他集合不同,数据是跨 JVM 进行分区的。 所有数据不需要存储/存在于单个 JVM 上。 所有 JVM 仍然可以访问完整的数据。 这为 Hazelcast 提供了一种跨可用 JVM 线性扩展的方法,并且不受单个 JVM 内存的限制。

IMap 实例被分为多个分区。 默认情况下,映射分为 271 个分区。 这些分区分布在可用的 Hazelcast 成员中。 添加到映射中的每个条目都存储在单个分区中。

让我们在 2 个 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 配置来配置该值。 例如,我们对缺货映射进行操作 −

<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() 等函数可能会在不拥有 key 的节点上调用,因此需要远程调用。

序列化和反序列化是昂贵的操作,因此 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 键的节点上序列化、传输和执行函数,而不是将数据引入到发起函数执行的节点。

示例

让我们通过一个例子来理解这一点。 假设我们创建了一个车辆 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.html