标签归档:hazelcast

Hazelcast 配置教程

在 入门及使用案例一文介绍了什么是Hazelcast,并展示了一个简单的使用例子。原理大家都懂了,后面的篇章会给兄弟们更多干货。

本篇博文将细说如何配置Hazelcast,听我慢慢给你们侃。

XML基本配置

如果用户没有指定或提供任何配置文件,Hazelcast默认会使用jar包中自带的配置文件——"hazelcast-default.xml"来配置Hazelcast的运行环境。Hazelcast默认采用XML格式作为配置文件,当然也支持其他配置方法,后文会详细说明。我们先看看下面这个简单的配置文件例子。

<hazelcast xsi:schemaLocation="//" xmlns="//" xmlns:xsi="">
    <group>
        <name>dev</name>
        <password>dev-pass</password>
    </group>
    <management-center enabled="false">http://localhost:8080/mancenter</management-center>
    <network>
		<port auto-increment="true" port-count="110">7701</port>
		<outbound-ports>
			<ports>0</ports>
		</outbound-ports>
        <join>
            <multicast enabled="true">
                <multicast-group>224.2.2.3</multicast-group>
                <multicast-port>54327</multicast-port>
            </multicast>
            <tcp-ip enabled="false">
                <interface>127.0.0.1</interface>
                <member-list>
                    <member>127.0.0.1</member>
                </member-list>
            </tcp-ip>
        </join>
	</network>
	<map name="demo.config">
		<backup-count>1</backup-count>
		<time-to-live-seconds>0</time-to-live-seconds>
		<max-idle-seconds>0</max-idle-seconds>
		<eviction-policy>NONE</eviction-policy>
		<max-size policy="PER_NODE">0</max-size>
		<eviction-percentage>25</eviction-percentage>
		<merge-policy>com.hazelcast.map.merge.LatestUpdateMapMergePolicy
		</merge-policy>
	</map>
</hazelcast>

如果你看到上面的配置内容有点蒙圈,建议你先看看上一篇Hazelcast基础介绍的文章。

前面已经介绍,Hazelcast以分布式的方式实现了Java中的绝大部分数据结构,这些数据结构的数据都以分区表的方式存储,因此可以推断XML配置文件中的<map></map>元素就是用来配置分布式map的相关参数的,这里先不细说每个参数的定义,从字面上看,大概就是配置map的备份副本个数、释放策略、释放比率等等。有了<map></map>当然还有<queue></queue><set></set><list></list>等针对各种数据结构的配置元素。

<network></network>是非常重要的元素,他指定了Hazelcast的网络环境。上面这个简短的配置文件例子指定网络使用5700到5800端口,使用组播协议来进行组网。

我们在创建Hazelcast集群时可以引入配置文件。下面的代码例子展示了如何引入自定义的配置文件。(文中所有例子的源码均在github:https://github.com/chkui/hazelcast-demo。使用“git clone”到本地用maven就可以运行。

/** https://github.com/chkui/hazelcast-demo/blob/master/src/main/java/org/palm/hazelcast/config/HazelcastConfigSimple.java */
public class HazelcastConfigSimple {
	public static void main(String[] args) {
		// 从classpath加载配置文件
		Config config = new ClasspathXmlConfig("xmlconfig/simple-config.xml");
		// 获取网络配置
		NetworkConfig netConfig = config.getNetworkConfig();
		// 获取用户定义的map配置
		MapConfig mapConfigXml = config.getMapConfig("demo.config");
		// 获取系统默认的map配置
		MapConfig mapConfigDefault = config.getMapConfig("default");
		// 输出集群监听的起始端口号
		System.out.println("Current port:" + netConfig.getPort());
		// 输出监听端口的累加号
		System.out.println("Current port count:" + netConfig.getPortCount());
		// 输出自定义map的备份副本个数
		System.out.println("Config map backup count:" + mapConfigXml.getBackupCount());
		// 输出默认map的备份副本个数
		System.out.println("Default map backup count:" + mapConfigDefault.getBackupCount());

		// 测试创建Hazelcast实例并读写测试数据
		HazelcastInstance instance1 = Hazelcast.newHazelcastInstance(config);
		HazelcastInstance instance2 = Hazelcast.newHazelcastInstance(config);

		Map<Integer, String> defaultMap1 = instance1.getMap("defaultMap");
		defaultMap1.put(1, "testMap");
		Map<Integer, String> configMap1 = instance1.getMap("configMap");
		configMap1.put(1, "configMap");

		Map<Integer, String> testMap2 = instance2.getMap("defaultMap");
		System.out.println("Default map value:" + testMap2.get(1));
		Map<Integer, String> configMap2 = instance2.getMap("configMap");
		System.out.println("Config map value:" + configMap2.get(1));
	}
}

上面的例子使用ClasspathXmlConfig来生成Config实例,它表示从classpath路径来加载配置文件。 从上面的代码例子还可以看出,我们能够从Config实例中读取各种各样的配置信息,例如网络配置、Map配置等等。既然能get,当然也可以set,在Hazelcast没有初始化之前,都可以随意设置各种配置属性。下面的例子展示了如何在代码中修改Hazelcast的配置参数。

/** https://github.com/chkui/hazelcast-demo/blob/master/src/main/java/org/palm/hazelcast/config/HazelcastConfigRuntimeModify.java */
public class HazelcastConfigRuntimeModify {
	public static void main(String[] args) {
		// 创建默认config对象
		Config config = new Config();
		
		// 获取network元素<network></network>
		NetworkConfig netConfig = config.getNetworkConfig();
		System.out.println("Default port:" + netConfig.getPort());
		
		// 设置组网起始监听端口
		netConfig.setPort(9701);
		System.out.println("Customer port:" + netConfig.getPort());
		// 获取join元素<join></join>
		JoinConfig joinConfig = netConfig.getJoin();
		// 获取multicast元素<multicast></multicast>
		MulticastConfig multicastConfig = joinConfig.getMulticastConfig();
		// 输出组播协议端口
		System.out.println(multicastConfig.getMulticastPort());
		// 禁用multicast协议
		multicastConfig.setEnabled(false);
		
		// 初始化Hazelcast
		Hazelcast.newHazelcastInstance(config);
	}
}

上面的例子中,我们首先从Config中获取了NetworkConfig实例,然后调用NetworkConfig::setPort方法将集群的监听起始端口设置为9701(默认为5701)。运行以上代码会输出以下片段内容:

class:com.hazelcast.instance.DefaultAddressPicker
info: [LOCAL] [dev] [3.6.3] Picked Address[192.168.1.100]:9701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=9701], bind any local is true

XML配置与源码配置

看到这兄弟可能要问了:“又是XML配置,又是代码级配置的,他两到底啥关系呢?”。其实他两是相辅相成的,既可以只用XML配置、也可以只在代码中进行配置、还可以两者混合使用——先加载XML配置再对其进行修改以满足各种需要。

一个简单的例子

我们先看一个简单的例子,再深入了解Hazelcast实现XML到Java对象映射的原理。

<!-- XML配置 -->
<hazelcast>
	<network>
		<join>
			<multicast enabled="true">
				<multicast-group>224.2.2.3</multicast-group>
				<multicast-port>54327</multicast-port>
			</multicast>
		</join>
	</network>
</hazelcast>
// 代码读取数据
Config config = new Config();
NetworkConfig networkConfig = config.getNetworkConfig();
JoinConfig joinConfig = networkConfig .getJoin();
MulticastConfig multicastConfig = joinConfig.getMulticastConfig();
int multicastPort = multicastConfig.getMulticastPort();

在上面这个XML配置和代码的例子中,<hazelcast></hazelcast>对应JavaConfig对象,而<hazelcast>中包含<network></network>,因此Config::getNetwork方法可以获取NetworkConfig对象的实例。继续往下,<network>中包含<join></join>,因此NetworkConfig::getJoin可以得到JoinConfig。因为<join>包含<multicast></multicast>,所以JoinConfig::getMulticastConfig可以得到MulticastConfig

看到这里应该都明白了吧:就是每个XML元素对应一个Java实体或数据,只要按照XML配置文件的树形关系来调用get或set,就可以在源码中获取和设置所有配置数据。

