文档章节

搭建第一个Ignite集群时的注意事项

李玉珏
 李玉珏
发布于 03/27 12:35
字数 3483
阅读 1237
收藏 0

开发者在搭建第一个Ignite集群时,常常会遇到各种障碍,社区在收集了各种常见问题后,整理了一份检查清单帮助开发者,总之,本文的目的是帮助开发者在一开始就搭建一个正常的集群,走在正确的道路上。

配置日志

准备启动:设置日志

首先,需要日志,因为在解决很多问题的时候,需要每个节点的日志。

虽然Ignite默认是开启日志记录的,但是默认为QUIET模式,会忽略掉INFODEBUG级别的日志输出,如果系统属性IGNITE_QUIET配置为false,则Ignite将以正常的、没有限制的日志记录模式运行,注意,所有QUIET模式的日志都会输出到标准输出(STDOUT)。

只有严重的日志才会出现在控制台中,其它的日志都会记录在文件中,默认位置为${IGNITE_HOME}/work/log,注意不要删除它,以后可能有用。

Ignite以默认方式启动时的标准输出

对于一些简单问题的处理,不需要做单独的监控,只需要在命令行中以详细模式启动即可:

然后,系统会将所有事件与其它一些应用日志信息一起,输出到标准输出中。

这时,再有问题就可能从日志中找到解决方案,比如如果集群崩溃,可能会发现"在某某配置处增加某某超时配置"之类的信息,这就说明,该配置太小了,网络质量很差。

禁用组播

很多人遇到的第一个问题是,集群中出现了预期之外的节点,即启动一个节点后,在集群的拓扑快照中,不是一个节点,而是2个或者多个,怎么回事呢?

比如下图,指出集群中有2个节点:

出现这种情况的可能原因是,Ignite启动时默认使用组播,并在一个子网中查找位于同一组播组中的其它Ignite节点,找到后会尝试与其建立连接,如果连接失败,整个启动就会失败。

为了防止这种情况发生,最简单的做法就是使用静态IP地址,不是默认的TcpDiscoveryMulticastIpFinder而是TcpDiscoveryVmIpFinder,然后指定所有要连接的IP和端口,这会规避很多问题,尤其是在测试和开发环境中。

IP地址太多

另一个问题就是IP地址过多。禁用组播后再次启动集群,这时已经在配置中指定了来自不同环境的大量IP,有时第一个节点的启动需要5到10分钟,尽管后续每个节点的启动只需要5到10秒。

以IP地址列表为3个IP为例,对于每个地址,指定了10个端口的范围,这样总共得到了30个TCP地址,Ignite在创建新集群之前,默认会尝试连接到已有的集群,这样就会依次检查每个IP。

如果只是在自己的电脑上工作,这样问题不大,但是在云环境或者企业网络中,通常会启用端口扫描保护,这意味着如果要访问某IP上的专用端口时,可能在超时到期之前没有任何反馈,这个超时时间默认为10秒。

解决方案很简单,不要指定过多的端口,在生产上,一个端口就够了,当然也可以禁用内部网络的端口扫描保护。

IPv4和IPv6

第三个常见问题与IPv6有关,开发者有可能遇到一些奇怪的网络错误信息,比如:failed to connect或者failed to send a message, node segmented.等,如果已经从集群中分离,则会发生这种情况。通常来说这种问题是由IPv4和IPv6的异构环境造成的,Ignite虽然支持IPv6,但是目前存在一些问题。

最简单的解决方案是为JVM增加如下的参数:

之后Java和Ignite将不再使用IPv6,问题解决。

序列化/反序列化

集群搭建完成之后,业务代码和Ignite之间的主要交互点之一是Marshaller(序列化)。要将任何内容写入内存、持久化或通过网络发送,Ignite首先会将对象序列化,这时可能会看到类似cannot be written in binary formatcannot be serialized using BinaryMarshaller.这样的消息,这时就需要稍微调整一下代码以将其与Ignite结合使用。

