开源IMDG之GridGain
作为另一款主流的开源数据网格产品,GridGain是Hazelcast的强有力竞争者。同样提供了社区版和商业版,近日GridGain的开源版本已经进入Apache孵化器项目Ignite(一款开源的内存计算(In-Memory Computing)IMC中间件),目前Apache正在迁移GridGain开源版本的代码到Ignite项目。鉴于经过之前Hazelcast的介绍已经对数据网格产品有了一定了解,本文着重介绍GridGain与Hazelcast差异化之处。
1 重叠功能列举
比较 | Hazelcast | GridGain | |
使用性 | 安装 | Maven引入Jar包即可,无需安装软件 | |
客户端 | 支持各种语言的客户端 | ||
框架集成 | 集成Hibernate、Web Session、Spring | ||
基本功能 | 分布式计算工具 | 分布式的集合、并发包、消息队列、调度器 | |
性能 | 性能配置 | 内存索引、Near-Cache、数据亲和性 | |
可靠性 | 数据备份 | 分区数据冗余备份 | |
持久化 | read-through,write-through/behind | ||
事务 | 保证数据一致性 | ||
扩展性 | 自动分区 | 支持本地、分区、复制三种方式 | |
动态拓扑 | 动态添加删除结点,自动rebalance |
上面简单列举了一些Hazelcast和GridGain的重叠功能,而两者的差异之处主要在于以下几个方面:
Ø 整体功能的全面性:围绕内存计算提供的功能。
Ø 使用性:对SQL支持的完整性、对Continuous Query的支持、以及与持久化存储的数据集成。
Ø 性能:免费的off-heap存储实现。
Ø 可靠性方面:事务的隔离性、内存溢出到磁盘。
Ø 管理:提供强大的后台管理界面。
此外,企业版还提供了Portable跨平台对象、安全和审计、数据中心复制、可还原的本地cache以及split-brain网络分段问题解决等功能。本文暂不关注企业版中的附加功能,下面开始着重介绍上面列举的开源社区版与Hazelcast的功能差异。
2 全面的内存计算功能栈
从整体功能上来说,GridGain是个出色的多面手,不仅可以完成本职工作-内存计算/数据网格,还提供了:
1) GGFS(GridGain In-Memory File System),类似Spark生态圈中的Tachyon,能够加速MapReduce任务的执行。
2) 完整的ACID和事务支持,可以作为内存数据库。
3) 流式数据/事件处理,可以作为CEP事件处理器。
3 丰富的查询功能
一般开源IMDG产品支持基本的对象过滤查询能力,但GridGain底层借助H2数据库引擎来解析和执行SQL,所以支持复杂的对象联结查询,类似于GemFire中的OQL提供的功能,以及仅在Hazelcast商业版本中才支持的Continuous Query功能。
首先来看一下我们的测试数据和Entity是什么样子,代码忽略了构造函数、getter/setter和toString等方法。Person中包含了id、name、salary三个基本属性,并与Organization是多对一的关系,与Address是一对一的关系。
对于POJO要注意几点:1)查询中涉及的成员变量都要标上@GridCacheQuerySqlField注解;2)因为POJO会被哈希到其他结点上的分区,所以要实现序列化接口;3)下面例子只测试了one-to-one(直接嵌套实体Address)、many-to-one(通过orgId关联其他实体)关系的查询,而没有尝试one-to-many和many-to-many(都是在实体中嵌套另一实体id的集合);4)GridCacheConfiguration要开启setQueryIndexEnabled(true)。
3.1 简单的过滤查询
简单的对象过滤查询是最常见的,也是其他网格产品像Hazelcast支持的。
3.2 同cache下的join查询
同cache下可能会关联的数据,可以通过数据亲和性设置使相关数据分配到同一分区中,从而避免网络传输开销。当然,Hazelcast也是支持数据亲和性的,本节关注的重点是join查询。代码类似于3.3中的跨cache查询,差别只不过是:1)Organization对象不是保存到org-cache,而是与Person对象一起保存到person-cache;2)SQL中不需要显式指明缓存名称,因为对象都在一个缓存person-cache中。
3.3 跨cache的join查询
被join的cache(org-cache)必须是REPLICATE的,从而在各个结点上都存在,不会产生交叉join。(注:Impala支持这种join,将产生N*M次数据通信)
3.4 字段查询
GridGain不仅支持查询结果为实体,同时也支持各种SQL函数对实体进行各种操作,如聚合、字符串操作等。
3.5 Continuous查询
Continuous查询不支持SQL,只支持Predicate风格组装查询。
执行效果如下,首先初始化到缓存中的Person对象中有一个salary=300,满足条件,所以本地callback收到通知。之后我们试着更新缓存中的一个Person对象的salary=250,于是再次收到通知。最后我们新建一个Person,salary=500,并保存到缓存中,于是再次收到通知。这就是Continuous Query的运行效果。
4 集成持久化存储
类似Hazelcast,GridGain也提供了read-through、write-through以及异步write-behind三种与后端持久化存储通信的方式。此外,GridGain还支持事务提交时批量write,以及缓存entry即将过期时自动重新re-cache(refresh-ahead)功能。像refresh-ahead功能在GemFire等商业产品中才会实现。
5 堆外内存存储
不像Hazelcast等开源产品只在商业版中提供off-heap存储功能,GridGain在开源版本中就提供了此功能,从而显著地扩充JVM管理的内存容量,并减轻GC压力和停顿时间。GridGain提供了ONHEAP_TIERED(默认堆优先,溢出到非堆内存)、OFFHEAP_TIERED(不使用堆,直接将所有entry放入非堆内存)、OFFHEAP_VALUES(将key存储在堆,value存储在非堆内存)三种模式。当堆和非堆内存都不足时,还可以开启SWAP,将数据溢出到磁盘(详见第7部分:数据溢出到磁盘)。下图表示了堆、非堆、磁盘、外围存储的容量和延迟的关系。
6 完整的ACID和事务支持
内存事务与传统数据库事务有一点点不同。因为IMDG产品使用的是易逝内存,所以故障或断电时内存数据会全部丢失。一般IMDG重启时会从备份结点或其他持久化存储中恢复数据。但这不代表内存事务不重要!只要集群是存活的,GridGain就要保证不同结点间的数据一致性。为此,GridGain提供了两种TRANSACTIONAL和ATOMIC两种配置:
Ø TRANSACTIONAL:完整的ACID属性的事务,以及显式的锁。
Ø ATOMIC:没有事务和锁。
GridGain使用2PC(两阶段提交)协议实现分布式事务,同时支持乐观和悲观两种模式。乐观模式下所有key在提交时才会加锁,所以在Prepare阶段,prepare消息发送给各结点获取事务中将要操作的key的锁,各结点通过ACK消息应答。而悲观模式下,所有key在提交前就已加锁,所以Prepare阶段不需要做任何事。在Commit阶段,commit消息发送给各结点提交,若失败则发送回滚消息给各结点。可以设置各个结点之间是同步还是异步提交(注:Hazelcast支持一种2PC扩展协议,具体的优势还有待研究)。最后,GridGain支持READ_COMMITTED,REPEATABLE_READ和SERIALIZABLE三种事务隔离级别,默认是REPEATABLE_READ。而Hazelcast只支持REPEATABLE_READ一种。
7 数据溢出到磁盘
在第5部分堆外内存存储中提到过GridGain的层次化存储,以下面的缓存配置为例,它同时开启了off-heap和swap。1)首先,数据优先保存在堆内存中。2)当entry总数超过100个时,会通过LRU淘汰到非堆内存中。3)当超过非堆内存的最大容量5MB时,会将多出的数据保存在磁盘上。当然,磁盘IO操作的代价是很大,我们可以优先考虑使用超大的非堆内存,或者使用SSD闪存,以及开启操作系统的磁盘IO缓存来进行优化。
GridCacheConfiguration cacheCfg = new GridCacheConfiguration();
cacheCfg.setEvictionPolicy(new GridCacheLruEvictionPolicy(100));
cacheCfg.setOffHeapMaxMemory(5 * 1024L * 1024L);
cacheCfg.setSwapEnabled(true);
8 强大的管理界面
首先,以相同配置启动三个GridGain实例。然后,启动gridgain-fabric-os-6.5.5/bin/ ggvisorui.exe,在Visor GUI中选择File->Connect->External标签页下,直接localhost+默认端口连接即可进入Dashboard。在这能看到我们刚刚启动的三个结点的总体信息。
点击Data Grid标签页,可以查看各个cache的内存使用、读写以及命中情况,例如我们初始化了3个Person和2个Organization对象,并分别保存到了person-cache和org-cache中,于是我们可以在此标签页看到Primary Entry和Write个数都是3和2。