XML和源码配置的映射关系

友情提示:如果仅仅是想了解如何使用Hazelcast,建议直接跳过这一段。对XML定义、DTD、XSD不了解的话看多了反而容易混乱。

前文已经提到Hazelcast的配置文件已经预定义了所有要使用的 参数(对应XMLElementAttribuet),定义文件是hazelcast-<version>.jar包中的hazelcast-3.*.xsd(目前是3.6版本)。XSD文件中所有 类型(XSDType)不为 预定义类型(xs:booleanxs:unsignedInt 等)的 元素(XSD:Element)映射到Java中都对应一个 实体(EntityPojo)。如果 元素 中还包含 类型 不为预定义类型的 元素,则对应到Java数据结构时 实体 中还包含另外一个 实体。若XSD文件中定义的 元素类型 为 预定义类型,则对应一个Java基本数据值(intString等)。

例如下面这些XSD文件片段:

<xs:element name="network" type="network" minOccurs="0" maxOccurs="1"/>

<xs:element name="join" type="join" minOccurs="0" maxOccurs="1"/>

<xs:element name="multicast" type="multicast" minOccurs="0"/>

<xs:element name="multicast-port" type="xs:unsignedShort" minOccurs="0" maxOccurs="1" default="54327">

<network></network>元素在xsd文件中定义的类型为network,因此他是一个名为NetworkConfig的实体。XML文件中在<network>元素内还有一个<join></join>元素,<join>元素的type为join,因此调用NetworkConfig::getJoin方法可以得到一个JoinConfig实例。以此类推,<join>内的<multicast></multicast>元素也是一个名为MulticastConfig的实体,而<multicast>中的<multicast-port></multicast-port>对应一个Java的基本数据值——int,因为它在XSD中的类型为xs:unsignedShort

如果使用的XML配置文件中出现了XSD文件中没有定义的元素和属性,在解析过程中会抛出meaningful异常。

Hazelcast配置文件详解

前面通过几个例子介绍了Hazelcast如何配置,后面的篇幅将会逐一介绍Hazelcast所有配置细节及其参数定义。如果某位仁兄现在已经需要将Hazelcast引入到现在的项目中,建议您仔细阅读。

加载配置文件

当调用Hazelcast.newHazelcastInstance()Hazelcast.newHazelcastInstance(null)时,Hazelcast会从指定的路径加载XML配置文件或者加载默认配置文件。执行过程如下。

首先,可以通过系统配置参数(system property)指定XML配置文件的加载路径。Hazelcast将在创建实例时检查是否设置了"hazelcast.config"这个启动参数并引用。可以通过Jvm 参数或 System参数来指定它:

#!/bin/sh
java -Dhazelcast.config=/user/my_hazelcast_config.xml ....

// Java
System.setProperty( "hazelcast.config", "/user/my_hazelcast_config.xml" );

其次,如果没有设置这个参数或者指定路径的文件不存在,Hazelcast会搜寻当前classpath路径检查是否存在一个名为“hazelcast.xml”,有则使用。

最后,如果通过以上2个步骤都没有加载到配置文件,则使用jar包中的“hazelcast-default.xml”。

在编码中加载配置文件

除了上面指定系统参数的方法,还可以通过编码实现加载配置文件。Hazelcast提供了多种初始化配置文件的方法,主要有:ClasspathXmlConfigFileSystemXmlConfigUrlXmlConfigInMemoryXmlConfigXmlConfigBuilder

  1. ClasspathXmlConfig:从classpath路径加载配置文件。通常情况下,除了Java的运行环境路径,classpath的根目录可以认为是classes文件夹。因此如果一个文件存放于....../target/classes/xmlconfig/simple-config.xml。那么Config cfg = new ClasspathXmlConfig("xmlconfig/simple-config.xml")即可加载该配置文件。
  2. FileSystemXmlConfig:从文件系统加载配置文件。文件系统是指从操作系统的文件路径加载文件,因此如果文件存放在 linux:/user/local/hazelcast/hazelcast.xml windows:D:\local\hazelcast\hazelcast.xml。那么使用new FileSystemXmlConfig("/user/local/hazelcast/hazelcast.xml") new FileSystemXmlConfig("D:\\local\\hazelcast\\hazelcast.xml") 即可获取配置文件。
  3. UrlXmlConfig:从网络地址获取配置文件。
  4. InMemoryXmlConfig:从内存中的xml字符串生成配置文件。
  5. XmlConfigBuilder:InputStream流中读取配置文件。使用Config cfg = new XmlConfigBuilder(inputStream).build()可以创建一个Config实例。

得到Config实例之后使用 HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(config)可以创建HazelcastInstance实例。

Config中使用Config::setInstanceName方法可以设置实例名称。此后使用这个名称可以获取同一个HazelcastInstance 实例。例如:

//Java
// 创建配置
Config cfg = new XmlConfigBuilder(inputStream).build();
// 设置实例名称
config.setInstanceName("my-instance");
// 创建Hazelcast实例
Hazelcast.newHazelcastInstance(cfg);
// 获取已创建的实例
Hazelcast.getHazelcastInstanceByName("my-instance");

在配置文件中使用通配符

在XML配置文件中,可以使用通配符*来匹配某些元素的名称。例如像下面这样配置一个分布式Map的名称:

<map name="map.*">
...
</map>

在使用时,下面的方法都是获得同一个Map

Map map1 = hazelcastInstance.getMap("map.1");

Map map2 = hazelcastInstance.getMap("map.2");

Map map3 = hazelcastInstance.getMap("map.3");

在配置文件中使用变量

Hazelcast提供了使用变量来配置XML中元素值的方法,通过在配置文件中使用${}来指定变量要替换的参数。

首先,可以通过系统参数来设置Hazelcast参数。例如像下面这样设置变量:

-Dgroup.name=dev 
-Dgroup.password=somepassword

System.setProperty( "group.name", "demo" );
System.setProperty( "group.password", "passwd" );

可以在XML配置文件中可以像下面这样设置${}:

<hazelcast>
  <group>
    <name>${group.name}</name>
    <password>${group.password}</password>
  </group>
</hazelcast>

在创建配置文件时,${}会被变量替换。

其次,可以通过XML中的<properties></properties>元素配置参数。如下:

<hazelcast>
	<properties>
		<property name="group.name">dev</property>
		<property name="group.passwd">devpasswd</property>
	</properties>
    <group>
		<name>${group.name}</name>
		<password>${group.passwd}</password>
	</group>
</hazelcast>

引入配置文件后,会将properties中的变量替换到对应的${}中。

最后,还可以通过标准的properties文件来配置参数。如下面示例代码:

