为什么Hazelcast相对于数据库的性能较差?

为什么Hazelcast相对于数据库的性能较差?,hazelcast,Hazelcast,我有一个集群,我经常使用几个节点运行,我对解决一些性能问题感兴趣。这可能是我所做的是正确的,但我不能完全肯定,可以使用一些专家的指导。该项目的目标是将数据库数据卸载到hazelcast地图中,以提供更具可扩展性和性能的访问 假设集群中有三个节点,容器映射中有30000个条目大致均匀地分布在集群中。为了解决这个问题,假设这样一个简单的结构,其中包含现有的getter、setter、constructor等: class Container { int id; Set<Inte

我有一个集群,我经常使用几个节点运行,我对解决一些性能问题感兴趣。这可能是我所做的是正确的,但我不能完全肯定,可以使用一些专家的指导。该项目的目标是将数据库数据卸载到hazelcast地图中,以提供更具可扩展性和性能的访问

假设集群中有三个节点,容器映射中有30000个条目大致均匀地分布在集群中。为了解决这个问题,假设这样一个简单的结构,其中包含现有的getter、setter、constructor等:

class Container {
    int id;
    Set<Integer> dataItems;
}

class Data {
    int id;
    String value;
}
类容器{
int-id;
设置数据项;
}
类数据{
int-id;
字符串值;
}
两个映射的映射配置如下所示:

<map name="Container">
    <in-memory-format>OBJECT</in-memory-format>
    <backup-count>1</backup-count>
    <async-backup-count>0</async-backup-count>
    <time-to-live-seconds>0</time-to-live-seconds>
    <max-idle-seconds>259200</max-idle-seconds>
    <eviction-policy>LRU</eviction-policy>
    <max-size policy="PER_NODE">0</max-size>
    <eviction-percentage>25</eviction-percentage>
    <merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
</map>
public List<Map<String, Object>> jsonMap(final Set<Integer> keys) {
    final IMap<Integer, Container> cmap = hazelcast.getMap("Containers");
    final IMap<Integer, Data> dmap = hazelcast.getMap("Data");

    final List<Map<String, Object>> result = new ArrayList<>();
    cmap.getAll(keys).values().stream().forEach((c) -> {
        final Map<String, Object> cJson = new HashMap<>();
        result.add(cJson);
        cJson.put("containerId", c.id);
        final List<Map<String, Object>> dataList = new ArrayList<>();
        cJson.put("data", dataList);
        dmap.getAll(c.dataItems).values().stream().forEach(d -> {
            final Map<String, Object> dJson = new HashMap<>();
            dataList.add(dJson);
            dJson.put("id", d.id);
            dJson.put("value", d.value);
        });
    });
    return result;
}

对象
1.
0
0
259200
LRU
0
25
com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
正如你所见,这张地图有一个大的驱逐时间,但被大量使用。由于数据经历了大量的写流量以及更大的读流量,我认为近缓存可能不会完全有帮助,因为失效很快。如果这是一个本地数据集,那么标准的迭代策略如下所示:

<map name="Container">
    <in-memory-format>OBJECT</in-memory-format>
    <backup-count>1</backup-count>
    <async-backup-count>0</async-backup-count>
    <time-to-live-seconds>0</time-to-live-seconds>
    <max-idle-seconds>259200</max-idle-seconds>
    <eviction-policy>LRU</eviction-policy>
    <max-size policy="PER_NODE">0</max-size>
    <eviction-percentage>25</eviction-percentage>
    <merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
</map>
public List<Map<String, Object>> jsonMap(final Set<Integer> keys) {
    final IMap<Integer, Container> cmap = hazelcast.getMap("Containers");
    final IMap<Integer, Data> dmap = hazelcast.getMap("Data");

    final List<Map<String, Object>> result = new ArrayList<>();
    cmap.getAll(keys).values().stream().forEach((c) -> {
        final Map<String, Object> cJson = new HashMap<>();
        result.add(cJson);
        cJson.put("containerId", c.id);
        final List<Map<String, Object>> dataList = new ArrayList<>();
        cJson.put("data", dataList);
        dmap.getAll(c.dataItems).values().stream().forEach(d -> {
            final Map<String, Object> dJson = new HashMap<>();
            dataList.add(dJson);
            dJson.put("id", d.id);
            dJson.put("value", d.value);
        });
    });
    return result;
}
公共列表jsonMap(最终设置键){
最终IMap cmap=hazelcast.getMap(“容器”);
最终IMap dmap=hazelcast.getMap(“数据”);
最终列表结果=新建ArrayList();
cmap.getAll(keys).values().stream().forEach((c)->{
最终映射cJson=新HashMap();
结果。添加(cJson);
cJson.put(“集装箱”,c.id);
最终列表数据列表=新的ArrayList();
cJson.put(“数据”,数据列表);
dmap.getAll(c.dataItems).values().stream().forEach(d->{
final Map dJson=new HashMap();
dataList.add(dJson);
dJson.put(“id”,d.id);
dJson.put(“价值”,d.value);
});
});
返回结果;
}
正如您所看到的,这里有一个简单的迭代来创建JSON表示。但是,由于数据分散在节点上,我们发现这在性能上非常缓慢。比直接从数据库获取数据慢一个数量级。这让一些人质疑使用hazelcast的策略。作为一个解决方案,我建议重新设计系统,使用一个通过执行回调创建的完全未来

public <K, R> CompletableFuture<R> submitToKeyOwner(final K key, final String executor, final Callable<R> callable) {
    final CompletableFuture<R> future = new CompletableFuture<>();
    hazelcast.getExecutorService(executor).submitToKeyOwner((Callable<R> & Serializable) callable, key, new FutureExecutionCallback<>(future));
    return future;
}

public class FutureExecutionCallback<R> implements ExecutionCallback<R> {
    private final CompletableFuture<R> future;

    public FutureExecutionCallback(final CompletableFuture<R> future) {
        this.future = future;
    }

    @Override
    public void onResponse(final R response) {
        future.complete(response);
    }

    @Override
    public void onFailure(final Throwable t) {
        future.completeExceptionally(t);
    }
}

public List<Map<String, Object>> jsonMap2(final Set<Integer> keys) {
    final List<Map<String, Object>> result = new ArrayList<>();

    keys.stream().forEach(k -> {
        result.add(submitToKeyOwner(k, (Callable<Map<String, Object>> & Serializable) () -> {
            final IMap<Integer, Container> cmap = hazelcast.getMap("Containers");
            final Container c = cmap.get(k);
            final Map<String, Object> cJson = new HashMap<>();
            cJson.put("containerId", c.id);
            final List<Map<String, Object>> dataList = new ArrayList<>();
            cJson.put("data", dataList);
            c.dataItems.stream().map((dk) ->
                    dataList.add(submitToKeyOwner(dk, (Callable<Map<String, Object>> & Serializable) () -> {
                        final IMap<Integer, Data> dmap = hazelcast.getMap("Data");
                        final Data d = dmap.get(dk);
                        final Map<String, Object> dJson = new HashMap<>();
                        dJson.put("id", d.id);
                        dJson.put("value", d.value);
                        return dJson;
                    }).join()));
            return cJson;
        }).join());
    });

    return result;
}
public CompletableFuture submitToKeyOwner(最终K键、最终字符串执行器、最终可调用){
最终CompletableFuture=新的CompletableFuture();
getExecutorService(executor).submitToKeyOwner((可调用和序列化)可调用,键,新未来执行回调(未来));
回归未来;
}
公共类FutureExecutionCallback实现ExecutionCallback{
私人最终可完成的未来;
公共未来执行回调(最终可完成未来){
this.future=未来;
}
@凌驾
公共无效回复(最终回复){
未来。完成(回应);
}
@凌驾
失效时公共失效(最终可丢弃t){
未来。完全例外(t);
}
}
公共列表jsonMap2(最终设置键){
最终列表结果=新建ArrayList();
keys.stream().forEach(k->{
结果.添加(submitToKeyOwner(k,(可调用和可序列化)(->{
最终IMap cmap=hazelcast.getMap(“容器”);
最终容器c=cmap.get(k);
最终映射cJson=新HashMap();
cJson.put(“集装箱”,c.id);
最终列表数据列表=新的ArrayList();
cJson.put(“数据”,数据列表);
c、 dataItems.stream().map((dk)->
dataList.add(submitToKeyOwner(dk,(可调用和可序列化)(->{
最终IMap dmap=hazelcast.getMap(“数据”);
最终数据d=dmap.get(dk);
final Map dJson=new HashMap();
dJson.put(“id”,d.id);
dJson.put(“价值”,d.value);
返回dJson;
}).join());
返回cJson;
}).join());
});
返回结果;
}
从本质上说,我已经将所有内容都转移到submitToKey中,并使用Completable futures来完成所有内容。逻辑是,对象的获取将仅在本地存储该对象的节点上运行。尽管这是可行的,但它仍然比直接访问数据库慢,因为我们正在访问数百条记录,而一个数据库Hibernate调用将在纳秒内带来这些记录,而这条记录是以几十毫秒为单位测量的。在某些方面,这似乎违反直觉。我认为对缓存的访问应该比实际速度快得多。也许我在迭代的实现中或者仅仅是在一个通用的范例中做了一些错误的事情。入口处理器不是一个选项,因为尽管我发布了一个简单的示例,但真实的示例在其过程中也使用了其他映射,入口处理器有严重的限制。使用map-reduce是不合适的,因为作业的管理开销已被证明比这两种方法中的任何一种都要昂贵

我的问题是,这些是否都是正确的范例,以及我是否应该预期几十毫秒或几百毫秒的延迟?这仅仅是在集群容错世界中开展业务的成本,还是我可以做些什么来缩短时间?最后,在以这种方式访问数据时,是否有更好的范例可供使用


非常感谢您的时间。

这不会解决您的问题,但值得一提的是,
二进制
通常比
对象
产生更好的性能(使用
对象
map.get()
添加了序列化步骤)

从:

正规的