Apache Ignite 学习笔记(四): Ignite缓存冗余备份策略
Ignite的数据网格是围绕着基于内存的分布式key/value存储能力打造的。当初技术选型的时候,决定用Ignite也是因为虽然同样是key/value存储,它有着和其他key/value存储系统不同的特性。根据官网的介绍,Ignite在设计之初,就是为了能方便的水平扩展而设计的。Ignite将数据分片,每个节点只存储数据的一部分,这样每当有新节点加入时,整个集群可以存储更多的数据。为了提高可用性,Ignite也支持用不同的策略对数据分片进行冗余备份,这样保证数据不会因为集群中一两个节点失效而丢失。另外,和其他key/value缓存系统最大的不同是,Ignite支持用SQL语句对缓存数据进行查询。正是由于有对SQL 99的支持,我们甚至可以把Ignite当做一个分布式内存数据库来使用。
从这篇文章开始,我们先聚焦在Ignite提供的数据网格服务上,看看同样是基于key/value存储,Ignite的key/value缓存又提供了哪些能力。
消失的数据
在介绍Ignite不同的缓存冗余备份模式之前,我们先用上一篇文章的代码来模拟一下在默认配置下,如果Ignite集群中有节点失效,我们的数据是否还完整有效。我们在同一台虚拟机按以下的顺序分别启动server和client实例:
- 启动一个server实例,该实例会创建一个“TEST”缓存并写入三条缓存数据。
- 再启动一个server实例,该实例会自动加入之前启动的server节点,组成一个Ignite集群。因为该实例创建的缓存名字和写入的缓存数据都一样,所以"TEST"缓存里的数据保持不变。
- 启动一个client实例,查询“TEST”缓存里的数据,此时该client应该可以查询到三条之前写入的缓存数据:
Montreal is in Quebec
Edmonton is in Alberta
Markham is in Ontario
Toronto is in null
- 关闭一个server实例,再启动一个client实例做查询,此时我们会发现某些缓存数据消失了。比如在我的环境里,就查不到Markham这条数据了:
Montreal is in Quebec
Edmonton is in Alberta
Markham is in null
Toronto is in null
因为Ignite将缓存数据分片存储的,即同一缓存中的不同数据存到不同的server节点上,Ignite通过一个哈希算法计算出某个数据分片所属的节点。除了原生的哈希算法,用户也可实现自己的哈希算法来决定数据分片对应的节点。在上面的步骤1中,缓存数据全部存在仅有的唯一一个server实例中。在步骤2中,当有新的server实例加入集群,Ignite通过默认的哈希算法决定哪部分的数据分片应该存到新加入的server实例中,然后将数据在两个实例间重新平衡分布。由于我们采用的是默认的配置,所以每个数据分片只有一份拷贝,这就是为什么当我们关了一个server实例后会发生数据丢失的情况。因此,为了保证数据的高可用,我们必须调整数据分片拷贝的数量。接下来,我们就来看看Ignite提供了哪些对数据分片进行冗余备份的策略,以便用户根据实际需求在性能和数据高可用性之间做选择。
Ignite缓存数据分片冗余策略
Local模式
我们先从简单的模式说起,Local模式,顾名思义就是缓存的所有数据只保存在本地节点,数据不会分布到其他节点上,也就没有数据分片和数据拷贝这么一说。Local模式的最大好处是它的轻量化,因为没有了数据分片和冗余备份的负担,其非常适合于数据只读模式和需要定期刷新的场景,也适合于作为一个read-through的缓存。除了数据分布不同,采用local模式的缓存和分布式缓存有着相同的功能,比如数据自动清除,过期失效,磁盘交换,数据查询以及事务等特性。
Replicated模式
Replicated模式下,缓存数据虽然被均分为多个数据分片,但每个节点上都有该缓存的全部数据分片。下面这张官网图很好的展示了replicated模式的数据分布:
在replicated模式下,缓存数据被平分为4个数据分片A、B、C、D。在节点1(JVM1)上有分片A的primary拷贝和B、C、D的backup拷贝。在节点2(JVM2)上有分片C的primary拷贝和A、B、D的backup拷贝。节点3(JVM3)和节点4(JVM4)的情况也类似。关于数据分片的primary和backup拷贝的概念我们在下一篇介绍,这里只要记住当primary拷贝失效了,Ignite可以用backup拷贝恢复数据,保证了数据的高可靠性。所以在replicated模式下,每个节点其实有缓存的所有数据分片拷贝,即便集群里其他节点都失效,Ignite还是可以通过仅存的一个节点提供数据读写服务
Partitioned模式
Partition模式下,缓存数据被均分为多个数据分片,数据分片的拷贝均等的分布在集群的某些节点上。换句话说,和replicated模式最大的不同就是,一个节点上没有全部的缓存数据分片拷贝。让我们借用官网的图来解释一下partitioned模式:
如上图所示,在partitioned模式下,缓存数据被平分为4个数据分片A、B、C、D,每个数据分片有一份primary拷贝和backup拷贝,所以每个节点只保存两个数据分片的拷贝,比如节点1(JVM1)有分片A的primary分片和分片B的backup分片,节点2(JVM2)有分片C的primary分片和分片A的backup分片。Backup拷贝的数量是用户可配置的,如果配置为0时,代表着一个数据分片没有副本,一旦某个节点挂了,数据就会丢失。如果配置为(集群节点数量-1),代表着集群的每个节点上都有一份该数据分片的拷贝,这就相当于一种特殊的replicated模式。拷贝数量越多,代表数据约可靠,但也会带来额外的开销,所以我们还是要根据实际的场景和需求来调整拷贝数量。
Replicated V.S. Partitioned
让我们简单的比较下两种模式的优缺点以及它们适合的场景:
- 首先,从数据的可靠性来说当然是replicated模式占优势,毕竟每个节点都有缓存的所有数据分片,只要集群中有一个节点还能工作,就能从该节点恢复数据。而partitioned模式下,数据的可靠性是和backup数量N相关的,在partitioned模式下,一旦有N+1个节点失效,集群就有可能出现丢失数据的情况。
- 其次,从扩展性上看,是partitioned模式优于replicated模式。因为每个节点需要有所有数据分片的拷贝,在replicated模式下,集群所能容纳的数据大小是受单个节点的内存和硬盘(如果启用了Ignite原生的持久化功能)限制的,即便新增节点,也不能提高集群的数据容量。反观partitioned模式,新增加一个节点就可以给集群增加更多的存储能力,容纳更多的数据。
- 再次,从读写性能上看,replicated模式适合多读少写的场景,因为每写一份数据,就要同步到集群中所有的节点上,如果节点数量多了,同步的开销还是很可观的。对于读数据,因为每个节点上都有缓存数据的拷贝,所以在replicated模式下的读可以充分利用所有节点的带宽,提供更好的读性能。而Partitioned模式更适合多写少读的场景,因为写数据时需要同步的节点数量要少,所以写性能更好。对于读场景,因为一份数据的拷贝只在集群的几台节点上,所以读性能势必会受影响。
配置缓存Replicated/Partitioned模式
好了,在了解完Ignite缓存不同的数据分片冗余策略后,让我们通过一个实际的例子看看如何在代码或是xml配置文件中配置不同的数据分片冗余策略。我们在上一篇文章的server节点代码上进行改造,大部分逻辑都保持不变,重点注意一下第26行~34行加入的新代码:
public class IgniteCacheOpModeExample {
public static void main(String[] args) {
Ignite ignite;
// 创建一个TEST缓存并写入一些数据, key是城市的名字,value是省的名字
IgniteCache<String, String> cityProvinceCache;
if(args.length == 1 && !args[0].isEmpty())
{
//如果启动时指定了配置文件,则用指定的配置文件
System.out.println("Use " + args[0] + " to start.");
ignite = Ignition.start(args[0]);
//配置文件中,我们将缓存设置为partitioned模式,backup数量为1
cityProvinceCache = ignite.getOrCreateCache("TEST");
}
else
{
//如果启动时没指定配置文件,则生成一个配置文件
System.out.println("Create an IgniteConfiguration to start.");
TcpDiscoverySpi spi = new TcpDiscoverySpi();
TcpDiscoveryMulticastIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();
ipFinder.setMulticastGroup("224.0.0.251");
spi.setIpFinder(ipFinder);
IgniteConfiguration cfg = new IgniteConfiguration();
cfg.setDiscoverySpi(spi);
ignite = Ignition.start(cfg);
CacheConfiguration<String, String> cacheCfg = new CacheConfiguration("TEST");
// 如果不用配置文件启动,缓存模式被设置为replicated
cacheCfg.setCacheMode(CacheMode.REPLICATED);
/* 下面的配置将"TEST"缓存设为partitioned模式,并且设置了backup数量为1,这样保证即使有一个node出现
故障的情况下,缓存数据还是完整可用的
cacheCfg.setCacheMode(CacheMode.PARTITIONED);
cacheCfg.setBackups(1);
*/
cityProvinceCache = ignite.getOrCreateCache(cacheCfg);
}
cityProvinceCache.put("Edmonton", "Alberta");
cityProvinceCache.put("Markham", "Ontario");
cityProvinceCache.put("Montreal", "Quebec");
}
}
在调用ignite.getOrCreateCache()函数之前,我们为"TEST"先生成一个CacheConfiguration,然后调用setCacheMode()将其模式设置为REPLICATED模式(在29~33行被注释掉的代码中,是如何设置PARTITIONED模式以及backups数量的代码),最后再交由Ignite根据configuration生成"TEST"缓存。 当然,和上一篇一样,也可以通过XML文件来配置缓存模式:
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<property name="ipFinder">
<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">
<property name="multicastGroup" value="224.0.0.251"/>
</bean>
</property>
</bean>
</property>
<property name="cacheConfiguration">
<bean class="org.apache.ignite.configuration.CacheConfiguration">
<!-- 设置缓存名字. -->
<property name="name" value="TEST"/>
<!-- 设置缓存模式. -->
<property name="cacheMode" value="PARTITIONED"/>
<property name="backups" value="1"/>
<!-- 下面将缓存设置为replicated模式 -->
<!--property name="cacheMode" value="REPLICATED"/-->
</bean>
</property>
</bean>
</beans>
在XML文件中,我们加入了cacheConfiguration的配置,为了和代码里创建的缓存名字保持一致,配置里也使用了"TEST"作为缓存名字,"cacheMode"设为PARTITIONED,"backups"值设为了1(每个数据分片除了primay拷贝外,还有额外的一份backup拷贝,即缓存可以允许有一个节点故障而保证缓存数据的完整性)。
更新了代码和配置文件后,server节点如果制定了XML配置文件启动,生成的缓存为带一个backup的PARTITIONED模式,如果不用XML配置文件,则生成的缓存为REPLICATED模式。无论用哪种方式启动server节点,我们再重复这篇文章开头的那个实验,就会发现即使在一个节点失效的情况下,client节点还是可以访问到缓存中的所有数据,不会再出现丢数据的情况了。在实际使用过程中,正确的配置缓存的冗余模式直接影响到Ignite集群数据的高可用性。
总结
这篇文章我们介绍了Ignite集群中数据分片的不同冗余策略,在实际的使用过程中,不同的策略会直接影响集群中数据的高可用性和读写性能,所以理解不同的策略的优缺点,是使用好Ignite数据网格集群的第一步。 这篇文章里用到的例子的完整代码和maven工程可以在这里找到。 Server对应的xml配置文件在src/main/resources目录下。
下一篇,我们将继续了解一下Ignite针对不同的冗余策略提供的功能,比如数据分片由于节点失效出现丢失时的行为,primary拷贝和backup拷贝之前的同步等。
Apache Ignite 学习笔记(四): Ignite缓存冗余备份策略的更多相关文章
- Apache Ignite 学习笔记(一): Ignite介绍、部署安装和REST/SQL客户端使用
Apache Ignite 介绍 Ignite是什么呢?先引用一段官网关于Ignite的描述: Ignite is memory-centric distributed database, cachi ...
- Apache Ignite 学习笔记(三): Ignite Server和Client节点介绍
在前两篇文章中,我们把Ignite集群当做一个黑盒子,用二进制包自带的脚本启动Ignite节点后,我们用不同的客户端连接上Ignite进行操作,展示了Ignite作为一个分布式内存缓存,内存数据库的基 ...
- Apache Ignite 学习笔记(二): Ignite Java Thin Client
前一篇文章,我们介绍了如何安装部署Ignite集群,并且尝试了用REST和SQL客户端连接集群进行了缓存和数据库的操作.现在我们就来写点代码,用Ignite的Java thin client来连接集群 ...
- Apache Ignite 学习笔记(6): Ignite中Entry Processor使用
之前的文章我们其实已经用到了两种不同的方式访问Ignite中的数据.一种方式是第一篇文章中提到通过JDBC客户端用SQL访问数据,在这篇文章中我们也会看到不使用JDBC,如何通过Ignite API用 ...
- 零拷贝详解 Java NIO学习笔记四(零拷贝详解)
转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...
- openresty 学习笔记四:连接mysql和进行相关操作
openresty 学习笔记四:连接mysql和进行相关操作 毕竟redis是作为缓存,供程序的快速读写,虽然reidis也可以做持久化保存,但还是需要一个做数据存储的数据库.比如首次查询数据在red ...
- C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻
前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...
- IOS学习笔记(四)之UITextField和UITextView控件学习
IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...
- java之jvm学习笔记四(安全管理器)
java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...
随机推荐
- 百度地图Key的设置方法
一.为什么要设置百度Key 万能地图下载器提供了百度POI的下载功能,但由于本软件用户群极大,会导致一天之内访问量超出300万次以上而无法继续下载. 因此,当POI下载不成功能,用户可以自己申请百度地 ...
- BZOJ5368:[PKUSC2018]真实排名(组合数学)
Description 小C是某知名比赛的组织者,该比赛一共有n名选手参加,每个选手的成绩是一个非负整数,定义一个选手的排名是:成绩不小于他的选手的数量(包括他自己). 例如如果333位选手的成绩分别 ...
- iframe-metamask
iframe--require('iframe') higher level api for creating and removing iframes in browsers 用于创建或移除浏览器中 ...
- 开源http协议库curl和wget的区别和使用
curl和wget基础功能有诸多重叠,如下载等. 在高级用途上的curl由于可自定义各种请求参数所以长于模拟web请求,用于测试网页交互(浏览器):wget由于支持ftp和Recursive所以长于下 ...
- Python基础(9)——类
类的语法 类的定义 class Dog(object): print("hello,I am a dog!") d = Dog() #实例化这个类, #此时的d就是类Dog的实例化 ...
- 【转】系统去掉 Android 4.4.2 的StatusBar和NavigationBar
系统Hide Status Bar frameworks/base/core/res/res/values/dimens.xml 把 <dimen name="status_bar_ ...
- JAVA框架 Spring 依赖注入
一:介绍 情景:我们在给程序分层的时候:web层.业务层.持久层,各个层之间会有依赖.比如说:业务层和持久层,业务层的代码在调用持久层的时候,传统方式:new 持久层类. 进而进行调用,这种方式会导致 ...
- java剪辑音频
用来剪辑特定长度的音频,并将它们混剪在一起,大体思路是这样的: 1. 使用 FileInputStream 输入两个音频 2. 使用 FileInputStream的skip(long n) 方法跳过 ...
- SQL 提高性能
参考博客:http://www.cnblogs.com/jiekzou/p/5988099.html 非常感谢博主分享. 1.set nocount on 关闭行基数信息,减少网络通信,提高程序性能 ...
- java List集合中contains方法总是返回false
ArrayList的contains方法 java 今天在用ArrayList类的caontains方法是遇到了问题,我写了一个存放User类的ArrayList 但在调用list.contains( ...