/** https://github.com/chkui/hazelcast-demo/blob/master/src/main/java/org/palm/hazelcast/config/HazelcastConfigVariable.java */
public class HazelcastConfigVariable {
	// XML配置文件存放路径
	final static String DEF_CONFIG_FILE = "xmlconfig/variable-config.xml";
	// properties文件路径
	final static String DEF_PROPERTIES_FILE = "properties/variable-config.properties";
	public static void main(String[] args) {
		try {
			// 获取配置文件磁盘路径
			final String path = Thread.currentThread().getContextClassLoader().getResource("").toString() + DEF_CONFIG_FILE;
			// 构建XML配置
			XmlConfigBuilder builder = new XmlConfigBuilder(path);
			// 设置properties
			builder.setProperties(getProperties());
			// 创建Config,此时会替换${}
			Config config = builder.build();
			// 输出Config参数
			System.out.println(config.getGroupConfig().getName());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	// get Properties
	private static Properties getProperties() {
		Properties p = null;
		try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(DEF_PROPERTIES_FILE)) {
			if (null != in) {
				p = new Properties();
				p.load(in);
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
		return p;
	}
}

代码对应的XML配置文件:

<!-- https://github.com/chkui/hazelcast-demo/blob/master/src/main/resources/xmlconfig/variable-config.xml -->
<hazelcast>
	<group>
		<name>${group.name}</name>
		<password>${group.password}</password>
	</group>
	<network>
		<port auto-increment="true" port-count="100">5701</port>
		<join>
			<multicast enabled="true">
				<multicast-group>224.2.2.3</multicast-group>
				<multicast-port>54327</multicast-port>
			</multicast>
		</join>
	</network>
</hazelcast>

对应properties文件:

#https://github.com/chkui/hazelcast-demo/blob/master/src/main/resources/properties/variable-config.properties

group.name=demo
group.password=demopasswd

上面的代码先创建了一个XmlConfigBuilder实例,然后调用XmlConfigBuilder::setProperties方法设置Properties到 XmlConfigBuilder中。在build时,会用Properties定义的变量替换XML中对应的${}参数。通过使用properties来控制配置参数,我们可以使用更多的工具来管理Hazelcast配置,例如使用Maven<resources>元素管理properties。

结构化配置

和spring的配置文件一样,Hazelcast的XML配置文件也可以通过<import>元素来整合多个配置文件。例如有下面2份配置文件。

group-config.xml :

<hazelcast>
  <group>
      <name>dev</name>
      <password>dev-pass</password>
  </group>
</hazelcast>

network-config.xml

<hazelcast>
  <network>
    <port auto-increment="true" port-count="100">5701</port>
    <join>
        <multicast enabled="true">
            <multicast-group>224.2.2.3</multicast-group>
            <multicast-port>54327</multicast-port>
        </multicast>
    </join>
  </network>
</hazelcast>

然后我们可以像下面这样把2份配置整合在一起。

<hazelcast>
  <import resource="group-config.xml"/>
  <import resource="network-config.xml"/>
</hazelcast>

<import>元素同样支持参数:

<hazelcast>
  <import resource="${param1}-group-config.xml"/>
  <import resource="${param2}-network-config.xml"/>
</hazelcast>

有了结构化配置的方法,可以把一份大文档,划分成很多相关部分去维护。

Spring Boot集成Hazelcast实现集群与分布式内存缓存

Hazelcast是Hazelcast公司开源的一款分布式内存数据库产品,提供弹性可扩展、高性能的分布式内存计算。并通过提供诸如Map,Queue,ExecutorService,Lock和JCache等Java的许多开发人员友好的分布式实现。

了解Hazelcast
Hazelcast特性

简单易用
Hazelcast是用Java编写的,没有其他依赖关系。只需简单的把jar包引入项目的classpath即可创建集群。
无主从模式
与许多NoSQL解决方案不同,Hazelcast节点是点对点的。没有主从关系; 所有成员都存储相同数量的数据,并进行相等的处理,避免了单点故障。
弹性可扩展
Hazelcast旨在扩展成千上万的成员。新成员启动,将自动发现群集,并线性增加存储和处理能力。成员之间通过TCP保持连接和通讯。
读写快速高效
Hazelcast所有数据都存储在内存中,提供基于内存快速高效的读写能力。
Hazelcast部署拓扑
在Hazelcast官方提供两种方式部署集群(图片均来自官方文档):
如需聚焦异步或高性能大批量任务的缓存服务,嵌入式方式是相对有优势的,最明显嵌入式方式访问数据延迟性低。
独立创建Hazelcast集群,统一管理,所有的应用程序如果需要访问缓存,可通过Hazelcast客户端(有java .NET C++的实现)或Memcache客户端或简单的REST客户端访问。后续demo示例以嵌入式为例。

Hazelcast数据分区
在Hazelcast分布式环境中,默认情况下,Hazelcast有271个分区。
当启动第一个成员的时候,成员1在集群中的分区如下图:
当在集群中新添加一个节点2时,分区图如下:
在图示中,黑色分区是主分区,蓝色分区是副本分区(备份)。第一个成员具有135个主分区(黑色),并且每个分区都备份在第二个成员(蓝色)中。同时,第一个成员还具有第二个成员的主分区的副本分区。

随着成员的增多,Hazelcast将一些主要和副本分区逐个移动到新成员,使所有成员相等和冗余。只有最小量的分区将被移动到扩展Hazelcast。以下是具有四个成员的Hazelcast集群中的分区图示如下:
Hazelcast在群集成员之间平均分配分区。Hazelcast创建分区的备份,并将其分配给成员之间进行冗余。

上述插图中的分区是为了方便描述。通常,Hazelcast分区不会按照顺序分配(如这些图所示),而是随机分布。Hazelcast在成员间平均分配了分区和备份。

Hazelcast优势

Hazelcast提供开源版本。
Hazelcast无需安装,只是个极小jar包。
Hazelcast提供开箱即用的分布式数据结构,如Map,Queue,MultiMap,Topic,Lock和Executor。
Hazelcast集群非传统主从关系,避免了单点故障;集群中所有成员共同分担集群功能。
Hazelcast集群提供弹性扩展,新成员在内存不足或负载过高时能动态加入集群。
Hazelcast集群中成员分担数据缓存的同时互相冗余备份其他成员数据,防止某成员离线后数据丢失。
Hazelcast提供SPI接口支持用户自定义分布式数据结构。
Hazelcast适用场景

频繁读写数据
需要高可用分布式缓存
内存行NoSql存储
分布式环境中弹性扩展
下面我们来使用Spring Boot集成Hazelcast实现分布式集群服务看看

Spring Boot集成Hazelcast实现分布式集群服务
首先新建一个Spring Boot的gradle项目,引入Hazelcast相关jar包:

dependencies {
compile 'com.hazelcast:hazelcast'
compile 'org.springframework.boot:spring-boot-starter-web'
}
当Hazelcast包在classpath上,Spring Boot将通过下面两种方式之一为我们创建一个HazelcastInstance实例:

方式一,通过配置属性指定的Hazelcast.xml文件创建:
spring.hazelcast.config = classpath:hazelcast.xml
该方式需要编写一个hazelcast.xml文件,通过xml文件描述Hazelcast集群

方式二,通过提供一个com.hazelcast.config.Config javabean到Spring容器中(下面所有demo是基于java config方式)

@Bean
public Config hazelCastConfig() {
//如果有集群管理中心,可以配置
ManagementCenterConfig centerConfig = new ManagementCenterConfig();
centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
centerConfig.setEnabled(true);
return new Config()
.setInstanceName("hazelcast-instance")
.setManagementCenterConfig(centerConfig)
.addMapConfig(
new MapConfig()
.setName("instruments")
.setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
.setEvictionPolicy(EvictionPolicy.LRU)
.setTimeToLiveSeconds(20000));
}
上面代码通过提供Config的bean时候,主要做了如下几个事:

创建一个默认名为hazelcast-instance的HazelcastInstance实例;
使用默认的组播发现模式,组播传播地址默认为:224.2.2.3,如果想修改信息或修改为TCP模式可通过setNetworkConfig()接口设置相关信息;
创建一个名为dev,访问密码为dev-pass的group保障节点加入,如果想修改组,可通过setGroupConfig()接口设置相关信息;
创建了一个名为instruments的分布式map数据结构,并设置了该map的最大容量200/逐出策略LRU/有效期20000ms等信息,当集群启动后,我们可以在任一成员节点上通过HazelcastInstance读写该map。
完整代码:

@SpringBootApplication
public class StartUp {

private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);

public static void main(String[] args) {
SpringApplication.run(StartUp.class, args);
}

@Bean
public Config hazelCastConfig() {
//如果有集群管理中心,可以配置
ManagementCenterConfig centerConfig = new ManagementCenterConfig();
centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
centerConfig.setEnabled(true);
return new Config()
.setInstanceName("hazelcast-instance")
.setManagementCenterConfig(centerConfig)
.addMapConfig(
new MapConfig()
.setName("instruments")
.setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
.setEvictionPolicy(EvictionPolicy.LRU)
.setTimeToLiveSeconds(20000));
}
}
下面我们通过修改server.port分别启动端口为8080和8081的成员服务
当启动完8080成员的时候,可以在8080控制台看到如下日志:

Members [1] {
Member [172.17.42.1]:5701 - 0d39dd66-d4fb-4af4-8ddb-e9f4c7bbe5a1 this
}
因我们使用的是组播传播模式,5701为节点在组播网络中分配的端口
当启动完8081成员的时候,可以在8081控制台看到如下日志:

Members [2] {
Member [172.17.42.1]:5701 - 0d39dd66-d4fb-4af4-8ddb-e9f4c7bbe5a1
Member [172.17.42.1]:5702 - a46ceeb4-e079-43a5-9c9d-c74265211bf7 this
}
回到8080控制台,发现多了一行日志:

Members [2] {
Member [172.17.42.1]:5701 - 0d39dd66-d4fb-4af4-8ddb-e9f4c7bbe5a1 this
Member [172.17.42.1]:5702 - a46ceeb4-e079-43a5-9c9d-c74265211bf7
}
发现8081成员也加入进来了。两个控制台都能看到成员列表。集群就已经搭建成功。

为了验证结果,上面我们在集群中已经创建了一个名为instruments的分布式map数据结构,现在我们通过写个接口证明:

@GetMapping("/greet")
public Object greet() {
Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
if (Objects.isNull(value)) {
Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");

} LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
return value;
}
首先通过访问8080服务的/greet,第一次访问instruments中是没有key为hello的键值对,会往里面塞入{"helo":"world!"},然后访问8081服务的/greet,这个时候应该是能取得改键值对的。

完整代码:

@RestController
@SpringBootApplication
public class StartUp {

private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);

public static void main(String[] args) {
SpringApplication.run(StartUp.class, args);
}

@Bean
public Config hazelCastConfig() {
//如果有集群管理中心,可以配置
ManagementCenterConfig centerConfig = new ManagementCenterConfig();
centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
centerConfig.setEnabled(true);
return new Config()
.setInstanceName("hazelcast-instance")
.setManagementCenterConfig(centerConfig)
.addMapConfig(
new MapConfig()
.setName("instruments")
.setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
.setEvictionPolicy(EvictionPolicy.LRU)
.setTimeToLiveSeconds(20000));
}
@GetMapping("/greet")
public Object greet() {
Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
if (Objects.isNull(value)) {
Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");

} LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
return value;
}
}
重启8080和8081服务
通过浏览器请求http://localhost:8080/greet
查看8080控制台日志:
2017-10-23 13:52:27.865 INFO 13848 --- [nio-8080-exec-1] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=nul

通过浏览器请求http://localhost:8081/greet
查看8081控制台日志:
2017-10-23 13:52:40.116 INFO 13860 --- [nio-8081-exec-2] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=world

Spring Boot为Hazelcast提供了明确的缓存支持。如果启用缓存, HazelcastInstance则会自动包含在CacheManager实现中。所以完全可以支持Spring Cache。

以往我们用Spring Cache都是基于Redis做存储后端,现在我们使用Hazelcast来尝试一下 首先在启动类上开启缓存
@EnableCaching

建立个service类,demo为了方便,写在一起
完整代码:

@EnableCaching
@RestController
@SpringBootApplication
public class StartUp {

private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);

public static void main(String[] args) {
SpringApplication.run(StartUp.class, args);
}

@Bean
public Config hazelCastConfig() {
//如果有集群管理中心,可以配置
ManagementCenterConfig centerConfig = new ManagementCenterConfig();
centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
centerConfig.setEnabled(true);
return new Config()
.setInstanceName("hazelcast-instance")
.setManagementCenterConfig(centerConfig)
.addMapConfig(
new MapConfig()
.setName("instruments")
.setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
.setEvictionPolicy(EvictionPolicy.LRU)
.setTimeToLiveSeconds(20000));
}
@GetMapping("/greet")
public Object greet() {
Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
if (Objects.isNull(value)) {
Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");

} LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
return value;
}

@Autowired
private DemoService demoService;

@GetMapping("/cache")
public Object cache() {
String value = demoService.greet("hello"); LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
return value;
}

}