Ignite支持3种序列化机制:

  • JdkMarshaller:常见的Java序列化;
  • OptimizedMarshaller:优化的Java序列化,但与JdkMarshaller基本一致;
  • BinaryMarshaller:一个专门为Ignite实现的序列化机制。它有很多优点,有时需要摆脱过多的序列化/反序列化,有时甚至可以为无法序列化的对象提供一个API接口,并以二进制格式处理它,就像JSON一样。

BinaryMarshaller能够对除了字段和简单方法之外什么都没有的POJO对象进行序列化和反序列化。但是如果通过readObject()writeObject()方法自定义了序列化,并且使用了Externalizable接口,那么BinaryMarshaller将无法对对象进行序列化,它会回归到OptimizedMarshaller

如果发生了这种情况,就必须实现Binarylizable接口。这非常简单。

例如有一个Java中的标准TreeMap,它通过readObject()writeObject()方法自定义了序列化和反序列化,首先它描述了一些字段,然后向OutputStream写入了长度和数据:

TreeMapwriteObject()实现:

BinarylizablewriteBinary()readBinary()工作方式类似:BinaryTreeMap将自身包装到简单的TreeMap中并将其写入OutputStream,这种方式对于编码来说微不足道,但是性能提升非常明显。

BinaryTreeMapwriteBinary()实现:

正确地使用Ignite实例

Ignite是支持分布式计算的,那么如何在所有服务器上执行lambda表达式呢?

首先看下下面的代码有什么错误:

或者这样:

熟悉lambda表达式和匿名类缺陷的人都知道,当引用外部变量时会出现问题,匿名类更复杂。

还有一个示例,这里再次使用Ignite的API执行lambda。

在闭包中使用Ignite实例的错误方式:

这个代码段的逻辑基本上是从Ignite中获取缓存并在本地执行一个SQL查询,当只需要处理远程节点上的本地数据时,这会很有用。

那么问题是什么呢?Lambda又引用了外部资源,但这次不是一般的对象,而是发送Lambda的节点上的本地Ignite实例。这样做可能可以运行,因为Ignite对象有一个readResolve()方法,它可以通过反序列化将通过网络传输的Ignite对象替换为目标节点上的本地对象,但这样做也可能产生预期之外的结果。

从本质上讲,虽然只要想要,可以通过网络传输尽可能多的数据,但是获取Ignite对象或者它的任意接口的最简单方法,是使用Ignition.localIgnite()。可以从Ignite创建的任何线程调用它,并获得对本地对象的引用。在Lambda、服务等等中,如果需要Ignite对象,都建议这样做。

在闭包中使用Ignite的正确方式:通过localIgnite()

这部分的最后一个示例,Ignite中有一个服务网格,它允许在集群中部署微服务,Ignite可以永久保持在线所需的实例数。想象一下,假如也需要在此服务中引用Ignite实例,怎么弄呢?其实也可以使用localIgnite(),但这时需要在字段中保留此引用。

它接收一个Ignite实例的引用作为构造函数的参数,这是错误的。

这个问题有更简单的解决办法,即使用@IgniteInstanceResource注释该字段,创建服务后,该实例会自动注入,然后就可以用了。建议开发者这样做,而不要传递Ignite对象或者它的子对象。

服务使用@IgniteInstanceResource后:

控制基线拓扑

现在集群已经搭建好,代码也有了。

考虑下下面的场景:

  • 一个REPLICATED模式的缓存:每个节点上都有数据副本;
  • 打开了原生持久化:写入磁盘。

先启动一个节点,因为开启了原生持久化,所以必须先激活才能用,激活之后,再启动一些其它的节点。

看上去是正常的:可以按照预期读取和写入数据,每个节点都有数据副本。关闭一个节点也是可以的,但是如果关闭第一个节点,就不行了,会发现出现了数据丢失,集群无法操作,这是由基线拓扑(存储持久化数据的节点集)引起的,因为其它节点并不持久化数据。

这个节点集在第一次激活时定义,后续添加的节点默认是不会包含在基线拓扑中的,因此目前的基线拓扑只包含最初的第一个节点,这个节点故障整个集群就会故障。为了避免这种情况,正确的做法是首先就要启动所有的节点,然后再激活集群,如果要往基线拓扑中添加或者删除节点,可以使用下面的命令:

