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仅执行一次。 这显着减少了外部依赖项上的负担,而没有引入任何复杂性。

从本质上说IMapMapLoader类似于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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362231.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Android开发从零开始》——22.数据存储(1)

本节课的主要内容有&#xff1a;1.介绍Android中数据存储方式2.介绍SQLite3.讲解SQLite数据类型4.讲解基本SQL命令 课程下载地址&#xff1a;http://u.115.com/file/clc4te8w课件及源码下载地址&#xff1a;http://u.115.com/file/clc41tt5转载于:https://www.cnblogs.com/cool…

计划Java EE 7批处理作业

Java EE 7添加了使用JSR 352以标准方式执行批处理作业的功能。 <job id"myJob" xmlns"http://xmlns.jcp.org/xml/ns/javaee" version"1.0"><step id"myStep"><chunk item-count"3"><reader ref"…

webpack配置说明

webpack是一个现代JavaScript应用程序的静态模块打包器。 它有几个核心概念&#xff1a; 一、entry&#xff08;入口&#xff09; 指示webpack应该使用哪个模块&#xff0c;来作为构建其内部依赖图的开始&#xff0c; 可以在webpack.config.js中配置entry属性&#xff0c;来指…

poj 2137

题目大意&#xff1a; 农夫FJ想把他所有的牛在它们吃草的时候用一根绳子连起来&#xff08;就是1-2-3-1按顺序连接起来&#xff09;&#xff0c;每头牛有若干个吃草的位置&#xff0c;它们必须要在这些位置吃草。 用绳子连接两头牛需要绳子的长度公式为 Sqrt( Sqr( x1-x2 ) Sq…

JS中捉摸不透的==(宽松等于)

首先来看一个有意思的面试题&#xff1a; if(a 3 && a 4){//... }第一眼看到这个面试题我是拒绝的&#xff0c;这个等式根本不会成立&#xff0c;怎么会存在一个值既等于3并且还同时等于4呢&#xff1f;根本不可能。 但是在神奇的javascript中这个a是存在的。&…

vue脚手架的使用

安装&#xff1a;1.全局安装脚手架&#xff1a;cnpm install -g vue/cli 使用&#xff1a;2.新建文件夹,在当前目录执行命令 vue create "项目名称"3.配置&#xff1a;选择Manually select feautures--》空格选择Babel和CSS Pre-procesors--》选择Sass/SCSS(with dar…

Akka笔记–演员介绍

过去做过多线程的任何人都不会否认管理多线程应用程序有多么艰辛和痛苦。 我说管理是因为它一开始很简单&#xff0c;一旦您开始看到性能改进&#xff0c;它就会变得非常有趣。 但是&#xff0c;当您发现没有一种简单的方法可以从子任务中的错误或难以发现的僵尸错误中恢复时&a…

浏览器的同源策略与跨域

本文所有案例在本地址都可找到&#xff1a;https://github.com/dancingZhou/sameOrigin/tree/dev 什么是同源策略 两个页面地址中的协议、域名和端口号一致&#xff0c;则表示同源。 例如该地址 https://www.google.com 和以下地址对比 地址同源原因http://www.google.com否…

poj 1185

经典状态dp 代码&#xff1a; #include<iostream> #include<fstream> #include<cmath>using namespace std;int n,m;char map[101][11];int state[101][1024]; int num[101]; int value[1024]; int maxx;int ok(int s,int t){int i,j,k;for(i0;i<m;){jt&…

day03 爬虫

今日内容&#xff1a;一 爬虫原理二 Requests请求库一 爬虫原理1.什么是互联网&#xff1f;指的是由一堆网络设备&#xff0c;把一台台的计算机互联网到一起称之为互联网。2.互联网建立的目的&#xff1f;互联网建立的目的是为了数据的传递以及数据的共享。3.什么是数据&#x…

Java英雄:丹·艾伦

“ Java英雄 ”系列休息了很长时间。 老实说&#xff0c;我想即使有很多人想在这里收录&#xff0c;它也可能会以虚无收场。 其中之一是丹。 我第一次要求他捐款已经将近一年半了&#xff0c;与此同时发生的一切&#xff0c;让我不再有任何答案就让我安心了。 但是以下内容在Ja…

yearProgress.vue

1 <template>2 <div class"progressbar">3 <el-progress :text-inside"true" :soke-width"18" :percentage"percent" status"exception"></el-progress>4 <p>{{year}}年已经过去了…

group by rollup

首先引用ITPUB上的总结&#xff1a; rollup(a,b,c)----------------> 从右到底递减汇总>group by a,b,c (减0次)UNION ALL>group by a,b (减1次)UNION ALL>group by a (减2次)UNION ALL>group by null(全部汇总) (全部减掉)移动了4次&#xff0c;所…

Java-Class-I:java.util.List

ylbtech-Java-Class-I&#xff1a;java.util.List1.返回顶部 1.1、import java.util.ArrayList;import java.util.List; 1.2、List<Integer> newList new ArrayList<Integer>();newList.add(3); 2、 2.返回顶部1.1、import java.util.*;public class Test{public …

JS中编码的三种方法

在开发中经常需要对用户输入的数据进行编码然后才能通过HTTP请求发送给后台&#xff0c;或者对传递过来的数据进行解码。在JS中原生提供了三种编码/解码方式&#xff0c;分别是 encodeURI、 encodeURIComponent和 escape。 为什么URL需要编码&#xff1f; URI设计要求可移植&…

一个类加载的谜团解决了

面对一个好老问题 我在应用程序服务器上遇到一些类加载问题。 这些库被定义为Maven依赖项&#xff0c;因此被打包到WAR和EAR文件中。 不幸的是&#xff0c;其中一些也已安装到应用程序服务器中&#xff0c;但版本不同。 启动应用程序时&#xff0c;我们遇到了与这些类型的问题相…

vue 隐藏滚动条

element-ui隐藏组件scrollbar&#xff1a; <el-scrollbar style"height:100%"> </el-scrollbar>真正的隐藏滚动条代码在这里&#xff1a;.el-scrollbar__thumb {display: none;}.el-scrollbar__wrap {overflow-x: hidden;overflow-y: auto;}更多专业前端…

希望菜鸟通过博客园的记录和学习,成为一个可以能把自己想发实现的小程序员!...

我是一个学习电气自动化专业的毕业生&#xff0c;工作多年&#xff0c;接触过c语言、vb、单片机、PLC、linux&#xff0c;希望菜鸟通过博客园的记录和学习&#xff0c;成为一个可以能把自己想发实现的小程序员&#xff01; 生活和工作中有许多自己的表格和统计数据&#xff0c;…

获取DOM元素方法小结

在开发中不可避免的需要操作DOM&#xff0c;现在就来总结一下原生的获取DOM的API。 getElementById() 该方法是最常用的通过元素的id属性来获取DOM元素的API&#xff0c;返回一个DOM元素。 <body><div id"div">我是div</div><script type&qu…

推荐:个人时间跟踪工具 ManicTime

在《个人管理 &#xff0d; 目标管理之前&#xff0c;你会时间管理吗》中我介绍的时间管理三阶段之一“对时间的实际去处进行记录”时说过现在有很多时间管理工具&#xff0c;也有人希望我介绍一下我使用的工具&#xff0c;那么我就利用中午休息时间&#xff0c;马上给大家介绍…