@Service
@CacheConfig(cacheNames = "instruments")
class DemoService {

private Logger LOGGER = LoggerFactory.getLogger(DemoService.class);

@Cacheable(key = "#key")
public String greet(String key) { LOGGER.info("缓存内没有取到key={}", key);
return "world!";
}

}
连续访问两次8080服务的/cache接口 第一次控制台输出日志:

2017-10-23 14:10:02.201 INFO 13069 --- [nio-8081-exec-1] com.hazelcast.DemoService: 缓存内没有取到key=hello
2017-10-23 14:10:02.202 INFO 13069 --- [nio-8081-exec-1] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=world!
第二次控制台输出日志:
2017-10-23 14:11:51.966 INFO 13069 --- [nio-8081-exec-3] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=world!

第二次比第一次相比少了执行service方法体内容,证明第二次是通过了缓存获取。

在Hazelcast官网上,有使用Hazelcast集群和Redis集群做缓存的对比
单只性能上来说,写入速度Hazelcast比Redis快44%,读取速度Hazelcast比Redis快56%
详情移步底下参考资料中链接
下面,我们再来一个尝试,既然有分布式缓存了,我们可以把我们的8080和8081服务做成一个web集群,web服务集群主要标志是前端负载均衡和session共享,我们来实现8080和8081的session共享。
Spring Session已经支持使用Hazelcast作为会话缓存后端,首先引入Spring Session jar包

dependencies {
compile 'com.hazelcast:hazelcast'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.session:spring-session'
}
要启用Hazelcast作为集群会话缓存后端,有两种方式
第一种Spring Boot配置文件里面配置spring.session.*属性:
spring.session.store-type=hazelcast

第二种使用java注解开启:
@EnableHazelcastHttpSession

这里选择第二种方式,要证明集群会话共享,我们定一个简单接口打印一下sessionId,通过同一浏览器访问8080和8081服务的该接口,看看不同服务请求的时候sessionId是否一致,完整代码如下:

@EnableCaching
@RestController
@EnableHazelcastHttpSession
@SpringBootApplication
public class StartUp {

private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);

public static void main(String[] args) {
SpringApplication.run(StartUp.class, args);
}

@Bean
public Config hazelCastConfig() {
//如果有集群管理中心,可以配置
ManagementCenterConfig centerConfig = new ManagementCenterConfig();
centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
centerConfig.setEnabled(true);
return new Config()
.setInstanceName("hazelcast-instance")
.setManagementCenterConfig(centerConfig)
.addMapConfig(
new MapConfig()
.setName("instruments")
.setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
.setEvictionPolicy(EvictionPolicy.LRU)
.setTimeToLiveSeconds(20000));
}
@GetMapping("/greet")
public Object greet() {
Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
if (Objects.isNull(value)) {
Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");

} LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
return value;
}

@Autowired
private DemoService demoService;

@GetMapping("/cache")
public Object cache() {
String value = demoService.greet("hello"); LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
return value;
}

@GetMapping("/session")
public Object session(HttpSession session) {
String sessionId = session.getId(); LOGGER.info("当前请求的sessionId={}", sessionId);
return sessionId;
}
}

@Service
@CacheConfig(cacheNames = "instruments")
class DemoService {

private Logger LOGGER = LoggerFactory.getLogger(DemoService.class);

@Cacheable(key = "#key")
public String greet(String key) { LOGGER.info("缓存内没有取到key={}", key);
return "world!";
}

}
访问8080服务/session接口,控制台日志如下:
2017-10-23 14:28:41.991 INFO 14140 --- [nio-8080-exec-2] com.hazelcast.StartUp: 当前请求的sessionId=e75ffc53-90bc-41cd-8de9-e9ddb9c2a5ee

访问8081服务/session接口,控制台日志如下:
2017-10-23 14:28:45.615 INFO 14152 --- [nio-8081-exec-1] com.hazelcast.StartUp: 当前请求的sessionId=e75ffc53-90bc-41cd-8de9-e9ddb9c2a5ee
集群会话共享生效。

集群管理界面
在上面的demo中,在创建Config的时候,设置了一个ManagementCenterConfig配置,该配置是指向一个Hazelcast集群管理平台,比如demo中表示在本地启动了一个管理平台服务。该功能也是相对其他NoSql服务的一个优势。

要部署ManagementCenter管理平台有多种方式
比如通过https://download.hazelcast.com/management-center/management-center-3.8.3.zip地址下载,解压后启动;
sh ./startManCenter.sh 8200 /mancenter

如果有docker环境,直接可以docker部署:
docker run -ti -p 8200:8080 hazelcast/management-center:latest

