5

我已经实现了一个 hazelcast 服务,它通过 MapStoreFactory 和 newMapLoader 将其数据存储到本地 mapdb 实例中。如果需要重新启动集群,则可以通过这种方式加载密钥:

public class HCMapStore<V> implements MapStore<String, V> {

Map<String, V> map;

/** specify the mapdb e.g. via 
  * DBMaker.newFileDB(new File("mapdb")).closeOnJvmShutdown().make() 
  */
public HCMapStore(DB db) {
    this.db = db;
    this.map = db.createHashMap("someMapName").<String, Object>makeOrGet();
}

// some other store methods are omitted
@Override
public void delete(String k) {
    logger.info("delete, " + k);
    map.remove(k);
    db.commit();
}

// MapLoader methods
@Override
public V load(String key) {
    logger.info("load, " + key);
    return map.get(key);
}

@Override
public Set<String> loadAllKeys() {
    logger.info("loadAllKeys");
    return map.keySet();
}

@Override
public Map<String, V> loadAll(Collection<String> keys) {
    logger.info("loadAll, " + keys);
    Map<String, V> partialMap = new HashMap<>();
    for (String k : keys) {
        partialMap.put(k, map.get(k));
    }
    return partialMap;
}}

我现在面临的问题是来自 hazelcast 的 MapLoader 接口的 loadAllKeys 方法需要返回整个集群的所有键,但每个节点只存储它拥有的对象。

示例:我有两个节点并存储了 8 个对象,那么例如 5 个对象存储在 node1 的 mapdb 中,3 个对象存储在 node2 的 mapdb 中。我认为哪个对象由哪个节点拥有由 hazelcast 决定。现在重新启动时,node1 将为 loadAllKeys 返回 5 个键,node2 将返回 3 个。Hazelcast 决定忽略这 3 个项目,数据“丢失”。

有什么好的解决办法呢?

赏金更新在这里,我在 hc 邮件列表中提出了这个问题,提到了 2 个选项(我会再添加 1 个),我想知道 hazelcast 3.2 或 3.3 是否已经可以实现这样的事情:

  1. 目前 MapStore 接口仅从本地节点获取数据或更新。是否可以将整个集群的每个存储操作都通知 MapStore 接口?或者也许这已经可以通过一些听众魔法来实现?也许我可以强制 hazelcast 将所有对象放入一个分区并在每个节点上拥有 1 个副本。

  2. 如果我重新启动例如 2 个节点,则 MapStore 接口会被我的本地数据库正确调用,用于 node1,然后用于 node2。但是当两个节点都加入时,node2 的数据将被删除,因为 Hazelcast 假设只有主节点是正确的。我可以教 hazelcast 接受来自两个节点的数据吗?

4

4 回答 4

2

根据 Hazelcast 3.3 文档,MapLoader 初始化流程如下:

当第一次从任何节点调用 getMap() 时,初始化将根据 InitialLoadMode 的值开始。如果设置为 EAGER,则初始化开始。如果设置为 LAZY,则实际上不会开始初始化,而是在每次分区加载完成时加载数据。

  1. Hazelcast 将调用 MapLoader.loadAllKeys() 来获取每个节点上的所有密钥
  2. 每个节点都会计算出它拥有的密钥列表
  3. 每个节点将通过调用 MapLoader.loadAll(keys) 加载其所有拥有的密钥
  4. 每个节点通过调用 IMap.putTransient(key,value) 将其拥有的条目放入映射中

以上暗示如果节点以不同的顺序启动,那么密钥也会以不同的方式分布。因此,每个节点都不会在其本地存储中找到所有/部分分配的键。您应该能够通过在 HCMapStore.loadAllKeys 和 HCMapStore.loadAll 中设置断点来验证它,并将您检索到的键与该键进行比较。

在我看来,您试图实现的目标与具有弹性特性(如 Hazelcast)的分布式缓存的概念相矛盾,因此是不可能的。即,当一个节点消失(无论出于何种原因出现故障或断开连接)时,集群将通过移动部分数据来重新平衡,每次节点加入集群时都会发生相同的过程。因此,在集群发生变化的情况下,丢失节点的本地后备存储将变得过时。

Hazelcast 集群本质上是动态的,因此它不能依赖具有静态分布式拓扑的后台存储。本质上,您需要有一个共享的后备库才能使其与动态 hazelcast 集群一起使用。backstore 也可以是分布式的,例如 cassandra,但它的拓扑必须独立于缓存集群拓扑。

更新:在我看来,您要实现的目标是以具有本地缓存​​的分布式数据存储(在 MapDB 之上)的形式更合乎逻辑。

我希望这有帮助。

于 2014-09-09T09:28:18.027 回答
1

可以加载存储在所有节点上的数据,但目前您必须手动完成。

在每个节点上:

HCMapStore store = createMapDbStore();
HazelcastInstance hz = createHz( store ); // use store in MapStoreConfig as implementation
IMap imap = hz.getMap("map"); 
Map diskMap = store.loadAll( store.loadAllKeys() ); // load all entries on disk
imap.putAll( diskMap ); // put into distributed map

但正如邮件列表中提到的那样,MapStore并不是真的打算以这种方式使用。另请注意,备份不会以这种方式保存到磁盘。因此,如果您重新启动集群并且一个节点上的磁盘死了,这些条目将会丢失。

于 2014-09-11T12:10:26.817 回答
1

也许有两个选择:

1) 深入了解 Hazelcast 中的分区是如何工作的。我认为可以有办法让每个分区都有 MapLoader,并强制节点只加载自己的分区,这样可以解决冲突。

2)当节点重新上线时,在添加节点之前与 Hazelcast 集群进行交互。您可以合并两组,一组来自 HZ,第二组来自 MapDB。

3) 强制 Hazelcast 将所有数据存储在每个节点上。将分区号设置为 1 或其他

于 2014-09-09T08:08:58.990 回答
0

这似乎不容易

Hazelcast 的持久层要求它是某种中央存储。像数据库或共享文件。

或看这里这里。将调查使用 Hazelcast 并保存到光盘的 OrientDB。

于 2014-09-02T14:54:38.747 回答