- Hazelcast 教程
- Hazelcast - 首页
- Hazelcast - 简介
- Hazelcast - 设置
- Hazelcast - 第一个应用
- Hazelcast - 配置
- 设置多节点实例
- Hazelcast - 数据结构
- Hazelcast - 客户端
- Hazelcast - 序列化
- Hazelcast 高级特性
- Hazelcast - Spring 集成
- Hazelcast - 监控
- Map Reduce & 聚合
- Hazelcast - 集合监听器
- 常见问题 & 性能技巧
- Hazelcast 有用资源
- Hazelcast 快速指南
- Hazelcast - 有用资源
- Hazelcast - 讨论
Hazelcast 快速指南
Hazelcast - 简介
分布式内存数据网格
数据网格是分布式缓存的超集。分布式缓存通常仅用于存储和检索跨缓存服务器分布的键值对。但是,数据网格除了支持存储键值对之外,还支持其他功能,例如:
它支持其他数据结构,例如锁、信号量、集合、列表和队列。
它提供了一种通过丰富的查询语言(例如 SQL)查询存储数据的途径。
它提供了一个分布式执行引擎,有助于并行操作数据。
Hazelcast 的优势
支持多种数据结构 − Hazelcast 支持使用多种数据结构以及 Map。一些例子包括 Lock、Semaphore、Queue、List 等。
快速读写访问 − 鉴于所有数据都在内存中,Hazelcast 提供了非常高速的数据读写访问。
高可用性 − Hazelcast 支持跨机器分发数据,并额外支持备份。这意味着数据并非存储在单一机器上。因此,即使机器发生故障(这在分布式环境中经常发生),数据也不会丢失。
高性能 − Hazelcast 提供了可用于在多台工作机器之间分配工作负载/计算/查询的构造。这意味着计算/查询使用来自多台机器的资源,从而大大缩短了执行时间。
易于使用 − Hazelcast 实现并扩展了许多 java.util.concurrent 构造,这使得它非常易于使用并与代码集成。要在机器上开始使用 Hazelcast 的配置只需将 Hazelcast jar 添加到我们的类路径中。
Hazelcast 与其他缓存和键值存储的比较
将 Hazelcast 与其他缓存(如 Ehcache、Guava 和 Caffeine)进行比较可能并不十分有用。这是因为,与其他缓存不同,Hazelcast 是一个分布式缓存,即它将数据分布在机器/JVM 上。虽然 Hazelcast 也可在单个 JVM 上运行良好,但它在分布式环境中更有用。
同样,将它与 MongoDB 等数据库进行比较也没有多大用处。这是因为 Hazelcast 主要将数据存储在内存中(尽管它也支持写入磁盘)。因此,它提供高速的读写速度,但其局限性在于数据需要存储在内存中。
与其他数据存储不同,Hazelcast 还支持缓存/存储复杂数据类型并提供查询它们的接口。
但是,可以与也提供类似功能的Redis进行比较。
Hazelcast 与 Redis 的比较
就功能而言,Redis 和 Hazelcast 非常相似。但是,以下几点是 Hazelcast 优于 Redis 的地方:
从一开始就为分布式环境而构建 − 与最初作为单机缓存的 Redis 不同,Hazelcast 从一开始就为分布式环境而构建。
简单的集群扩展/缩容 − 在 Hazelcast 中,维护添加或删除节点的集群非常简单,例如,添加节点只需启动具有所需配置的节点即可。删除节点需要简单地关闭节点。Hazelcast 自动处理数据的划分等。对 Redis 进行相同的设置并执行上述操作需要更多注意和人工操作。
支持故障转移所需资源更少 − Redis 采用主从方法。为了实现故障转移,Redis 需要额外的资源来设置Redis Sentinel。这些 Sentinel 节点负责在原始主节点出现故障时将从节点提升为主节点。在 Hazelcast 中,所有节点都被视为平等的,其他节点会检测到节点故障。因此,节点出现故障的情况处理得相当透明,而且无需任何额外的监控服务器。
简单的分布式计算 − Hazelcast 通过其EntryProcessor提供了一个简单的接口,用于将代码发送到数据以进行并行处理。这减少了网络上的数据传输。Redis 也支持这一点,但是实现这一点需要了解 Lua 脚本,这增加了额外的学习曲线。
Hazelcast - 设置
Hazelcast 需要 Java 1.6 或更高版本。Hazelcast 也可以与 .NET、C++ 或其他基于 JVM 的语言(如 Scala 和 Clojure)一起使用。但是,在本教程中,我们将使用 Java 8。
在我们继续之前,以下是本教程将使用的项目设置。
hazelcast/ ├── com.example.demo/ │ ├── SingleInstanceHazelcastExample.java │ ├── MultiInstanceHazelcastExample.java │ ├── Server.java │ └── .... ├── pom.xml ├── target/ ├── hazelcast.xml ├── hazelcast-multicast.xml ├── ...
目前,我们只需创建包,即 hazelcast 目录内的 com.example.demo。然后,只需 cd 到该目录。我们将在接下来的部分中查看其他文件。
安装 Hazelcast
安装 Hazelcast 只需将 JAR 文件添加到您的构建文件中即可。POM 文件或 build.gradle 文件取决于您分别使用 Maven 还是 Gradle。
如果您使用的是 Gradle,将以下内容添加到 build.gradle 文件就足够了:
dependencies { compile "com.hazelcast:hazelcast:3.12.12” }
教程的 POM
我们将使用以下 POM 用于我们的教程:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>1.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Hazelcast</description> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast</artifactId> <version>3.12.12</version> </dependency> </dependencies> <!-- Below build plugin is not needed for Hazelcast, it is being used only to created a shaded JAR so that --> <!-- using the output i.e. the JAR becomes simple for testing snippets in the tutorial--> <build> <plugins> <plugin> <!-- Create a shaded JAR and specify the entry point class--> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
Hazelcast - 第一个应用
Hazelcast 可以独立运行(单节点),也可以运行多个节点以形成集群。让我们首先尝试启动单个实例。
单实例
示例
现在,让我们尝试创建和使用 Hazelcast 集群的单个实例。为此,我们将创建 SingleInstanceHazelcastExample.java 文件。
package com.example.demo; import java.util.Map; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class SingleInstanceHazelcastExample { public static void main(String... args){ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); System.out.println(“Hello world”); // perform a graceful shutdown hazelcast.shutdown(); } }
现在让我们编译代码并执行它:
mvn clean install java -cp target/demo-0.0.1-SNAPSHOT.jar com.example.demo.SingleInstanceHazelcastExample
输出
如果您执行上述代码,输出将是:
Hello World
但是,更重要的是,您还会注意到来自 Hazelcast 的日志行,这表示 Hazelcast 已启动。由于我们只运行一次此代码,即单个 JVM,因此我们的集群中只有一个成员。
Jan 30, 2021 10:26:51 AM com.hazelcast.config.XmlConfigLocator INFO: Loading 'hazelcast-default.xml' from classpath. Jan 30, 2021 10:26:51 AM com.hazelcast.instance.AddressPicker INFO: [LOCAL] [dev] [3.12.12] Prefer IPv4 stack is true. Jan 30, 2021 10:26:52 AM com.hazelcast.instance.AddressPicker INFO: [LOCAL] [dev] [3.12.12] Picked [localhost]:5701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5701], bind any local is true Jan 30, 2021 10:26:52 AM com.hazelcast.system ... Members {size:1, ver:1} [ Member [localhost]:5701 - 9b764311-9f74-40e5-8a0a-85193bce227b this ] Jan 30, 2021 10:26:56 AM com.hazelcast.core.LifecycleService INFO: [localhost]:5701 [dev] [3.12.12] [localhost]:5701 is STARTED ... You will also notice log lines from Hazelcast at the end which signifies Hazelcast was shutdown: INFO: [localhost]:5701 [dev] [3.12.12] Hazelcast Shutdown is completed in 784 ms. Jan 30, 2021 10:26:57 AM com.hazelcast.core.LifecycleService INFO: [localhost]:5701 [dev] [3.12.12] [localhost]:5701 is SHUTDOWN
集群:多实例
现在,让我们创建 MultiInstanceHazelcastExample.java 文件(如下所示),它将用于多实例集群。
package com.example.demo; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class MultiInstanceHazelcastExample { public static void main(String... args) throws InterruptedException{ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); //print the socket address of this member and also the size of the cluster System.out.println(String.format("[%s]: No. of hazelcast members: %s", hazelcast.getCluster().getLocalMember().getSocketAddress(), hazelcast.getCluster().getMembers().size())); // wait for the member to join Thread.sleep(30000); //perform a graceful shutdown hazelcast.shutdown(); } }
让我们在两个不同的 shell上执行以下命令:
java -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.MultiInstanceHazelcastExample
您会在第一个 shell上注意到 Hazelcast 实例已启动并已分配成员。请注意输出的最后一行,该行显示使用端口 5701 的单个成员。
Jan 30, 2021 12:20:21 PM com.hazelcast.internal.cluster.ClusterService INFO: [localhost]:5701 [dev] [3.12.12] Members {size:1, ver:1} [ Member [localhost]:5701 - b0d5607b-47ab-47a2-b0eb-6c17c031fc2f this ] Jan 30, 2021 12:20:21 PM com.hazelcast.core.LifecycleService INFO: [localhost]:5701 [dev] [3.12.12] [localhost]:5701 is STARTED [/localhost:5701]: No. of hazelcast members: 1
您会在第二个 shell上注意到 Hazelcast 实例已加入第一个实例。请注意输出的最后一行,该行显示现在有使用端口 5702 的两个成员。
INFO: [localhost]:5702 [dev] [3.12.12] Members {size:2, ver:2} [ Member [localhost]:5701 - b0d5607b-47ab-47a2-b0eb-6c17c031fc2f Member [localhost]:5702 - 037b5fd9-1a1e-46f2-ae59-14c7b9724ec6 this ] Jan 30, 2021 12:20:46 PM com.hazelcast.core.LifecycleService INFO: [localhost]:5702 [dev] [3.12.12] [localhost]:5702 is STARTED [/localhost:5702]: No. of hazelcast members: 2
Hazelcast - 配置
Hazelcast 支持基于程序的配置和基于 XML 的配置。但是,鉴于其易用性,基于 XML 的配置在生产中大量使用。但是 XML 配置在内部使用程序配置。
XML 配置
这些配置需要放在 hazelcast.xml 中。该文件按以下位置(按相同顺序)搜索,并从第一个可用位置选择:
通过系统属性 - Dhazelcast.config=/path/to/hazelcast.xml 将 XML 的位置传递给 JVM
当前工作目录中的 hazelcast.xml
类路径中的 hazelcast.xml
Hazelcast 提供的默认 hazelcast.xml
找到 XML 后,Hazelcast 将从 XML 文件加载所需的配置。
让我们用一个例子来试试。在当前目录中创建一个名为 hazelcast.xml 的 XML 文件。
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <!-- name of the instance --> <instance-name>XML_Hazelcast_Instance</instance-name> </hazelcast>
目前,XML 只包含用于验证的 Hazelcast XML 的模式位置。但更重要的是,它包含实例名称。
示例
现在创建一个包含以下内容的 XMLConfigLoadExample.java 文件。
package com.example.demo; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class XMLConfigLoadExample { public static void main(String... args) throws InterruptedException{ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); //specified the name written in the XML file System.out.println(String.format("Name of the instance: %s",hazelcast.getName())); //perform a graceful shutdown hazelcast.shutdown(); } }
使用以下命令执行上述 Java 文件:
java -Dhazelcast.config=hazelcast.xml -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.XMLConfigLoadExample
输出
上述命令的输出将是:
Jan 30, 2021 1:21:41 PM com.hazelcast.config.XmlConfigLocator INFO: Loading configuration hazelcast.xml from System property 'hazelcast.config' Jan 30, 2021 1:21:41 PM com.hazelcast.config.XmlConfigLocator INFO: Using configuration file at C:\Users\demo\eclipseworkspace\ hazelcast\hazelcast.xml ... Members {size:1, ver:1} [ Member [localhost]:5701 - 3d400aed-ddb9-4e59-9429-3ab7773e7e09 this ] Name of cluster: XML_Hazelcast_Instance
如您所见,Hazelcast 加载了配置并打印了在配置中指定的名称(最后一行)。
可以在 XML 中指定许多配置选项。完整列表可在以下位置找到:
随着教程的进行,我们将了解其中一些配置。
程序配置
如前所述,XML 配置最终是通过程序配置完成的。因此,让我们尝试对我们在 XML 配置中看到的相同示例进行程序配置。为此,让我们创建包含以下内容的 ProgramaticConfigLoadExample.java 文件。
示例
package com.example.demo; import com.hazelcast.config.Config; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class ProgramaticConfigLoadExample { public static void main(String... args) throws InterruptedException { Config config = new Config(); config.setInstanceName("Programtic_Hazelcast_Instance"); // initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(config); // specified the name written in the XML file System.out.println(String.format("Name of the instance: %s", hazelcast.getName())); // perform a graceful shutdown hazelcast.shutdown(); } }
让我们通过以下方式执行代码,而不传递任何 hazelcast.xml 文件:
java -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.ProgramaticConfigLoadExample
输出
上述代码的输出是:
Name of the instance: Programtic_Hazelcast_Instance
日志记录
为了避免依赖关系,Hazelcast 默认使用基于 JDK 的日志记录。但它也支持通过slf4j、log4j进行日志记录。例如,如果我们想通过 sl4j 和 logback 设置日志记录,我们可以更新 POM 以包含以下依赖项:
<!-- contains both sl4j bindings and the logback core --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
示例
定义一个 logback.xml 配置文件并将其添加到您的类路径中,例如 src/main/resources。
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="STDOUT" /> </root> <logger name="com.hazelcast" level="error"> <appender-ref ref="STDOUT" /> </logger> </configuration>
现在,当我们执行以下命令时,我们会注意到有关 Hazelcast 成员创建等的元信息未打印。这是因为我们将 Hazelcast 的日志级别设置为错误,并要求 Hazelcast 使用 sl4j 记录器。
java -Dhazelcast.logging.type=slf4j -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.SingleInstanceHazelcastExample
输出
John
变量
写入 XML 配置文件的值可能会因环境而异。例如,在生产环境中,您可能使用与开发环境不同的用户名/密码连接到 Hazelcast 集群。无需维护单独的 XML 文件,也可以在 XML 文件中编写变量,然后通过命令行或以编程方式将这些变量传递给 Hazelcast。这是一个从命令行选择实例名称的示例。
因此,这是我们的包含变量 ${varname} 的 XML 文件
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <instance-name>${instance_name}</instance-name> </hazelcast>
示例
这是我们将用来打印变量值的示例 Java 代码:
package com.example.demo; import java.util.Map; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class XMLConfigLoadWithVariable { public static void main(String... args) throws InterruptedException { // initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); // specified the name written in the XML file System.out.println(String.format("Name of the instance: %s", hazelcast.getName())); // perform a graceful shutdown hazelcast.shutdown(); } }
以下是命令:
java -Dhazelcast.config=others\hazelcast.xml -Dinstance_name=dev_cluster -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.XMLConfigLoadWithVariable
输出
输出显示 Hazelcast 正确替换了变量。
Name of the instance: dev_cluster
Hazelcast - 设置多节点实例
鉴于 Hazelcast 是一个分布式 IMDG,通常在多台机器上设置,它需要访问内部/外部网络。最重要的用例是在集群内发现 Hazelcast 节点。
Hazelcast 需要以下端口:
1 个入站端口,用于接收来自其他 Hazelcast 节点/客户端的 ping/数据
n 个出站端口,用于向集群的其他成员发送 ping/数据。
节点发现通过几种方式发生:
组播
TCP/IP
Amazon EC2 自动发现
其中,我们将了解组播和 TCP/IP
组播
默认情况下启用多播加入机制。https://en.wikipedia.org/wiki/Multicast 是一种通信方式,其中消息被传输到组中的所有节点。Hazelcast 使用此方法来发现集群中的其他成员。我们之前看过的所有示例都使用多播来发现成员。
示例
现在让我们明确地启用它。将以下内容保存到 hazelcast-multicast.xml 中
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <network> <join> <multicast enabled="true" /> </join> </network> </hazelcast>
然后,让我们执行以下操作:
java -Dhazelcast.config=hazelcast-multicast.xml -cp .\target\demo-0.0.1- SNAPSHOT.jar com.example.demo.XMLConfigLoadExample
输出
在输出中,我们注意到 Hazelcast 的以下几行,这有效地意味着使用多播连接器来发现成员。
Jan 30, 2021 5:26:15 PM com.hazelcast.instance.Node INFO: [localhost]:5701 [dev] [3.12.12] Creating MulticastJoiner
默认情况下,多播接受来自多播组中所有机器的通信。这可能是一个安全问题,这就是为什么通常在内部部署中,多播通信会被防火墙阻止的原因。因此,虽然多播非常适合开发工作,但在生产环境中,最好使用基于 TCP/IP 的发现机制。
TCP/IP
由于多播的缺点,TCP/IP 是首选的通信方式。对于 TCP/IP,成员只能连接到已知/列出的成员。
示例
让我们使用 TCP/IP 进行发现机制。将以下内容保存到 hazelcast-tcp.xml 中
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <network> <join> <multicast enabled="false" /> <tcp-ip enabled="true"> <members>localhost</members> </tcp-ip> </join> </network> </hazelcast>
然后,让我们执行以下命令:
java -Dhazelcast.config=hazelcast-tcp.xml -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.XMLConfigLoadExample
输出
输出如下:
INFO: [localhost]:5701 [dev] [3.12.12] Creating TcpIpJoiner Jan 30, 2021 8:09:29 PM com.hazelcast.spi.impl.operationexecutor.impl.OperationExecutorImpl
上述输出显示使用 TCP/IP 连接器加入了两个成员。
如果您在两个不同的 shell 上执行以下命令:
java '-Dhazelcast.config=hazelcast-tcp.xml' -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.MultiInstanceHazelcastExample
我们将看到以下输出:
Members {size:2, ver:2} [ Member [localhost]:5701 - 62eedeae-2701-4df0-843c-7c3655e16b0f Member [localhost]:5702 - 859c1b46-06e6-495a-8565-7320f7738dd1 this ]
上述输出意味着节点能够使用 TCP/IP 加入,并且两者都使用 localhost 作为 IP 地址。
请注意,我们可以在 XML 配置文件中指定更多 IP 或机器名称(这些名称将由 DNS 解析)。
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <network> <join> <multicast enabled="false" /> <tcp-ip enabled="true"> <members>machine1, machine2....</members> </tcp-ip> </join> </network> </hazelcast>
Hazelcast - 数据结构
java.util.concurrent 包提供了诸如 AtomicLong、CountDownLatch、ConcurrentHashMap 等数据结构,当多个线程读取/写入数据到数据结构时,这些数据结构非常有用。但是为了提供线程安全性,所有这些线程都应该在单个 JVM/机器上。
分布式数据结构有两个主要优点:
性能提升 - 如果多台机器可以访问数据,它们都可以并行工作,并在更短的时间内完成工作。
数据备份 - 如果一个 JVM/机器宕机,我们还有其他 JVM/机器保存着数据。
Hazelcast 提供了一种跨 JVM/机器分布数据结构的方法。
Hazelcast - 客户端
Hazelcast 客户端是 Hazelcast 成员的轻量级客户端。Hazelcast 成员负责存储数据和分区。它们在传统的客户端-服务器模型中充当服务器。
创建 Hazelcast 客户端只是为了访问存储在集群 Hazelcast 成员中的数据。它们不负责存储数据,也不承担任何存储数据的责任。
客户端具有自己的生命周期,并且不会影响 Hazelcast 成员实例。
首先让我们创建 Server.java 并运行它。
import java.util.Map; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class Server { public static void main(String... args){ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); //create a simple map Map<String, String> vehicleOwners = hazelcast.getMap("vehicleOwnerMap"); // add key-value to map vehicleOwners.put("John", "Honda-9235"); // do not shutdown, let the server run //hazelcast.shutdown(); } }
现在,运行上面的类。
java -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.Server
为了设置客户端,我们还需要添加客户端 jar。
<dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-client</artifactId> <version>3.12.12</version> </dependency>
现在让我们创建 Client.java。请注意,与 Hazelcast 成员类似,客户端也可以通过编程方式或通过 XML 配置进行配置(即,通过 -Dhazelcast.client.config 或 hazelcast-client.xml)。
示例
让我们使用默认配置,这意味着我们的客户端能够连接到本地实例。
import java.util.Map; import com.hazelcast.client.HazelcastClient; import com.hazelcast.core.HazelcastInstance; public class Client { public static void main(String... args){ //initialize hazelcast client HazelcastInstance hzClient = HazelcastClient.newHazelcastClient(); //read from map Map<String, String> vehicleOwners = hzClient.getMap("vehicleOwnerMap"); System.out.println(vehicleOwners.get("John")); System.out.println("Member of cluster: " + hzClient.getCluster().getMembers()); // perform shutdown hzClient.getLifecycleService().shutdown(); } }
现在,运行上面的类。
java -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.Client
输出
它将产生以下输出:
Honda-9235 Member of cluster: [Member [localhost]:5701 - a47ec375-3105-42cd-96c7-fc5eb382e1b0]
从输出中可以看到:
集群只包含一个成员,来自 Server.java。
客户端能够访问存储在服务器中的映射。
负载均衡
Hazelcast 客户端支持使用各种算法进行负载均衡。负载均衡确保负载在成员之间共享,并且集群的单个成员不会过载。默认负载均衡机制设置为轮询。可以通过在配置中使用 loadBalancer 标签来更改它。
我们可以使用配置中的 load-balancer 标签指定负载均衡器的类型。以下是一个选择随机选择节点的策略的示例。
<hazelcast-client xmlns="http://www.hazelcast.com/schema/client-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.hazelcast.com/schema/client-config http://www.hazelcast.com/schema/client-config/hazelcastclient-config-4.2.xsd"> <load-balancer type="random"/> </hazelcast-client>
故障转移
在分布式环境中,成员可能会任意失败。为了支持故障转移,建议提供多个成员的地址。如果客户端可以访问任何一个成员,则足以将其寻址到其他成员。可以在客户端配置中指定参数 addressList。
例如,如果我们使用以下配置:
<hazelcast-client xmlns="http://www.hazelcast.com/schema/client-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.hazelcast.com/schema/client-config http://www.hazelcast.com/schema/client-config/hazelcastclient-config-4.2.xsd"> <address-list>machine1, machine2</address-list> </hazelcast-client>
即使 machine1 宕机,客户端也可以使用 machine2 来访问集群中的其他成员。
Hazelcast - 序列化
Hazelcast 最适合用于数据/查询分布在多台机器上的环境。这需要将数据从我们的 Java 对象序列化为可以跨网络传输的字节数组。
Hazelcast 支持各种类型的序列化。但是,让我们来看一些常用的序列化方式,即 Java 序列化和 Java Externalizable。
Java 序列化
示例
首先让我们来看一下 Java 序列化。假设我们定义了一个实现了 Serializable 接口的 Employee 类。
public class Employee implements Serializable{ private static final long serialVersionUID = 1L; private String name; private String department; public Employee(String name, String department) { super(); this.name = name; this.department = department; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } @Override public String toString() { return "Employee [name=" + name + ", department=" + department + "]"; } }
现在让我们编写代码将 Employee 对象添加到 Hazelcast 映射中。
public class EmployeeExample { public static void main(String... args){ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); //create a set to track employees Map<Employee, String> employeeOwners=hazelcast.getMap("employeeVehicleMap"); Employee emp1 = new Employee("John Smith", "Computer Science"); // add employee to set System.out.println("Serializing key-value and add to map"); employeeOwners.put(emp1, "Honda"); // check if emp1 is present in the set System.out.println("Serializing key for searching and Deserializing value got out of map"); System.out.println(employeeOwners.get(emp1)); // perform a graceful shutdown hazelcast.shutdown(); } }
输出
它将产生以下输出:
Serializing key-value and add to map Serializing key for searching and Deserializing value got out of map Honda
这里非常重要的一点是,只需实现 Serializable 接口,我们就可以让 Hazelcast 使用 Java 序列化。还要注意,Hazelcast 存储键和值的序列化数据,而不是像 HashMap 一样将其存储在内存中。因此,Hazelcast 承担了序列化和反序列化的重任。
示例
但是,这里有一个陷阱。在上面的例子中,如果员工的部门发生了变化怎么办?这个人仍然是同一个人。
public class EmployeeExampleFailing { public static void main(String... args){ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); //create a set to track employees Map<Employee, String> employeeOwners=hazelcast.getMap("employeeVehicleMap"); Employee emp1 = new Employee("John Smith", "Computer Science"); // add employee to map System.out.println("Serializing key-value and add to map"); employeeOwners.put(emp1, "Honda"); Employee empDeptChange = new Employee("John Smith", "Electronics"); // check if emp1 is present in the set System.out.println("Checking if employee with John Smith is present"); System.out.println(employeeOwners.containsKey(empDeptChange)); Employee empSameDept = new Employee("John Smith", "Computer Science"); System.out.println("Checking if employee with John Smith is present"); System.out.println(employeeOwners.containsKey(empSameDept)); // perform a graceful shutdown hazelcast.shutdown(); } }
输出
它将产生以下输出:
Serializing key-value and add to map Checking if employee with name John Smith is present false Checking if employee with name John Smith is present true
这是因为 Hazelcast 在比较时不会反序列化键,即 Employee。它直接比较序列化键的字节码。因此,具有与所有属性相同值的同一个对象将被视为相同。但是,如果这些属性的值发生变化,例如上述场景中的部门,则这两个键将被视为唯一。
Java Externalizable
如果在上面的例子中,我们在对键进行序列化/反序列化时不关心部门的值怎么办?Hazelcast 还支持 Java Externalizable,它使我们可以控制用于序列化和反序列化的标记。
示例
让我们相应地修改我们的 Employee 类:
public class EmplyoeeExternalizable implements Externalizable { private static final long serialVersionUID = 1L; private String name; private String department; public EmplyoeeExternalizable(String name, String department) { super(); this.name = name; this.department = department; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Deserializaing...."); this.name = in.readUTF(); } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Serializing...."); out.writeUTF(name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } @Override public String toString() { return "Employee [name=" + name + ", department=" + department + "]"; } }
因此,正如您从代码中看到的,我们添加了 readExternal/writeExternal 方法,这些方法负责序列化/反序列化。鉴于我们在序列化/反序列化时对部门不感兴趣,我们在 readExternal/writeExternal 方法中排除了这些部门。
示例
现在,如果我们执行以下代码:
public class EmployeeExamplePassing { public static void main(String... args){ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); //create a set to track employees Map<EmplyoeeExternalizable, String> employeeOwners=hazelcast.getMap("employeeVehicleMap"); EmplyoeeExternalizable emp1 = new EmplyoeeExternalizable("John Smith", "Computer Science"); // add employee to map employeeOwners.put(emp1, "Honda"); EmplyoeeExternalizable empDeptChange = new EmplyoeeExternalizable("John Smith", "Electronics"); // check if emp1 is present in the set System.out.println("Checking if employee with John Smith is present"); System.out.println(employeeOwners.containsKey(empDeptChange)); EmplyoeeExternalizable empSameDept = new EmplyoeeExternalizable("John Smith", "Computer Science"); System.out.println("Checking if employee with John Smith is present"); System.out.println(employeeOwners.containsKey(empSameDept)); // perform a graceful shutdown hazelcast.shutdown(); } }
输出
我们得到的输出是:
Serializing.... Checking if employee with John Smith is present Serializing.... true Checking if employee with John Smith is present Serializing.... true
正如输出所示,使用 Externalizable 接口,我们可以为 Hazelcast 提供仅包含员工名称的序列化数据。
还要注意,Hazelcast 会对我们的键进行两次序列化:
一次是在存储键时,
第二次是在映射中搜索给定键时。如前所述,这是因为 Hazelcast 使用序列化的字节数组进行键比较。
总的来说,如果我们想要更好地控制要序列化的属性以及如何处理它们,与 Serializable 相比,使用 Externalizable 具有更多优势。
Hazelcast - Spring 集成
Hazelcast 提供了一种简单的方法来与 Spring Boot 应用程序集成。让我们通过一个例子来理解这一点。
我们将创建一个简单的 API 应用程序,该应用程序提供一个 API 来获取公司员工信息。为此,我们将使用 Spring Boot 驱动的 RESTController 以及 Hazelcast 来缓存数据。
请注意,为了在 Spring Boot 中集成 Hazelcast,我们需要两样东西:
将 Hazelcast 添加为我们项目的依赖项。
定义一个配置(静态的或编程的),并使其可用于 Hazelcast。
让我们首先定义 POM。请注意,我们必须指定 Hazelcast JAR 才能在 Spring Boot 项目中使用它。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>hazelcast</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project to explain Hazelcast integration with Spring Boot</description> <properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-all</artifactId> <version>4.0.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
还要将 hazelcast.xml 添加到 src/main/resources 中:
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <instance-name>XML_Hazelcast_Instance</instance-name> </hazelcast>
定义 Spring Boot 使用的入口文件。确保我们已指定 @EnableCaching:
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @EnableCaching @SpringBootApplication public class CompanyApplication { public static void main(String[] args) { SpringApplication.run(CompanyApplication.class, args); } }
让我们定义我们的员工 POJO:
package com.example.demo; import java.io.Serializable; public class Employee implements Serializable{ private static final long serialVersionUID = 1L; private int empId; private String name; private String department; public Employee(Integer id, String name, String department) { super(); this.empId = id; this.name = name; this.department = department; } public int getEmpId() { return empId; } public void setEmpId(int empId) { this.empId = empId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } @Override public String toString() { return "Employee [empId=" + empId + ", name=" + name + ", department=" + department + "]"; } }
最终,让我们定义一个基本的 REST 控制器来访问员工:
package com.example.demo; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/v1/") class CompanyApplicationController{ @Cacheable(value = "employee") @GetMapping("employee/{id}") public Employee getSubscriber(@PathVariable("id") int id) throws InterruptedException { System.out.println("Finding employee information with id " + id + " ..."); Thread.sleep(5000); return new Employee(id, "John Smith", "CS"); } }
现在让我们通过运行以下命令来执行上述应用程序:
mvn clean install mvn spring-boot:run
您会注意到,该命令的输出将包含 Hazelcast 成员信息,这意味着使用 hazelcast.xml 配置会自动为我们配置 Hazelcast 实例。
Members {size:1, ver:1} [ Member [localhost]:5701 - 91b3df1d-a226-428a-bb74-6eec0a6abb14 this ]
现在让我们通过 curl 或使用浏览器访问 API:
curl -X GET https://127.0.0.1:8080/v1/employee/5
API 的输出将是我们的示例员工。
{ "empId": 5, "name": "John Smith", "department": "CS" }
在服务器日志(即 Spring Boot 应用程序运行的位置)中,我们看到以下行:
Finding employee information with id 5 ...
但是,请注意,访问信息需要近 5 秒(因为我们添加了休眠)。但是,如果我们再次调用 API,API 的输出会立即显示。这是因为我们指定了 @Cacheable 注解。我们第一次 API 调用的数据已使用 Hazelcast 作为后端进行缓存。
Hazelcast - 监控
Hazelcast 提供多种监控集群的方法。我们将研究如何通过 REST API 和 JMX 进行监控。让我们首先研究 REST API。
通过 REST API 监控 Hazelcast
要通过 REST API 监控集群的运行状况或成员状态,必须启用成员之间的基于 REST API 的通信。这可以通过配置和编程方式完成。
让我们通过 hazelcast-monitoring.xml 中的 XML 配置启用基于 REST 的监控:
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <instance-name>XML_Hazelcast_Instance</instance-name> <network> <rest-api enabled="true"> <endpoint-group name="CLUSTER_READ" enabled="true" /> <endpoint-group name="HEALTH_CHECK" enabled="true" /> </rest-api> </network> </hazelcast>
让我们创建一个在 Server.java 文件中无限期运行的 Hazelcast 实例:
public class Server { public static void main(String... args){ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); // do not shutdown, let the server run //hazelcast.shutdown(); } }
现在让我们执行启动集群:
java '-Dhazelcast.config=hazelcast-monitoring.xml' -cp .\target\demo-0.0.1- SNAPSHOT.jar com.example.demo.Server
启动后,可以通过调用 API 来找出集群的运行状况,例如:
https://127.0.0.1:5701/hazelcast/health
上述 API 调用的输出:
Hazelcast::NodeState=ACTIVE Hazelcast::ClusterState=ACTIVE Hazelcast::ClusterSafe=TRUE Hazelcast::MigrationQueueSize=0 Hazelcast::ClusterSize=1
这显示我们的集群中有一个成员,并且它是活动的。
可以使用以下方法查找有关节点的更多详细信息,例如 IP、端口、名称:
https://127.0.0.1:5701/hazelcast/rest/cluster
上述 API 的输出:
Members {size:1, ver:1} [ Member [localhost]:5701 - e6afefcb-6b7c-48b3-9ccb-63b4f147d79d this ] ConnectionCount: 1 AllConnectionCount: 2
JMX 监控
Hazelcast 还支持监控其内部嵌入的数据结构,例如 IMap、Iqueue 等。
要启用 JMX 监控,我们首先需要启用基于 JVM 的 JMX 代理。这可以通过将“-Dcom.sun.management.jmxremote”传递给 JVM 来完成。为了使用不同的端口或使用身份验证,我们可以分别使用 -Dcom.sun.management.jmxremote.port、-Dcom.sun.management.jmxremote.authenticate。
除此之外,我们还必须为 Hazelcast MBean 启用 JMX。让我们通过 hazelcast-monitoring.xml 中的 XML 配置启用基于 JMX 的监控:
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.12.12.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <instance-name>XML_Hazelcast_Instance</instance-name> <properties> <property name="hazelcast.jmx">true</property> </properties> </hazelcast>
让我们在 Server.java 文件中创建一个无限期运行的 Hazelcast 实例并添加一个映射:
class Server { public static void main(String... args){ //initialize hazelcast server/instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); //create a simple map Map<String, String> vehicleOwners = hazelcast.getMap("vehicleOwnerMap"); // add key-value to map vehicleOwners.put("John", "Honda-9235"); // do not shutdown, let the server run //hazelcast.shutdown(); } }
现在我们可以执行以下命令来启用 JMX:
java '-Dcom.sun.management.jmxremote' '-Dhazelcast.config=others\hazelcastmonitoring. xml' -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.Server
现在可以通过 JMX 客户端(如 jConsole、VisualVM 等)连接 JMX 端口。
以下是使用jConsole连接并查看VehicleMap属性后获得的快照。可以看到,地图名称为vehicleOwnerMap,地图大小为1。
Hazelcast - Map Reduce与聚合
MapReduce是一种计算模型,当您拥有大量数据并且需要多台机器(即分布式环境)来计算数据时,它非常有用。它涉及将数据“映射”到键值对,然后“归约”,即对这些键进行分组并在值上执行操作。
鉴于Hazelcast的设计考虑了分布式环境,因此实现Map-Reduce框架是很自然的。
让我们来看一个例子。
例如,假设我们有关于汽车(品牌和车牌号)及其车主的数据。
Honda-9235, John Hyundai-235, Alice Honda-935, Bob Mercedes-235, Janice Honda-925, Catnis Hyundai-1925, Jane
现在,我们必须找出每个品牌的汽车数量,例如现代、本田等。
示例
让我们尝试使用MapReduce来找出答案:
package com.example.demo; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.ICompletableFuture; import com.hazelcast.core.IMap; import com.hazelcast.mapreduce.Context; import com.hazelcast.mapreduce.Job; import com.hazelcast.mapreduce.JobTracker; import com.hazelcast.mapreduce.KeyValueSource; import com.hazelcast.mapreduce.Mapper; import com.hazelcast.mapreduce.Reducer; import com.hazelcast.mapreduce.ReducerFactory; public class MapReduce { public static void main(String[] args) throws ExecutionException, InterruptedException { try { // create two Hazelcast instances HazelcastInstance hzMember = Hazelcast.newHazelcastInstance(); Hazelcast.newHazelcastInstance(); IMap<String, String> vehicleOwnerMap=hzMember.getMap("vehicleOwnerMap"); vehicleOwnerMap.put("Honda-9235", "John"); vehicleOwnerMap.putc"Hyundai-235", "Alice"); vehicleOwnerMap.put("Honda-935", "Bob"); vehicleOwnerMap.put("Mercedes-235", "Janice"); vehicleOwnerMap.put("Honda-925", "Catnis"); vehicleOwnerMap.put("Hyundai-1925", "Jane"); KeyValueSource<String, String> kvs=KeyValueSource.fromMap(vehicleOwnerMap); JobTracker tracker = hzMember.getJobTracker("vehicleBrandJob"); Job<String, String> job = tracker.newJob(kvs); ICompletableFuture<Map<String, Integer>> myMapReduceFuture = job.mapper(new BrandMapper()) .reducer(new BrandReducerFactory()).submit(); Map<String, Integer&g; result = myMapReduceFuture.get(); System.out.println("Final output: " + result); } finally { Hazelcast.shutdownAll(); } } private static class BrandMapper implements Mapper<String, String, String, Integer> { @Override public void map(String key, String value, Context<String, Integer> context) { context.emit(key.split("-", 0)[0], 1); } } private static class BrandReducerFactory implements ReducerFactory<String, Integer, Integer> { @Override public Reducer<Integer, Integer> newReducer(String key) { return new BrandReducer(); } } private static class BrandReducer extends Reducer<Integer, Integer> { private AtomicInteger count = new AtomicInteger(0); @Override public void reduce(Integer value) { count.addAndGet(value); } @Override public Integer finalizeReduce() { return count.get(); } } }
让我们尝试理解这段代码:
- 我们创建Hazelcast成员。在这个例子中,我们只有一个成员,但也可以有多个成员。
我们使用虚拟数据创建一个地图,并从中创建一个键值存储。
我们创建一个Map-Reduce作业,并要求它使用键值存储作为数据。
然后我们将作业提交到集群并等待完成。
映射器创建一个键,即从原始键中提取品牌信息,并将值设置为1,然后将该信息作为K-V发出给归约器。
归约器简单地对值求和,根据键(即品牌名称)对数据进行分组。
输出
代码输出:
Final output: {Mercedes=1, Hyundai=2, Honda=3}
Hazelcast - 集合监听器
当给定的集合(例如队列、集合、列表等)更新时,Hazelcast支持添加监听器。典型的事件包括条目添加和条目删除。
让我们通过一个例子看看如何实现集合监听器。假设我们想要实现一个跟踪集合中元素数量的监听器。
示例
首先,让我们实现生产者:
public class SetTimedProducer{ public static void main(String... args) throws IOException, InterruptedException { //initialize hazelcast instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); Thread.sleep(5000); // create a set ISet<String> hzFruits = hazelcast.getSet("fruits"); hzFruits.add("Mango"); Thread.sleep(2000); hzFruits.add("Apple"); Thread.sleep(2000); hzFruits.add("Banana"); System.exit(0); } }
现在让我们实现监听器:
package com.example.demo; import java.io.IOException; import com.hazelcast.core.ISet; import com.hazelcast.core.ItemEvent; import com.hazelcast.core.ItemListener; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class SetListener{ public static void main(String... args) throws IOException, InterruptedException { //initialize hazelcast instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); // create a set ISet<String> hzFruits = hazelcast.getSet("fruits"); ItemListener<String> listener = new FruitListener<String>(); hzFruits.addItemListener(listener, true); System.exit(0); } private static class FruitListener<String> implements ItemListener<String> { private int count = 0; @Override public void itemAdded(ItemEvent<String> item) { System.out.println("item added" + item); count ++; System.out.println("Total elements" + count); } @Override public void itemRemoved(ItemEvent<String> item) { count --; } } }
我们将首先运行生产者:
java -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.SetTimedProducer
然后,我们运行监听器并让它无限期运行:
java -cp .\target\demo-0.0.1-SNAPSHOT.jar com.example.demo.SetListener
输出
监听器的输出如下:
item added: ItemEvent{ event=ADDED, item=Mango, member=Member [localhost]:5701-c28a60b7-3259-44bf-8793-54063d244394 this} Total elements: 1 item added: ItemEvent{ event=ADDED, item=Apple, member=Member [localhost]:5701-c28a60b7-3259-44bf-8793-54063d244394 this} Total elements: 2 item added: ItemEvent{ event=ADDED, item=Banana, member=Member [localhost]:5701-c28a60b7-3259-44bf-8793-54063d244394 this} Total elements: 3
调用hzFruits.addItemListener(listener, true) 告诉Hazelcast提供成员信息。如果设置为false,我们只会收到添加/删除条目的通知。这有助于避免需要序列化和反序列化条目以使其可供监听器访问。
Hazelcast - 常见陷阱与性能提示
单机Hazelcast队列
Hazelcast队列存储在一个成员上(以及不同机器上的备份)。这实际上意味着队列可以容纳单台机器上可以容纳的任意数量的项目。因此,添加更多成员不会扩展队列容量。加载超过一台机器在队列中可以处理的数据可能会导致机器崩溃。
使用Map的set方法而不是put方法
如果我们使用IMap的put(key, newValue),Hazelcast会返回oldValue。这意味着,在反序列化中花费了额外的计算和时间。这也包括从网络发送的更多数据。相反,如果我们对oldValue不感兴趣,我们可以使用set(key, value),它返回void。
让我们看看如何存储和注入对Hazelcast结构的引用。以下代码创建了一个名为“stock”的地图,并在一个地方添加芒果,在另一个地方添加苹果。
//initialize hazelcast instance HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(); // create a map IMap<String, String> hzStockTemp = hazelcast.getMap("stock"); hzStock.put("Mango", "4"); IMap<String, String> hzStockTemp2 = hazelcast.getMap("stock"); hzStock.put("Apple", "3");
然而,这里的问题是我们两次使用了getMap("stock")。虽然在单节点环境中此调用看起来无害,但在集群环境中会造成速度缓慢。函数调用getMap()涉及到与集群其他成员的网络往返。
因此,建议我们将对地图的引用存储在本地,并在操作地图时使用该引用。例如:
// create a map IMap<String, String> hzStock = hazelcast.getMap("stock"); hzStock.put("Mango", "4"); hzStock.put("Apple", "3");
Hazelcast使用序列化数据进行对象比较
正如我们在前面的例子中看到的,务必注意Hazelcast在比较键时不使用反序列化对象。因此,它无法访问我们在equals/hashCode方法中编写的代码。根据Hazelcast,如果两个Java对象的属性值相同,则键相等。
使用监控
在大规模分布式系统中,监控起着非常重要的作用。使用REST API和JMX进行监控对于采取主动措施而不是被动措施非常重要。
同构集群
Hazelcast假设所有机器都是相同的,即所有机器具有相同的资源。但是,如果我们的集群包含一台性能较低的机器(例如,内存较少,CPU能力较低等),那么如果计算发生在那台机器上,就会造成速度缓慢。更糟糕的是,较弱的机器可能会耗尽资源,导致级联故障。因此,Hazelcast成员必须具有相同的资源能力。