部署成功后,访问http://ip:8200/mancenter,首次访问会让你配置个用户名密码,进入后 :
在左侧菜单栏,能看到现有支持的分布式数据格式,比如Maps下面名为instruments的是我们前面demo自己创建的,名为spring:session:sessions是我们用了Hazelcast做集群会话同步的时候Spring为我们创建的。

中间区域能看到所有节点成员的系统相关实时使用率,随便点击一个节点进去,能看到当前节点的系统实时使用率:
红圈里面的即是上面提到的节点数据分区数,通过左侧菜单栏的数据结构进去,能看到当前对应的数据结构的详细信息和实时吞吐量:
更多内容请参考下方参考资料。
示例代码可以通过https://github.com/zggg/hazelcast-in-spring-boot下载。

参考资料

为什么选Hazelcast:https://hazelcast.com/why-hazelcast/imdg/
Hazelcast官方文档:http://docs.hazelcast.org/docs/3.8.6/manual/html-single/index.html
Redis对比:https://hazelcast.com/use-cases/nosql/redis-replacement/
Redis 3.2.8 vs Hazelcast 3.8 集群基准测试对比:https://hazelcast.com/resources/benchmark-redis-vs-hazelcast/

 

来源: https://www.cnblogs.com/heishao/p/9951421.html

 

--------------------

Hazelcast与Spring集成问题

最近在项目中加入了Hazelcast第三方插件,在与Spring集成的时候,将Hazelcast配置到Spring的配置文件当中,出现了不能通过属性注入的方法,给属性赋值,代码如下:

<bean id="hazelcast" class="com.hazelcast.core.Hazelcast"
       factory-method="newHazelcastInstance">
       <constructor-arg>
           <bean class="com.hazelcast.config.Config">
               <property name="port" value="5701" />
               <property name="portAutoIncrement" value="true" />
               <property name="groupConfig">
                   <bean class="com.hazelcast.config.GroupConfig">
                       <property name="name" value="dev" />
                       <property name="password" value="pwd" />
                   </bean>
               </property>
               <property name="networkConfig">
                   <bean class="com.hazelcast.config.NetworkConfig">
                       <property name="join">
                           <bean class="com.hazelcast.config.Join">
                               <property name="multicastConfig">
                                   <bean class="com.hazelcast.config.MulticastConfig">
                                       <property name="enabled" value="false" />
                                       <property name="multicastGroup" value="224.2.2.3" />
                                       <property name="multicastPort" value="54327" />
                                   </bean>
                               </property>
                               <property name="tcpIpConfig">
                                   <bean class="com.hazelcast.config.TcpIpConfig">
                                       <property name="enabled" value="true" />
                                       <property name="members">
                                           <list>
                                               <value>10.2.108.194</value>
                                               <value>10.253.73.24</value>
                                           </list>
                                       </property>
                                   </bean>
                               </property>
                           </bean>
                       </property>
                   </bean>
               </property>
           </bean>
       </constructor-arg>
   </bean>

---------------

先将hazelcast-all-x.x.x.jar放到项目的lib中,
将hazelcast.xml复制到项目的根目录中。

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans                       

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd


http://www.springframework.org/schema/context

                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <bean id="instance" class="com.hazelcast.core.Hazelcast" factory-method="newHazelcastInstance"></bean>
    <bean name="multiMap" factory-bean="instance" factory-method="getMultiMap">
        <constructor-arg value="userSession"/>
    </bean>
</beans>

<constructor-arg value="userSession"/>的userSession名字随便取。
在hazelcast的管理界面会显示的。
id="instance"的bean不写<constructor-arg>会在根目录中寻找hazelcast.xml文件的。
除了有getMultiMap外,还有getMap,getQueue,getTopic,getSet,getList,getExecutorService,getIdGenerator,getAtomicLong,getSemaphore,getCountDownLatch,getLock等。

@Autowired
private MultiMap<String, Object> multiMap;

在java代码中这样就可以操作MultiMap了。

------------------

hazelcast初探
Hazelcast作为一个高度可扩展的数据分发和集群平台,提供了高效的、可扩展的分布式数据存储、数据缓存。Hazelcast是开源的,在分布式技术方面,Hazelcast提供了十分友好的接口供开发者选择,如Map,Queue,ExecutorService, Lock和Jcache。
Hazelcast的稳定性很高,分布式应用可以使用Hazelcast进行存储数据、同步数据、发布订阅消息等。Hazelcast是基于Java开发的,其客户端有Java, C/C++, .NET以及REST。Hazelcast同时也支持memcache协议。它很好的支持了Hibernate,可以很容易的在当今流行的数据库系统中应用。如果你在寻找一个基于内存的、可扩展的以及对开发者友好的NoSql,那么Hazelcast是一个很不错的选择!

Hazelcast是一个高度可扩展的数据分发和集群平台。特性包括:

提供java.util.{Queue, Set, List, Map}分布式实现。
提供java.util.concurrency.locks.Lock分布式实现。
提供java.util.concurrent.ExecutorService分布式实现。
提供用于一对多关系的分布式MultiMap。
提供用于发布/订阅的分布式Topic(主题)。
通过JCA与J2EE容器集成和事务支持。
提供用于安全集群的Socket层加密。
支持同步和异步持久化。
为Hibernate提供二级缓存Provider 。
通过JMX监控和管理集群。
支持动态HTTP Session集群。
利用备份实现动态分割。
支持动态故障恢复。
1.优点

a. Hazelcast开发比较简单
Hazelcast是基于Java写的,没有任何其它的以来。它提供的API跟Java util包很像。对于开发者来说,只需要加入hazelcast.jar,然后就可以快速使用在多个JVM之间的数据共享(分布式)。

b. Hazelcast的节点之间是平等的(Peer-to-Peer)
不像其它很多的NoSql解决方案,Hazelcast的节点之间是对等的(没有主次之分)。所有的节点存储的数据都是相等的,在应用中可以很容易的增加一个Hazelcast节点。或者以客户端-服务端的形式使用。

c. Hazelcast是可扩展的
Hazelcast的扩展性非常强,可以很简单的增加或减少节点。可以自动的监听节点的增加,并以线性的方式增加存储空间和能力。节点之间的通信是以TCP的方式建立的。

d. Hazelcast效率很高。
将数据存储在内存中,所以是非常高效的,包括读操作和写操作。

e. Hazelcast是可备份的
Hazelcast的数据会在多个节点上进行备份。一旦一个节点失败了,数据将会从别的节点上进行恢复。

f. Hazelcast页面元素齐全

页面可以看到map,list等数据内容,以及一些容量的仪表图.可以看到很多有用的数据,包括每个Map的请求次数等.

2. 使用场景
分布式缓存,通常使用在数据库之前的那一层
缓存服务器
NoSql的数据存储
Spring cache
微服务的结构
储存临时数据,如web的session等

3. Hazelcast的数据拆分
Hazelcast的数据拆分叫做间隔(Partitions)。默认情况下,Hazelcast会将数据拆分成271个间隔(总数,并不是单个单点)。当传入一个key时,Hazelcast会对它进行序列化,以及进行hash的算法等算出一个数值,通过该数值它存放在相应的间隔中(271个的其中一个)。在不同的节点中存放相同数量的间隔。Hazelcast还会生成备份的间隔,同样也是存放在这些间隔中。

4.配置

配置组连接选项

<group>
<name>test</name>
<password>test</password>
</group>
配置管理页面,如果配置为true,则可以在mancenter目录下启动页面管理

<management-center enabled="true">http://10.1.4.97:8080/mancenter</management-center>
配置连接网络(自增长如果配为true,端口启动失败时会自增)

<port auto-increment="true" port-count="100">5701</port>
集群节点发现机制,自选一种

<join>
<multicast enabled="false">
<multicast-group>224.2.2.3</multicast-group>
<multicast-port>54327</multicast-port>
</multicast>
<tcp-ip enabled="true">
<interface>10.10.10.10</interface>
<member-list>
<member>10.10.10.10</member>
</member-list>
</tcp-ip>
<aws enabled="false">
<access-key>my-access-key</access-key>
<secret-key>my-secret-key</secret-key>
<!--optional, default is us-east-1 -->
<region>us-west-1</region>
<!--optional, default is ec2.amazonaws.com. If set, region shouldn't be set as it will override this property -->
<host-header>ec2.amazonaws.com</host-header>
<!-- optional, only instances belonging to this group will be discovered, default will try all running instances -->
<security-group-name>hazelcast-sg</security-group-name>
<tag-key>type</tag-key>
<tag-value>hz-nodes</tag-value>
</aws>
</join>

