Hazelcast的MapLoader陷阱
Hazelcast提供的核心数据结构之一是IMap<K, V>
,它扩展了java.util.concurrent.ConcurrentMap
它基本上是一个分布式地图,通常用作缓存。 您可以将此类地图配置为使用自定义MapLoader<K, V>
-每次尝试从该地图(通过键)获取.get()
尚不存在的Java代码时,都会询问该Java代码。 当您将IMap
用作内存中的分布式缓存时,这特别有用–如果客户端代码要求尚未缓存的内容,Hazelcast将透明地执行MapLoader.load(key)
:
public interface MapLoader<K, V> {V load(K key);Map<K, V> loadAll(Collection<K> keys);Set<K> 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 two"def 'should use custom loader'() {given:MapLoader loaderMock = Mock()loaderMock.load(ANY_KEY) >> ANY_VALUEdef hz = build(loaderMock)IMap<Integer, 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(MapLoader<Integer, 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)IMap<Integer, 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 = "Don't 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)IMap<Integer, 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)IMap<Integer, 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 /服务器(!)中。
这会导致一种有趣的情况,您在一台计算机上请求给定密钥,而另一台计算机实际加载。 甚至更史诗般的–机器A,B和C请求给定密钥,而机器D实际加载该密钥的值。 基于一致的哈希算法确定哪个机器负责加载。
最后一句话–当然,您可以自定义运行这些操作的线程池的大小,请参阅“ 高级配置属性” 。
考虑到这一点,这是完全令人惊讶的,绝对可以期待:
def 'IMap.remove() on non-existing key still calls loader (!)'() {given:MapLoader loaderMock = Mock()def hz = build(loaderMock)IMap<Integer, String> emptyCache = hz.getMap("cache")when:emptyCache.remove(ANY_KEY)then:1 * loaderMock.load(ANY_KEY)cleanup:hz?.shutdown()
}
仔细地看! 我们要做的就是从地图上删除缺少的密钥。 没有其他的。 但是, loaderMock.load()
已执行。 这是一个问题,尤其是当您的自定义加载程序特别慢或昂贵时。 为什么在这里执行? 查找`java.util.Map#remove()的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() doesn't call loader"() {given:MapLoader loaderMock = Mock()def hz = build(loaderMock)IMap<Integer, 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)IMap<Integer, 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 put(K键,V值)
[…]
返回值:
与key关联的先前值;如果没有key映射,则为null。
Hazelcast假设IMap
只是对某些外部源的懒惰视图,因此当我们put()
某些内容放到以前没有的IMap
中时,它会首先加载“ previous”值,以便它可以返回它。 同样,当MapLoader
速度慢或价格昂贵时,这又是一个大问题–如果我们可以明确地将某些内容放入地图中,为什么要先加载它? 幸运的是,有一个简单的解决方法putTransient()
:
def "IMap.putTransient() doesn't call loader"() {given:MapLoader loaderMock = Mock()def hz = build(loaderMock)IMap<Integer, 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)IMap<Integer, 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)IMap<Integer, 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
Hazelcast的MapLoader陷阱相关推荐
- hazelcast_Hazelcast的MapLoader陷阱
hazelcast Hazelcast提供的核心数据结构之一是IMap<K, V> ,它扩展了java.util.concurrent.ConcurrentMap ,它基本上是一个分布式地 ...
- IMDG中的陷阱和问题
陷阱 使用cache API时,一个最重要的问题就是潜在的数据加载.因为IMDG提供的分布式集合也都是实现的JDK的Map.Set等接口,以JDK的Map为例,它接口规定put和remove返回被替换 ...
- Hazelcast IMDG参考中文版手册-第七章-分布式数据结构
如概述部分所述,Hazelcast提供Java接口的分布式实现.以下是这些实现的列表,其中包含指向本手册中相应部分的链接. 标准实用程序集合 Map是分布式实现的java.util.Map.它可以让你 ...
- Hazelcast与MongoDB集成
Hazelcast与MongoDB集成 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs 一.Hazelcast与Mong ...
- Golang 要注意的陷阱和常见错误
原文: 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs 翻译: Go的50度灰:新Golang开发者要 ...
- typedef的四个用途和两大陷阱
typedef的四个用途和两个陷阱 --------------------------------- 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如: c ...
- JS中8个常见的陷阱
译者按: 漫漫编程路,总有一些坑让你泪流满面. 原文: Who said javascript was easy ? 译者: Fundebug 为了保证可读性,本文采用意译而非直译.另外,本文版权归原 ...
- 跨越企业的“中等收入陷阱”
在国际经济学中,有一个"中等收入陷阱"的概念,含义为:新兴市场国家突破人均GDP1000美元的"贫困陷阱"后,很快会奔向1000美元至3000美元的" ...
- 《C陷阱与缺陷》一导读
前 言 C陷阱与缺陷 对于经验丰富的行家而言,得心应手的工具在初学时的困难程度往往要超过那些容易上手的工具.刚刚接触飞机驾驶的学员,初航时总是谨小慎微,只敢沿着海岸线来回飞行,等他们稍有经验就会明白这 ...
最新文章
- matlab 显示3d频谱_matlab 关于频谱分析程序集锦
- 虚拟机ubuntu安装ssh服务器,经过Xshell远程链接虚拟机VMVARE中的Ubuntu
- python 局域网 主机名_使用python获取连接到本地网络(基于主机名)的所有设备的ip...
- python中代理模式分为几种_Python设计模式之代理模式实例详解
- SQL语句中=null和is null
- java做的web系统 m1 读卡器 结合_IE浏览器接入IC卡读写器实现M1卡的读写功能
- python是什么语言-终于明白python语言的特点是什么
- 如何把多个PDF页面合并成一页PDF - PDF页面合并器使用方法
- 一个完整的机器学习模型的流程
- 制造业MES系统数字化转型
- JS手写面试题 --- 数组扁平化
- ABAQUS关联验证全部pass,但是cmd运行abaqus info=system找不到Fortran compiler ,Abaqus/Standard with user subroutine
- AntV X6流程图绘制程序(官方示例纯javascript+html+css)
- 友盟push java_友盟U-Push推送与获取状态
- .h文件和.cpp文件组织结构
- 中国12家云平台实际进展情况,看完这篇你就全部了解了!
- 环境混合物总体效应:加权分位数和回归(WQS)
- McDSP APB 调音台插件:Moo X Mixer 数模混合工作方式的展现
- 中国动力锂电池报废市场现状调研及投资潜力预测报告2022年版
- nodejs 下载必应中国的壁纸