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拷貝之前的同步等。