此脚本还可以对基线进行刷新,使其保持最新状态。

control.sh的使用:

调整数据并置

现在已经明确,数据已经持久化,下面就要对其进行读取。因为Ignite支持SQL,所以可以像Oracle一样执行SELECT查询,并且还支持线性扩展,因为数据是分布式的。考虑下面的模型:

SQL查询:

但是上面的代码并没有返回所有的数据,为什么呢?

这里Person通过orgIdOrganization进行关联,这是一个典型的外键。但是仅仅这样做,将两个表关联之后执行SQL查询,如果集群中有几个节点,那么并不会返回正确的结果。

这是因为默认的SQL关联仅适用于单个节点,如果SQL遍历整个集群以收集数据并返回,会非常低效,这样会失去分布式系统的优势,所以Ignite默认只会在本地节点上检索数据。

如果要获得正确的数据,需要对数据进行并置。要正确地关联PersonOrganization,相关的数据应该存储在同一个节点上。

最简单的做法是声明一个关系键,该键会针对给定的值确定实际的节点和分区。如果将Person中的orgId做为关系键,则具有相同orgId的人将位于相同的节点上。

如果由于某种原因无法做到这一点,那么还有另外一个比较低效的解决方案,即启用分布式关联。这是在API层面实现的,该过程取决于使用的是Java、JDBC还是其它的什么接口,虽然速度会变慢,但是至少会返回正确的结果。

下面要考虑如何定义关系键,即如何确认一个特定的ID和一个关联的字段适合定义关联性呢?如果定义所有具有相同orgId的人都被并置,那么orgId就是一个不可分割的独立的块,就不能再将其分布在不同的节点上。如果数据库中有10个组织,那么就可以在10个节点上有10个不可分割的块,如果集群中有更多的节点,那么就会有部分"多余"的节点不属于任何块,这在运行时很难确定,所以要提前规划好。

如果有1个大的组织和9个小的组织,那么块的大小就会有所不同。但是Ignite在节点间分发数据时并不会考虑某个关系组中有多少记录,它不会在一个节点上放1个大的块,然后在另一个节点上放9个块来平衡负载,更可能的分配比例是5:5(或者6:4,甚至于7:3)。

如何让数据分布均匀呢?可以看下面的维度:

  • K:键数量(即数据量);
  • A:关系键数量;
  • P:分区数,即Ignite在节点间分配的大量数据组;
  • N:节点数。

则需要满足的条件是:

这里>>是远大于,如果满足上图的条件,数据就会均匀分布。另外要指出的是,默认的分区数(P)为1024。

可能分布仍然不是很均匀,这是Ignite 1.x系列版本的问题。当时的算法为FairAffinityFunction,虽然运行正常但是节点间的流量过多,现在的算法是RendezvousAffinityFunction,但是也不是绝对公平,误差在正负5-10%。

总结

总结一下,在搭建第一个Ignite集群时,对于不熟悉Ignite的新人来说,要注意以下的注意事项:

  • 设置日志;
  • 禁用组播,仅指定实际使用的IP和端口;
  • 禁用IPv6;
  • 熟悉序列化/反序列化;
  • 控制基线拓扑;
  • 调整关系并置。

© 著作权归作者所有

李玉珏

李玉珏

粉丝 387
博文 79
码字总数 149758
作品 0
沈阳
架构师
私信 提问
加载中

评论(3)

我是来开源
我是来开源

引用来自“Choara”的评论

