本文深入探讨了Hazelcast 4.2.5版本中,当Replicatedmap配置为InMemoryFormat.BINARY并启用统计功能时,可能出现的ClassCastException。该异常源于Hazelcast内部在收集指标时,尝试将String类型的数据强制转换为内部的HeapData格式。教程将详细分析问题根源,并提供将ReplicatedMap泛型声明为Data的解决方案,同时讨论其对应用代码的影响及相关最佳实践。
Hazelcast ReplicatedMap与ClassCastException:深入理解InMemoryFormat.BINARY
在使用hazelcast的replicatedmap时,尤其当配置了inmemoryformat.binary并启用了统计功能 (setstatisticsenabled(true)) 时,可能会遇到Java.lang.classcastexception。该异常通常发生在hazelcast内部尝试收集replicatedmap的运行时指标时,日志中会显示类似以下内容:
java.lang.ClassCastException: class java.lang.String cannot be cast to class com.hazelcast.internal.serialization.impl.HeapData ... at com.hazelcast.replicatedmap.impl.ReplicatedMapService.provideDynamicMetrics(ReplicatedMapService.java:387) at com.hazelcast.replicatedmap.impl.ReplicatedMapService.getStats(ReplicatedMapService.java:357) at com.hazelcast.replicatedmap.impl.ReplicatedMapService.getLocalReplicatedMapStats(ReplicatedMapService.java:197) at com.hazelcast.replicatedmap.impl.LocalReplicatedMapStatsProvider.getLocalReplicatedMapStats(LocalReplicatedMapStatsProvider.java:85) ...
从堆栈信息可以看出,问题发生在LocalReplicatedMapStatsProvider.getLocalReplicatedMapStats方法内部,具体是在尝试获取内部数据记录的堆内存开销时。原始的Hazelcast配置示例如下:
import com.hazelcast.config.Config; import com.hazelcast.config.InMemoryFormat; import com.hazelcast.config.JoinConfig; import com.hazelcast.config.NetworkConfig; import com.hazelcast.config.ReplicatedMapConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.replicatedmap.ReplicatedMap; public class HazelcastConfigExample { private static HazelcastInstance setupHazelcastConfig() { Config config = new Config(); config.setInstanceName("rogueUsers"); NetworkConfig network = config.getNetworkConfig(); network.setPort(5701).setPortCount(20); network.setPortAutoIncrement(true); JoinConfig join = network.getJoin(); join.getMulticastConfig().setEnabled(true); HazelcastInstance hz = Hazelcast.getOrCreateHazelcastInstance(config); ReplicatedMapConfig replicatedMapConfig = config.getReplicatedMapConfig("rogueUsers"); // 关键配置:内存格式为BINARY,并启用统计 replicatedMapConfig.setInMemoryFormat(InMemoryFormat.BINARY); replicatedMapConfig.setAsyncFillup(true); replicatedMapConfig.setStatisticsEnabled(true); // 导致问题的根源之一 replicatedMapConfig.setSplitBrainProtectionName("splitbrainprotection-name"); // 问题所在:ReplicatedMap泛型声明为String, String ReplicatedMap<String, String> map = hz.getReplicatedMap("rogueUsers"); // map.addEntryListener(new RogueEntryListener()); // 假设存在监听器 return hz; } public static void main(String[] args) { HazelcastInstance instance = setupHazelcastConfig(); // 可以在这里进行map操作,例如: // ReplicatedMap<String, String> map = instance.getReplicatedMap("rogueUsers"); // map.put("user1", "statusA"); // System.out.println(map.get("user1")); } }
剖析问题根源:内部数据表示与统计
当ReplicatedMapConfig的inMemoryFormat设置为InMemoryFormat.BINARY时,Hazelcast会将存储在Map中的对象序列化成二进制形式,即com.hazelcast.internal.serialization.Data接口的实现类(例如HeapData)。这种格式有助于节省内存和优化网络传输。
ClassCastException的发生,是由于在LocalReplicatedMapStats类的第85行左右,Hazelcast内部在计算内存使用量时,期望获取的是HeapData类型的内部记录,但实际获取到的却是string类型,导致了强制类型转换失败。
// 简化后的内部代码逻辑,来自LocalReplicatedMapStats boolean isBinary = (replicatedMapConfig.getInMemoryFormat() == InMemoryFormat.BINARY); if (isBinary) { // 当 isBinary 为 true 时,期望 record.getValueInternal() 返回 HeapData memoryUsage += ((HeapData) record.getValueInternal()).getHeapcost(); // <-- 异常发生处 }
这意味着,尽管我们配置了InMemoryFormat.BINARY,且期望所有数据都以二进制Data的形式存储,但在某些情况下(可能与ReplicatedMap的特定实现或版本有关),当Map的泛型声明为ReplicatedMap
解决方案:调整ReplicatedMap的泛型声明
为了解决这个ClassCastException,一种有效的解决方案是显式地将ReplicatedMap的泛型类型声明为com.hazelcast.internal.serialization.Data。Data是Hazelcast内部序列化服务处理二进制形式对象的基本单元。
修改后的配置代码如下:
import com.hazelcast.config.Config; import com.hazelcast.config.InMemoryFormat; import com.hazelcast.config.JoinConfig; import com.hazelcast.config.NetworkConfig; import com.hazelcast.config.ReplicatedMapConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.replicatedmap.ReplicatedMap; import com.hazelcast.internal.serialization.Data; // 引入Data类 import com.hazelcast.spi.serialization.SerializationService; // 引入SerializationService public class HazelcastConfigFixedExample { private static HazelcastInstance setupHazelcastConfig() { Config config = new Config(); config.setInstanceName("rogueUsers"); NetworkConfig network = config.getNetworkConfig(); network.setPort(5701).setPortCount(20); network.setPortAutoIncrement(true); JoinConfig join = network.getJoin(); join.getMulticastConfig().setEnabled(true); HazelcastInstance hz = Hazelcast.getOrCreateHazelcastInstance(config); ReplicatedMapConfig replicatedMapConfig = config.getReplicatedMapConfig("rogueUsers"); replicatedMapConfig.setInMemoryFormat(InMemoryFormat.BINARY); replicatedMapConfig.set