配置节点对套接字加密,算法可选

<symmetric-encryption enabled="false">
<!--
encryption algorithm such as
DES/ECB/PKCS5Padding,
PBEWithMD5AndDES,
AES/CBC/PKCS5Padding,
Blowfish,
DESede
-->
<algorithm>PBEWithMD5AndDES</algorithm>
<!-- salt value to use when generating the secret key -->
<salt>thesalt</salt>
<!-- pass phrase to use when generating the secret key -->
<password>thepass</password>
<!-- iteration count to use when generating the secret key -->
<iteration-count>19</iteration-count>
</symmetric-encryption>
配置执行服务器的线程和队列容量

<executor-service name="default">
<pool-size>16</pool-size>
<!--Queue capacity. 0 means Integer.MAX_VALUE.-->
<queue-capacity>0</queue-capacity>
</executor-service>

参考:http://blog.csdn.net/hengyunabc/article/details/18514563

 

Hazelcast的一些问题

一。 hazelcast介绍

Hazelcast是一个高度可扩展的数据分发和集群平台,可用于实现分布式数据存储、数据缓存。特性包括:

  • 提供java.util.{Queue, Set, List, Map}分布式实现。
  • 提供java.util.concurrency.locks.Lock分布式实现。
  • 提供java.util.concurrent.ExecutorService分布式实现。
  • 提供用于一对多关系的分布式MultiMap。
  • 提供用于发布/订阅的分布式Topic(主题)。
  • 通过JCA与J2EE容器集成和事务支持。
  • 提供用于安全集群的Socket层加密。
  • 支持同步和异步持久化。
  • 为Hibernate提供二级缓存Provider 。
  • 通过JMX监控和管理集群。
  • 支持动态HTTP Session集群。
  • 利用备份实现动态分割。
  • 支持动态故障恢复。

示例代码:

import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;

import java.util.concurrent.ConcurrentMap;

public class DistributedMap {
public static void main(String[] args) {
Config config = new Config();
HazelcastInstance h = Hazelcast.newHazelcastInstance(config);
ConcurrentMap<String, String> map = h.getMap("my-distributed-map");
map.put("key""value");
map.get("key");
//Concurrent Map methods
map.putIfAbsent("somekey""somevalue");
map.replace("key""value""newvalue");
}
}

 

 

二 hazelcast的一些问题

1.下面以一个客户端创建和发请求的过程来分解描述。

public static void main(String[] args) {
ClientConfig clientConfig = new ClientConfig();
clientConfig.addAddress("10.10.4.40:5701");
// client初始化时会创建一系列service(线程池管理器、集群客户端服务、虚拟节点管理、动态扩展服务等),先启动ClientClusterServiceImpl,读取当前活动的实际节点(先根据clientConfig指定的地址获取connection,然后基于这个连接,再发起读取实际节点的请求),然后启动ClientPartitionServiceImpl,向各个实际活动节点发起请求获取其上的虚拟节点,记录到一个ConcurrentHashMap里。
HazelcastInstance instance = HazelcastClient.newHazelcastClient(clientConfig);
// 这里的map并不是真的map,而是一个mapProxy
// 并且这里要指定key和value的类型
Map<Integer, String> mapCustomers = instance.getMap("customers");
// put时,由这个mapProxy先把key和value都序列化为byte[]
// 然后用key的hash对虚拟节点数取余:key.getHash()%271,获得partitionId
// 根据ClientPartitionServiceImpl里的ConcurrentHashMap记录的虚拟节点和实际节点的对应关系,确定了该key对应的实际节点。然后通过BufferedOutputStream方式 对该地址发起操作请求。
mapCustomers.put(1, "Joe");
// 跟put类似,定位节点,然后发请求。只是请求类型不同而已。
System.out.println("Customer with key 1: "+ mapCustomers.get(1));
System.out.println("Map Size:" + mapCustomers.size());
}

2.单点问题:

hazelcast之所以没有单点问题,不是因为没有master节点,而是直接把集群中最早的节点作为master节点,一旦第一个节点挂了,第二个自然就成为了第一个,也就成为了master。而master的作用是与各成员保持连接和心跳,维护成员列表和虚拟节点列表,并在各节点和客户端要获取时提供。管理这两个列表的服务分别是ClusterService和PartitionService。

3.故障转移:

假设在执行mapCustomers.put(1, "Joe");操作时,要请求虚拟节点所在的实际节点挂了,则客户端会接收到IOException,此处客户端代码中判断如果出现IO异常,则向master节点发起一个异步的更新虚拟节点列表的请求,并重试刚才报IO异常的操作,重试时是向节点列表的下一个成员地址发请求。此处使用了while循环,除非客户端stop,否则会一直重试。如果挂掉的是master节点,则刚才向master发的获取虚拟节点列表的请求也会报IO异常,所以它也会找下一个节点重试。其实由于排在第二的节点自动就成为master,所以重试次数不会很多。

4.动态扩容:

当启动一个新节点时,它会先用组播服务向所有节点发起请求,从而能够与master节点连接上,加入成功则master会把新的成员列表发给各节点。然后执行rebalance:首先根据新成员列表创建出新的虚拟节点列表,然后把原虚拟节点列表与新列表做比较从而针对每个需要移动的虚拟节点创建一个task,放到一个task队列中去依次执行。

5.数据一致性:

客户端向 Hazelcast写入数据本体所在节点是必须同步的;而备份过程默认是异步的,也可以修改配置成同步。为了保证一致性,默认情况下,读取数据总是从数据的owner节点读取,这个也可以修改配置成允许从备份节点读数据,这样能给你带来更好的读性能。

举例来说,要更新key为1的数据时,由一致性hash算法得知其存在节点A上,则对节点A发起update请求,这时如果你用另一个客户端也要更新节点A上的key1时,咱俩这个操作肯定是同步控制的。而节点A把key1备份到节点B的过程你可以配成同步,然后再配成允许从备份节点读取,这样保证了一致性和高可读。如果备份过程配成异步,再配成不允许从备份节点读取,则保证了高可写,而一致性也基本ok,只是万一异步备份未完成时,数据本体所在节点挂掉,那数据就可能脏了。

 

另外一些问题

坑爹事情
配置各种找不到
有很多xml的配置方式没有写在文档上,要到代码里各种找。友情提示,可以到代码里的test目录下找到比较完整的配置:

https://github.com/hazelcast/hazelcast/blob/maintenance-3.x/hazelcast-spring/src/test/resources/com/hazelcast/spring/node-client-applicationContext-hazelcast.xml

有很多参数的配置没有写在文档上,要到代码里各种找。友情提示,在com.hazelcast.instance.GroupProperties 这个类里找到一些在文档上没有的配置参数。

默认的超时配置太长
很多超时配置都是上百秒的,试想现在的网站或者应用,有哪个可以忍受上百秒的超时。从另一个侧面也可以看出hazelcast的自己的信心不足,要靠超长时间的超时来保证运行的正确性。

即使配置了较短的超时时间,还是有可能会有各种出人意料的超时,认真研究过代码后,发现是有很多超时时间是在代码里写死的。。

版本之间不兼容
版本之间不兼容,不能滚动升级。这就意味着,当升级时,整个集群都要一块重启,这对很多网站来说,是不能忍受的。据说从3.1版本后会保证小版本的兼容性。

https://github.com/hazelcast/hazelcast/issues/14

hazelcast里代码一大问题就是把序列化方案和网络通讯混在一起了,导致各种升级兼容问题。每个消息包在解析时,都有可能因为类有改动而不兼容。

而且序列化方案还是那种要实现一个特定接口的。在Protobuf,Thrift,及各种基于反射的序列化方案这么流行的今天,很难想像会有这样难用的序列化方式。