刚接触ignite,请教关于ignite的一些问题:搭建了一个基于ignite2.7 的 6台节点的集群,开启了持久化,数据存储到服务器本地,使用sqlline的方式建表,不依赖java bean和注解。实际使用中,为了使速度足够快,配置的内存基本上可以存储所有数据,在集群全部重启后,持久化的数据是懒加载到内存中,有没有什么热数据的方式使集群快速的加载持久化数据到内存中? 尝试过两种方式:
1. Cache.loadCache();发现 Calling Cache.loadCache() method will have no effect, CacheConfiguration.getStore() is not defined for cache。
2. 使用 sql 查询所有数据,执行了近两个小时,监控内存的增长非常缓慢
请问下有什么方式能解决这个问题
老哥,能问下你的集群怎么配置的么,我的怎么都连接不上其他节点
我是来开源
我是来开源
老哥,能问下你的集群怎么配置的么,我的怎么都连接不上其他节点
C
Choara
刚接触ignite,请教关于ignite的一些问题:搭建了一个基于ignite2.7 的 6台节点的集群,开启了持久化,数据存储到服务器本地,使用sqlline的方式建表,不依赖java bean和注解。实际使用中,为了使速度足够快,配置的内存基本上可以存储所有数据,在集群全部重启后,持久化的数据是懒加载到内存中,有没有什么热数据的方式使集群快速的加载持久化数据到内存中? 尝试过两种方式:
1. Cache.loadCache();发现 Calling Cache.loadCache() method will have no effect, CacheConfiguration.getStore() is not defined for cache。
2. 使用 sql 查询所有数据,执行了近两个小时,监控内存的增长非常缓慢
请问下有什么方式能解决这个问题
Apache Ignite 2.5.0 版本发布,千级节点伸缩性

Apache Ignite 2.5: 千级节点伸缩性 Apache Ignite的用户通常知道的两个关键点是-扩展性和性能。在很多分布式系统的整个生命周期中,通常会不停地改进性能,而对扩展性相关的改进次数,会比较...

李玉珏
2018/06/01
1K
10
Apache Ignite原生持久化概述

很多人会问,Ignite有没有持久化存储,或者说是不是一个单纯的内存存储? 答案是都有,Ignite的原生持久化可以打开,也可以关闭。这使得Ignite可以存储比可用内存量大得多的数据集,也就是说...

李玉珏
02/11
522
0
内存数据组织 - Apache Ignite

1.Ignite是什么? Apache Ignite是一个以内存为中心的分布式数据库、缓存和处理平台,支持事务、分析以及流式负载,可以在PB级数据上享有内存级的性能。 1.1.Ignite定位 Ignite是不是内存数据...

匿名
2015/01/10
29K
8
Apache Ignite(六):Ignite的集群部署

Ignite的集群部署 Ignite具有非常先进的集群能力,本文针对和集群有关的技术点做一个简短的介绍,然后针对实际应用的可能部署形式做了说明和对比,从中我们可以发现,Ignite平台在部署的灵活...

李玉珏
2016/03/31
5.1K
21
Apache Ignite上的TensorFlow

任何深度学习都是从数据开始的,这是关键点。没有数据,就无法训练模型,也无法评估模型质量,更无法做出预测,因此,数据源非常重要。在做研究、构建新的神经网络架构、以及做实验时,会习惯...

李玉珏
03/20
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

DDD(十)--仓储

1、引言 DDD中的Repository(仓储):协调领域和数据映射层,利用类似与集合的接口来访问领域对象。——《领域驱动设计-软件核心复杂性应对之道》 仓储是DDD中产生的概念,也就是说,如果应...

MrYuZixian
12分钟前
5
0
Jenkins的多种迁移方法

说明 Jenkins有时需要进行迁移,主目录会发生改变,本文主要讲解如何更改主目录。由于jenkins安装方式的不同,主目录也不一样。 本测试环境:Centos7.6 X64。注意:在更改主目录之前,请一定...

Elson
13分钟前
5
0
好程序员web前端教程分享前端javascript练习题三

好程序员web前端教程分享前端javascript练习题三,cookie 一周内免登录 样式代码: <form action=""> 姓名:<input type="text" id="usename"/><br /> 密码:<input type="text" i="mima"/>......

好程序员官网
32分钟前
5
0
Table 信息转成pojo属性

import com.google.common.base.CaseFormat;import java.sql.*;/** * @author: liyhu * @date: 2019/11/22 */public class TableToPojo { static String url="jdbc:mys......

暗中观察
今天
8
0
Access数据库-C#操作类

//Access数据库-C# 操作类 代码using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Data.OleDb;using System.Data;namespace XXX{......

芳缘
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部