东丽做网站,洛阳市做网站的,安徽网站制作公司,网站开发建站微信公众号小程序Hazelcast提供的核心数据结构之一是IMapK, V #xff0c;它扩展了java.util.concurrent.ConcurrentMap它基本上是一个分布式地图#xff0c;通常用作缓存。 您可以将此类地图配置为使用自定义MapLoaderK, V -每次尝试从该地图#xff08;通过键#xff09;… Hazelcast提供的核心数据结构之一是IMapK, V 它扩展了java.util.concurrent.ConcurrentMap它基本上是一个分布式地图通常用作缓存。 您可以将此类地图配置为使用自定义MapLoaderK, V -每次尝试从该地图通过键获取.get()尚不存在的Java代码时都会询问该Java代码。 当您将IMap用作内存中的分布式缓存时这特别有用–如果客户端代码要求尚未缓存的内容Hazelcast将透明地执行MapLoader.load(key) public interface MapLoaderK, V {V load(K key);MapK, V loadAll(CollectionK keys);SetK loadAllKeys();
} 其余两种方法在启动期间用于通过加载预定义的键集来预热缓存。 您的自定义MapLoader可以连接到否SQL数据库Web服务文件系统您命名。 使用这样的缓存要方便得多因为您不必执行繁琐的“ 如果不在缓存负载中并放入缓存 ”循环。 而且 MapLoader具有出色的功能-如果许多客户端同时从不同的线程甚至从不同的集群成员因此从机器要求相同的密钥则MapLoader仅执行一次。 这显着减少了外部依赖项上的负担而没有引入任何复杂性。 从本质上说IMap与MapLoader类似于LoadingCache中发现的番石榴 -但分布。 但是强大的功能会带来极大的挫败感尤其是当您不了解API的特殊性和分布式系统的固有复杂性时。 首先让我们看看如何配置自定义MapLoader 。 您可以hazelcast.xml使用hazelcast.xml map-store/元素但是随后就无法控制加载程序的生命周期例如您不能使用Spring bean。 一个更好的主意是直接从代码配置Hazelcast并传递MapLoader的实例 class HazelcastTest extends Specification {public static final int ANY_KEY 42public static final String ANY_VALUE Forty twodef should use custom loader() {given:MapLoader loaderMock Mock()loaderMock.load(ANY_KEY) ANY_VALUEdef hz build(loaderMock)IMapInteger, String emptyCache hz.getMap(cache)when:def value emptyCache.get(ANY_KEY)then:value ANY_VALUEcleanup:hz?.shutdown()} 请注意我们如何获得一个空的地图但是当要求输入ANY_KEY 我们得到ANY_VALUE作为回报。 这不足为奇这正是我们的loaderMock所期望的。 我离开了Hazelcast配置 def HazelcastInstance build(MapLoaderInteger, String loader) {final Config config new Config(Cluster)final MapConfig mapConfig config.getMapConfig(default)final MapStoreConfig mapStoreConfig new MapStoreConfig()mapStoreConfig.factoryImplementation {name, props - loader } as MapStoreFactorymapConfig.mapStoreConfig mapStoreConfigreturn Hazelcast.getOrCreateHazelcastInstance(config)
} 任何IMap 按名称标识都可以具有不同的配置。 但是特殊的default映射为所有映射指定默认配置。 让我们玩一下自定义加载器看看当MapLoader返回null或引发异常时它们的行为 def should return null when custom loader returns it() {given:MapLoader loaderMock Mock()def hz build(loaderMock)IMapInteger, String cache hz.getMap(cache)when:def value cache.get(ANY_KEY)then:value null!cache.containsKey(ANY_KEY)cleanup:hz?.shutdown()
}public static final String SOME_ERR_MSG Dont panic!def should propagate exceptions from loader() {given:MapLoader loaderMock Mock()loaderMock.load(ANY_KEY) {throw new UnsupportedOperationException(SOME_ERR_MSG)}def hz build(loaderMock)IMapInteger, String cache hz.getMap(cache)when:cache.get(ANY_KEY)then:UnsupportedOperationException e thrown()e.message.contains(SOME_ERR_MSG)cleanup:hz?.shutdown()
} 到目前为止不足为奇。 您可能遇到的第一个陷阱是线程在这里如何交互。 永远不会从客户端线程执行MapLoader 而总是从单独的线程池执行 def loader works in a different thread() {given:MapLoader loader Mock()loader.load(ANY_KEY) {key - $key: ${Thread.currentThread().name}}def hz build(loader)IMapInteger, String cache hz.getMap(cache)when:def value cache.get(ANY_KEY)then:value ! $ANY_KEY: ${Thread.currentThread().name}cleanup:hz?.shutdown()
} 该测试通过是因为当前线程是main线程而加载是从hz.Cluster.partition-operation.thread-10 。 这是一个重要的观察结果如果您记得当许多线程尝试访问相同的缺席密钥时加载程序仅被调用一次则这实际上很明显。 但是这里需要进一步说明。 IMap上的几乎每个操作都封装到一个操作对象中 另请参见 命令模式 。 此操作随后分派给一个或所有群集成员并在单独的线程池中甚至在另一台计算机上远程执行。 因此不要期望加载发生在同一线程甚至同一JVM /服务器中。 这会导致一种有趣的情况您在一台计算机上请求给定密钥而另一台计算机实际加载。 甚至更史诗般的–机器AB和C请求给定密钥而机器D实际加载该密钥的值。 基于一致的哈希算法确定哪个机器负责加载。 最后一句话–当然您可以自定义运行这些操作的线程池的大小请参阅“ 高级配置属性” 。 考虑到这一点这是完全令人惊讶的绝对可以期待 def IMap.remove() on non-existing key still calls loader (!)() {given:MapLoader loaderMock Mock()def hz build(loaderMock)IMapInteger, String emptyCache hz.getMap(cache)when:emptyCache.remove(ANY_KEY)then:1 * loaderMock.load(ANY_KEY)cleanup:hz?.shutdown()
} 仔细地看 我们要做的就是从地图上删除缺少的密钥。 没有其他的。 但是 loaderMock.load()已执行。 这是一个问题尤其是当您的自定义加载程序特别慢或昂贵时。 为什么在这里执行 查找java.util.Mapremove的API V remove(Object key) […] 返回此映射先前与该键相关联的值如果该映射不包含该键的映射关系则返回null。 也许这是有争议的但有人可能会认为Hazelcast做得正确。 如果您认为附有MapLoader的地图就像外部存储的视图一样那是有道理的。 当删除缺少的密钥时Hazelcast实际上会询问我们的MapLoader 以前的值是什么 它假装好像地图包含从MapLoader返回的每个单个值但延迟加载。 这不是错误因为有一个特殊的方法IMap.delete()就像remove()一样工作但是不会加载“先前”值 Issue(https://github.com/hazelcast/hazelcast/issues/3178)
def IMap.delete() doesnt call loader() {given:MapLoader loaderMock Mock()def hz build(loaderMock)IMapInteger, String cache hz.getMap(cache)when:cache.delete(ANY_KEY)then:0 * loaderMock.load(ANY_KEY)cleanup:hz?.shutdown()
} 实际上存在一个错误 IMap.delete()不应调用 3.2.6和3.3中修复的MapLoader.load() 。 如果尚未升级则即使IMap.delete()也将转到MapLoader 。 如果您认为IMap.remove()令人惊讶请查看put()工作原理 如果您认为remove()首先加载值是可疑的那么显式put()首先为给定键加载值怎么办 毕竟我们明确地通过键将某些内容放入地图为什么Hazelcast首先通过MapLoader加载此值 def IMap.put() on non-existing key still calls loader (!)() {given:MapLoader loaderMock Mock()def hz build(loaderMock)IMapInteger, String emptyCache hz.getMap(cache)when:emptyCache.put(ANY_KEY, ANY_VALUE)then:1 * loaderMock.load(ANY_KEY)cleanup:hz?.shutdown()
} 再次让我们还原到java.util.Map.put() JavaDoc V putK键V值 […] 返回值 与key关联的先前值如果没有key映射则为null。 Hazelcast假设IMap只是对某些外部源的懒惰视图因此当我们put()某些内容放到以前没有的IMap中时它会首先加载“ previous”值以便它可以返回它。 同样当MapLoader速度慢或价格昂贵时这又是一个大问题–如果我们可以明确地将某些内容放入地图中为什么要先加载它 幸运的是有一个简单的解决方法putTransient() def IMap.putTransient() doesnt call loader() {given:MapLoader loaderMock Mock()def hz build(loaderMock)IMapInteger, String cache hz.getMap(cache)when:cache.putTransient(ANY_KEY, ANY_VALUE, 1, TimeUnit.HOURS)then:0 * loaderMock.load(ANY_KEY)cleanup:hz?.shutdown()
} 一个警告是您必须显式提供TTL而不是依赖于已配置的IMap默认值。 但这还意味着您可以为每个映射条目分配任意TTL不仅可以全局分配给整个映射-很有用。 记住我们的比喻 IMap与后盾MapLoader行为就像在数据的外部源视图。 这就是为什么在空地图上的containsKey()会调用MapLoader并不令人惊讶的原因 def IMap.containsKey() calls loader() {given:MapLoader loaderMock Mock()def hz build(loaderMock)IMapInteger, String emptyMap hz.getMap(cache)when:emptyMap.containsKey(ANY_KEY)then:1 * loaderMock.load(ANY_KEY)cleanup:hz?.shutdown()
} 每当我们请求地图中不存在的键时Hazelcast都会询问MapLoader 。 同样只要装载机速度快无副作用且可靠这不是问题。 如果不是这种情况将会杀死您 def IMap.get() after IMap.containsKey() calls loader twice() {given:MapLoader loaderMock Mock()def hz build(loaderMock)IMapInteger, String cache hz.getMap(cache)when:cache.containsKey(ANY_KEY)cache.get(ANY_KEY)then:2 * loaderMock.load(ANY_KEY)cleanup:hz?.shutdown()
} 尽管containsKey()调用MapLoader 它不会“缓存”加载的值以供以后使用。 这就是为什么containsKey()后跟get()两次调用MapLoader 这非常浪费。 幸运的是如果您对现有密钥调用containsKey() 则它几乎立即运行尽管很可能需要网络跳转。 不幸的是Hazelcast 3.3版之前的keySet() values() entrySet()和其他一些方法的行为。 如果一次加载任何密钥这些将全部阻止。 因此如果您有一个包含数千个键的映射并要求提供keySet() 则一个缓慢的MapLoader.load()调用将阻塞整个群集。 幸运的是此问题已在3.3中修复因此即使当前正在计算某些键也不会阻塞IMap.keySet() IMap.values()等。 如您所见 IMap MapLoader组合功能强大但也充满陷阱。 其中一些由API规定osme由Hazelcast的分布式特性决定最后一些是特定于实现的。 在实施加载缓存功能之前请确保您了解它们。 翻译自: https://www.javacodegeeks.com/2014/09/hazelcasts-maploader-pitfalls.html