一个结点出问题,影响整个集群
当集群里某个节点出故障时,比如OOM,CPU100%,没反应之后,集群里发到那个结点的操作就各种超时,各种不正常。这个可以算是hazelcast的一个致命的缺点。

我们线上的集群有30多个结点,随便一个有问题,都会导致整个集群有问题。另外,当集群里有一个应用下线/上线,都会引起数据的迁移,尽管迁移是自动的,但是也是一个不可控的风险。

我们开始时用的是hazelcast2.5.1,后来升级到3.1.3版本。升级后发现两个结点间经常会有网络流量超高的情况,最后发现是merge-policy的配置在3.0只能配置类的全名,而在2.5是可以配置一个简称的。然后在集群里有数据要迁移,进行Merge时,就会因为ClassNotFoundException而失败。而Hazelcast坑爹的地方在于它不断地重试,而且是无停顿地重试,从而导致两个结点之间网络流量超高,甚至超过了100Mbps。

hazelcast client很难用
首先,还是文档太少,很多配置根本没有提到,得自己到代码里去找。

另外,如果hazelcast server集群全部挂掉后,client居然不会自己重连(重试3次就放弃了)。现在的各种组件重启是很正常的事情,而hazelcast client居然不会自动重连,真心令人无语。更加扯蛋的是,比如map.get,如果没有连接上,会抛出一个RuntimeException,那么整个线程都退出了。

3.0版本和3.0.2版本之间的配置格式居然有很大的变化,很多时候,找个配置,得自己去看xml的xsd文件。。

结点之间Merge时,需要反序列化
这个我认为是代码太多导致的混乱。结点之间数据合并时,本来只要比较下数据的版本,时间等就可以了,但是在合并时却把对象反序化出来。如果在Server端没有对应的jar包,则会抛出ClassNotFoundException。

参考这里:

https://github.com/hazelcast/hazelcast/issues/1514

一些原理性的东东
Partition
从原理上来说,hazelcast是默认有271个partition,这271个parition平均分布在集群里的结点中,因此集群里的数据分散在每个结点中。然后在进行操作时,先计算得到key所在的partiion,再进行操作。

详细请参考PartitionServiceImpl这个类的代码:

public final int getPartitionId(Data key) {
int hash = key.getPartitionHash();
return (hash != Integer.MIN_VALUE) ? Math.abs(hash) % partitionCount : 0;
}
NearCache的实现原理
hazelcast里有一个所谓的nearcache的东东,其实这个很简单,就是一个本地的二级缓存。在get的时候先到本地的nearcache里查找,如果没有计算hash,再到对应的结点中取数据,再放到nearcache里。
---------------------

 

 

Redis和Hazelcast–RadarGun 对二者的比较

自从2009年初始发布以来,Redis受到了巨大的欢迎并且成为拥有大型社区的部署数据存储平台之一。

虽然Radis有很令人难忘的特性,但是它也有一个严重的限制--它是为了单机模式设计的。如果用户需要超过单机的能力,就需要使用专用分区系统。不过3.0.0版本发布了一种集群系统产品,可以从根本上简化分布式Redis部署。

所有人都认可Radis很快,我们来看看Hazelcast和Redis的比较。这份报告是为了观察Redis集群方案(v3.0.7)对比Hazelcast(v3.6.1)的表现, 特别是在重负荷的情况下。

为了确保我们在比较Hazelcast和Redis时候拥有稳定的环境,我们选择使用我们通常用于测试Hazelcast性能改进的室内测试实验室。

实验室配置

测试在由HP ProLiant DL380系列服务器构成的集群上进行。每一台机器配置有双socket Xeon E5-2687W v3@3.10Ghz, 每个CPU10核,超线程可用(一共有20个物理的,40个虚拟核),并且还有768G 2133Mhz的内存(24X32GB 模块)。在操作系统方面,我们使用简单的RHEL7安装,这表示在测量中不会使用虚拟软件。每台机器使用一个40 GbE SolarFlare 网卡(Sloarflare SFN7142Q Dual Port 40 GbE)来做点对点的通信。

为了执行Redis 测试,我们决定小痾怒责Jedis做客户端,因为它很流行(在github上有3481个星)并且对集群模式有非常好的支持度。

为了排除其他影响,我们决定雇佣Infinispan开发社区创建的第三方测量工具RadarGun。因为RadarGun不提供直接可用的Redis支持,我们需要自己集成。感兴趣的人可以去github上获取RadarGun fork和Redis插件。

我们需要什么

作为测试场景,我们想要基于客户增长数和并发处理数据数来比较Hazelcast和Redis的表现。所有的测试都在测试环境的 4个节点集群上执行并观察,4个基本测试场景需要被执行:

脚本  客户端数 每个客户端的进程数

1        1            1

2        4            8

3        4            32

4        4            64

正如上文所说,我们用以下版本来执行测试:

  • Hazelcast Version 3.6.1
  • Redis Version 3.0.7, Jedis 2.8.0

吞吐量

看一下吞吐量结果,Redis在少量客户端和/或进程的情况下非常快,但是它在高并发负载下变得很慢。测量超过特定数量的线程会拖慢Redis内部结构的可扩展性。另一方面,Hazelcast在很小的负载情况下表现较差,但是在并发处理和高数量客户端或线程开始被进入的时候,测量表现要远高于Redis。

脚本  Hazelcast 结果(reqs/s)     Redis 结果(reqs/s)

113,954 50,634
2365,792645,976
3872,773702,671
41,166,204722,531

延迟性

根据结果,延迟性表现和并发性测试里有类似模式。Redis在低数据负载的时候响应比Hazelcast表现更好,而在数据负载和并发请求增加时则表现相反。在不常见的大环境(如我们在脚本4)我们可以看到Redis的平均响应时间剧烈的增长。Hazelcast响应时间虽然也随着线程数增加而增长,但是这种增长要稳定得多,而且不像Redis表现的那样是指数级。

                脚本                Hazelcast (响应时间 μs)                  Redis (响应时间 μs)
                1                  70,14                    19,37
                2                  85,83                    48,98
                3                  144,7                    225,22
                4                  217,52                    640,51

结论

尽管在基准测试中的变量来自于测试的方法,调整设置,相对来说是一种常见的模式。Redis 在低用户数量和低资源争用连接情况下是一个好的选择,而这个霸主地位将会被更多的负载集群所打破。

从这个结果来说,大量的 Redis 用户不希望看到这个。他们真的有低量的数据负载系统,还是他们有另一种更好的方式呢?我们推荐开发者和架构师在使用任何技术之前,应该尽职预言并充分测试自己的用例。

一定要下载完整的基准( PDF 格式),并查看新的 Redis 为 Hazelcast 用户对应的白皮书。

来源: https://www.oschina.net/translate/redis-vs-hazelcast-radargun-puts-them-to-a-challenge

Hazelcast介绍与使用

Hazelcast 是一个开源的可嵌入式数据网格(社区版免费,企业版收费)。你可以把它看做是内存数据库,不过它与 Redis 等内存数据库又有些不同。项目地址:http://hazelcast.org/

Hazelcast 使得 Java 程序员更容易开发分布式计算系统,提供了很多 Java 接口的分布式实现,如:Map, Queue, ExecutorService, Lock, 以及 JCache。它以一个 JAR 包的形式提供服务,只依赖于 Java,并且提供 Java, C/C++, .NET 以及 REST 客户端,因此十分容易使用。

  1 import com.hazelcast.config.Config;
  2 import com.hazelcast.core.Hazelcast;
  3 import com.hazelcast.core.HazelcastInstance;
  4 
  5 import java.util.concurrent.ConcurrentMap;
  6 
  7 public class DistributedMap {
  8     public static void main(String[] args) {
  9         Config config = new Config();
 10         HazelcastInstance h = Hazelcast.newHazelcastInstance(config);
 11         ConcurrentMap<String, String> map = h.getMap("my-distributed-map");
 12         map.put("key", "value");
 13         map.get("key");
 14 
 15         //Concurrent Map methods
 16         map.putIfAbsent("somekey", "somevalue");
 17         map.replace("key", "value", "newvalue");
 18     }
 19 }
 20

如何存储数据

Hazelcast 服务之间是端对端的,没有主从之分,因此也不存在单点故障。集群中所有的节点都存储等量的数据以及进行等量的计算。

Hazelcast 缺省情况下把数据分为 271 个区。这个值可配置于系统属性 hazelcast.partition.count。 对于一个给定的键,在经过序列号、哈希并对分区总数取模之后能得到此键对应的分区号。所有的分区等量的分布与集群中所有的节点中,每个分区对应的备份也同样分布在集群中。

下例是拥有2个节点的 Hazelcast 集群:

黑色字体表示分区,蓝色字体表示备份。节点1存储了1到135分区,这些分区同时备份在节点2中。节点2存储了136到271分区,并备份在节点1中。

此时如果添加2个节点到集群中,Hazelcast 一个一个的移动分区和备份到新的节点,使得集群数据分布平衡。

注意实际中分区并不是有顺序的分布,而是随机分布,上面的示例只是为了方便理解。重要的是理解 Hazelcast 平均分布分区以及备份。

Hazelcast 使用哈希算法进行数据分区。对于一个给定的键(如Map)或者对象名称(如topic和list):

  • 序列化此键或对象名称,得到一个byte数组。
  • 对byte数组进行哈希。
  • 取模后的值即为分区号。

每个节点维护一个分区表,存储着分区号与节点之间的对应关系。这样每个节点都知道如何获取数据。

重分区

集群中最老的节点(或者说最先启动)负责定时发送分区表到其他节点。这样如果有节点加入或者离开集群,所有的节点也能更新分区表。

注意 注意: 如果最老的节点挂了,次老节点会接手这个任务。

这个定时任务时间间隔可配置系统属性 hazelcast.partition.table.send.interval。 缺省值为15秒。

重分区发生在:

  • 节点加入集群。
  • 节点离开集群。

此时最老节点会更新分区表,分发,接着集群开始移动分区,或者从备份恢复分区。

使用方式

有两种方式:嵌入式和客户端服务器。

  • 嵌入式,Hazelcast 服务器的 jar 包被导入宿主应用程序,服务器启动并存在于各个宿主应用中。优点是可以更低延迟的数据访问。

  • 客户端服务器,Hazelcast 客户端的 jar 包被导入宿主应用程序,服务器 jar 包独立运行于 JVM 中。优点是更容易调试以及更可靠的性能,最重要的是更好的扩展性。

来源: http://www.cnblogs.com/seasonsluo/p/hazelcast-intro.html

Hazelcast作为一个高度可扩展的数据分发和集群平台,提供了高效的、可扩展的分布式数据存储、数据缓存。Hazelcast是开源的,在分布式技术方面,Hazelcast提供了十分友好的接口供开发者选择,如Map,Queue,ExecutorService, Lock和Jcache。
Hazelcast的稳定性很高,分布式应用可以使用Hazelcast进行存储数据、同步数据、发布订阅消息等。Hazelcast是基于Java开发的,其客户端有Java, C/C++, .NET以及REST。Hazelcast同时也支持memcache协议。它很好的支持了Hibernate,可以很容易的在当今流行的数据库系统中应用。

如果你在寻找一个基于内存的、可扩展的以及对开发者友好的NoSql,那么Hazelcast是一个很不错的选择!

 

Hazelcast是一个高度可扩展的数据分发和集群平台。特性包括:

  • 提供java.util.{Queue, Set, List, Map}分布式实现。
  • 提供java.util.concurrency.locks.Lock分布式实现。
  • 提供java.util.concurrent.ExecutorService分布式实现。
  • 提供用于一对多关系的分布式MultiMap。
  • 提供用于发布/订阅的分布式Topic(主题)。
  • 通过JCA与J2EE容器集成和事务支持。
  • 提供用于安全集群的Socket层加密。
  • 支持同步和异步持久化。
  • 为Hibernate提供二级缓存Provider 。
  • 通过JMX监控和管理集群。
  • 支持动态HTTP Session集群。
  • 利用备份实现动态分割。
  • 支持动态故障恢复。

1.优点

a. Hazelcast开发比较简单
Hazelcast是基于Java写的,没有任何其它的以来。它提供的API跟Java util包很像。对于开发者来说,只需要加入hazelcast.jar,然后就可以快速使用在多个JVM之间的数据共享(分布式)。

b. Hazelcast的节点之间是平等的(Peer-to-Peer)
不像其它很多的NoSql解决方案,Hazelcast的节点之间是对等的(没有主次之分)。所有的节点存储的数据都是相等的,在应用中可以很容易的增加一个Hazelcast节点。或者以客户端-服务端的形式使用。

c. Hazelcast是可扩展的
Hazelcast的扩展性非常强,可以很简单的增加或减少节点。可以自动的监听节点的增加,并以线性的方式增加存储空间和能力。节点之间的通信是以TCP的方式建立的。

d. Hazelcast效率很高。
将数据存储在内存中,所以是非常高效的,包括读操作和写操作。

e. Hazelcast是可备份的
Hazelcast的数据会在多个节点上进行备份。一旦一个节点失败了,数据将会从别的节点上进行恢复。

f. Hazelcast页面元素齐全

页面可以看到map,list等数据内容,以及一些容量的仪表图.可以看到很多有用的数据,包括每个Map的请求次数等.

 

2. 使用场景
分布式缓存,通常使用在数据库之前的那一层
缓存服务器
NoSql的数据存储
Spring cache
微服务的结构
储存临时数据,如web的session等

3. Hazelcast的数据拆分
Hazelcast的数据拆分叫做间隔(Partitions)。默认情况下,Hazelcast会将数据拆分成271个间隔(总数,并不是单个单点)。当传入一个key时,Hazelcast会对它进行序列化,以及进行hash的算法等算出一个数值,通过该数值它存放在相应的间隔中(271个的其中一个)。在不同的节点中存放相同数量的间隔。Hazelcast还会生成备份的间隔,同样也是存放在这些间隔中。

 

4.配置

配置组连接选项

    <group>
        <name>test</name>
        <password>test</password>
    </group>

配置管理页面,如果配置为true,则可以在mancenter目录下启动页面管理

<management-center enabled="true">http://10.1.4.97:8080/mancenter</management-center>

配置连接网络(自增长如果配为true,端口启动失败时会自增)

        <port auto-increment="true" port-count="100">5701</port>

集群节点发现机制,自选一种

        <join>
            <multicast enabled="false">
                <multicast-group>224.2.2.3</multicast-group>
                <multicast-port>54327</multicast-port>
            </multicast>
            <tcp-ip enabled="true">
                <interface>10.10.10.10</interface>
                <member-list>
                    <member>10.10.10.10</member>
                </member-list>
            </tcp-ip>
            <aws enabled="false">
                <access-key>my-access-key</access-key>
                <secret-key>my-secret-key</secret-key>
                <!--optional, default is us-east-1 -->
                <region>us-west-1</region>
                <!--optional, default is ec2.amazonaws.com. If set, region shouldn't be set as it will override this property -->
                <host-header>ec2.amazonaws.com</host-header>
                <!-- optional, only instances belonging to this group will be discovered, default will try all running instances -->
                <security-group-name>hazelcast-sg</security-group-name>
                <tag-key>type</tag-key>
                <tag-value>hz-nodes</tag-value>
            </aws>
        </join>

配置节点对套接字加密,算法可选

        <symmetric-encryption enabled="false">
            <!--
               encryption algorithm such as
               DES/ECB/PKCS5Padding,
               PBEWithMD5AndDES,
               AES/CBC/PKCS5Padding,
               Blowfish,
               DESede
            -->
            <algorithm>PBEWithMD5AndDES</algorithm>
            <!-- salt value to use when generating the secret key -->
            <salt>thesalt</salt>
            <!-- pass phrase to use when generating the secret key -->
            <password>thepass</password>
            <!-- iteration count to use when generating the secret key -->
            <iteration-count>19</iteration-count>
        </symmetric-encryption>

配置执行服务器的线程和队列容量

    <executor-service name="default">
        <pool-size>16</pool-size>
        <!--Queue capacity. 0 means Integer.MAX_VALUE.-->
        <queue-capacity>0</queue-capacity>
    </executor-service>