Hazelcast集羣服務(1)——Hazelcast介紹

Hazelcast是什麼

    「分佈式」、「集羣服務」、「網格式內存數據」、「分佈式緩存「、「彈性可伸縮服務」——這些牛逼閃閃的名詞拿到哪都是ITer裝逼的不二之選。在Javaer的世界,有這樣一個開源項目,只須要引入一個jar包、只需簡單的配置和編碼便可實現以上高端技能,他就是 Hazelcasthtml

    Hazelcast 是由Hazelcast公司(沒錯,這公司也叫Hazelcast!)開發和維護的開源產品,能夠爲基於jvm環境運行的各類應用提供分佈式集羣和分佈式緩存服務。Hazelcast能夠嵌入到任何使用Java、C++、.NET開發的產品中(C++、.NET只提供客戶端接入)。Hazelcast目前已經更新到3.X版本,Java中絕大部分數據結構都被其覺得分佈式的方式實現。好比Javaer熟悉的Map接口,當經過Hazelcast建立一個Map實例後,在節點A調用 Map::put("A","A_DATA") 方法添加數據,節點B使用 Map::get("A") 能夠獲到值爲"A_DATA" 的數據。Hazelcast 提供了 Map、Queue、MultiMap、Set、List、Semaphore、Atomic 等接口的分佈式實現;提供了基於Topic 實現的消息隊列或訂閱\發佈模式;提供了分佈式id生成器(IdGenerator);提供了分佈式事件驅動(Distributed Events);提供了分佈式計算(Distributed Computing);提供了分佈式查詢(Distributed Query)。總的來講在獨立jvm常用數據結果或模型 Hazelcast 都提供了分佈式集羣的實現。java

    Hazelcast 有開源版本和商用版本。開源版本遵循 Apache License 2.0 開源協議無償使用。商用版本須要獲取特定的License,二者之間最大的區別在於:商用版本提供了數據高密度存儲。咱們都知道jvm有本身特定的GC機制,不管數據是在堆仍是棧中,只要發現無效引用的數據塊,就有可能被回收。而Hazelcast的分佈式數據都存放在jvm的內存中,頻繁的讀寫數據會致使大量的GC開銷。使用商業版的Hazelcast會擁有高密度存儲的特性,大大下降Jvm的內存開銷,從而下降GC開銷。git

    不少開源產品都使用Hazelcast 來組建微服務集羣,例如我們的Vert.x,首選使用Hazelcast來組建分佈式服務。有興趣能夠看個人這篇分享——http://my.oschina.net/chkui/blog/678347 ,文中說明了Vert.x如何使用Hazelcast組建集羣。github

    附:數據庫

Hazelcast的特性

自治集羣(無中心化)

    Hazelcast 沒有任何中心節點(文中的節點能夠理解爲運行在任意服務器的獨立jvm,下同),或者說Hazelcast 不須要特別指定一箇中心節點。在運行的過程當中,它本身選定集羣中的某個節點做爲中心點來管理全部的節點。緩存

數據按應用分佈式存儲

    Hazelcast 的數據是分佈式存儲的。他會將數據儘可能存儲在須要使用該項數據的節點上,以實現數據去中心化的目的。在傳統的數據存儲模型中(MySql、MongDB、Redis 等等)數據都是獨立於應用單獨存放,當須要提高數據庫的性能時,須要不斷加固單個數據庫應用的性能。即便是如今大量的數據庫支持集羣模式或讀寫分離,可是基本思路都是某幾個庫支持寫入數據,其餘的庫不斷的拷貝更新數據副本。這樣作的壞處一是會產生大量髒讀的問題,二是消耗大量的資源來傳遞數據——從數據源頻繁讀寫數據會耗費額外資源,當數據量增加或建立的主從服務愈來愈多時,這個消耗呈指數級增加。服務器

    使用 Hazelcast 能夠有效的解決數據中心化問題。他將數據分散的存儲在每一個節點中,節點越多越分散。每一個節點都有各自的應用服務,而Hazelcast集羣會根據每一個應用的數據使用狀況分散存儲這些數據,在應用過程當中數據會盡可能「靠近」應用存放。這些在集羣中的數據共享整個集羣的存儲空間和計算資源。網絡

抗單點故障

    集羣中的節點是無中心化的,每一個節點都有可能隨時退出或隨時進入。所以,在集羣中存儲的數據都會有一個備份(能夠配置備份的個數,也能夠關閉數據備份)。這樣的方式有點相似於 hadoop,某項數據存放在一個節點時,在其餘節點一定有至少一個備份存在。當某個節點退出時,節點上存放的數據會由備份數據替代,而集羣會從新建立新的備份數據。數據結構

簡易性

    全部的 Hazelcast 功能只需引用一個jar包,除此以外,他不依賴任何第三方包。所以能夠很是便捷高效的將其嵌入到各類應用服務器中,而沒必要擔憂帶來額外的問題(jar包衝突、類型衝突等等)。他僅僅提供一系列分佈式功能,而不須要綁定任何框架來使用,所以適用於任何場景。框架

    除了以上特性,Hazelcast 還支持服務器/客戶端模型,支持腳本管理、可以和 Docker 快速整合等等。

簡單使用例子

    前面說了那麼多概念,必需要來一點乾貨了。下面是一個使用 Hazelcast 的極簡例子。文中的全部代碼都在github上:https://github.com/chkui/hazelcast-demo

    首先引入Hazelcast的jar包。

    Maven(pom.xml)

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast</artifactId>
    <version>${hazelcast.vertsion}</version>
</dependency>

    Gradle(build.gradle)

compile com.hazelcast:hazelcast:${hazelcast.vertsion}

    先創一個建 Hazelcast 節點:

//org.palm.hazelcast.getstart.HazelcastGetStartServerMaster 
public class HazelcastGetStartServerMaster {
    public static void main(String[] args) {
        // 建立一個 hazelcastInstance實例
        HazelcastInstance instance = Hazelcast.newHazelcastInstance();
        // 建立集羣Map
        Map<Integer, String> clusterMap = instance.getMap("MyMap");
        clusterMap.put(1, "Hello hazelcast map!");

        // 建立集羣Queue
        Queue<String> clusterQueue = instance.getQueue("MyQueue");
        clusterQueue.offer("Hello hazelcast!");
        clusterQueue.offer("Hello hazelcast queue!");
    }
}
 
 
 
 

    上面的代碼使用 Hazelcast 實例建立了一個節點。而後經過這個實例建立了一個分佈式的Map和分佈式的Queue,並向這些數據結構中添加了數據。運行這個main方法,會在console看到如下內容:

Members [1] {
    Member [192.168.1.103]:5701 this
}

     隨後再建立另一個節點:

// org.palm.hazelcast.getstart.HazelcastGetStartServerSlave
public class HazelcastGetStartServerSlave {
    public static void main(String[] args) {
        //建立一個 hazelcastInstance實例
        HazelcastInstance instance = Hazelcast.newHazelcastInstance();
        Map<Integer, String> clusterMap = instance.getMap("MyMap");
        Queue<String> clusterQueue = instance.getQueue("MyQueue");
        
        System.out.println("Map Value:" + clusterMap.get(1));
        System.out.println("Queue Size :" + clusterQueue.size());
        System.out.println("Queue Value 1:" + clusterQueue.poll());
        System.out.println("Queue Value 2:" + clusterQueue.poll());
        System.out.println("Queue Size :" + clusterQueue.size());
    }
}
 
 

    該節點的做用是從Map、Queue中讀取數據並輸出。運行會看到如下輸出

Members [2] {
    Member [192.168.1.103]:5701
    Member [192.168.1.103]:5702 this
}

八月 06, 2016 11:33:29 下午 com.hazelcast.core.LifecycleService
信息: [192.168.1.103]:5702 [dev] [3.6.2] Address[192.168.1.103]:5702 is STARTED
Map Value:Hello hazelcast map!
Queue Size :2
Queue Value 1:Hello hazelcast!
Queue Value 2:Hello hazelcast queue!
Queue Size :0

    至此,2個節點的集羣建立完畢。第一個節點向map實例添加了{key:1,value:"Hello hazelcast map!"},向queue實例添加[「Hello hazelcast!」,「Hello hazelcast queue!」],第二個節點讀取並打印這些數據。

    除了直接使用Hazelcast服務來組建集羣,Hazelcast還提供了區別於服務端的客戶端應用包。客戶端與服務端最大的不一樣是:他不會存儲數據也不能修改集羣中的數據。目前客戶端有C++、.Net、Java多種版本。

    使用客戶端首先要引入客戶端jar包。

    Maven(pom.xml)

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-client</artifactId>
    <version>${hazelcast.version}</version>
</dependency>

    Gradle(build.gradle)

compile com.hazelcast:hazelcast-client:${hazelcast.vertsion}

    建立一個client節點。

public class HazelcastGetStartClient {
    public static void main(String[] args) {
        ClientConfig clientConfig = new ClientConfig();
        HazelcastInstance instance = HazelcastClient.newHazelcastClient(clientConfig);
        Map<Integer, String> clusterMap = instance.getMap("MyMap");
        Queue<String> clusterQueue = instance.getQueue("MyQueue");
        
        System.out.println("Map Value:" + clusterMap.get(1));
        System.out.println("Queue Size :" + clusterQueue.size());
        System.out.println("Queue Value 1:" + clusterQueue.poll());
        System.out.println("Queue Value 2:" + clusterQueue.poll());
        System.out.println("Queue Size :" + clusterQueue.size());
    }
}
 
 

    而後先啓動 HazelcastGetStartServerMaster::main,再啓動 HazelcastGetStartClient::main。能夠看到客戶端輸出:

Members [1] {
    Member [192.168.197.54]:5701
}

八月 08, 2016 10:54:22 上午 com.hazelcast.core.LifecycleService
信息: HazelcastClient[hz.client_0_dev][3.6.2] is CLIENT_CONNECTED
Map Value:Hello hazelcast map!
Queue Size :2
Queue Value 1:Hello hazelcast!
Queue Value 2:Hello hazelcast queue!
Queue Size :0

    至此,客戶端功能也建立完畢 。能夠看到客戶端的console輸出內容比服務端少了不少,這是由於客戶端沒必要承載服務端的數據處理功能,也沒必要維護各類節點信息。

例子運行解析

    下面咱們根據console的輸出來看看 Hazelcast 啓動時到底幹了什麼事。(下面的輸出因環境或IDE不一樣,可能會有差別)

class: com.hazelcast.config.XmlConfigLocator
info: Loading 'hazelcast-default.xml' from classpath. 

    這裏輸出的內容表示Hazelcast啓動時加載的配置文件。若是用戶沒有提供有效的配置文件,Hazelcast會使用默認配置文件。後續的文章會詳細說明 Hazelcast 的配置。

class: com.hazelcast.instance.DefaultAddressPicker
info: Prefer IPv4 stack is true.
class: com.hazelcast.instance.DefaultAddressPicker
info: Picked Address[192.168.197.54]:5701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5701], bind any local is true

    這一段輸出說明了當前 Hazelcast 的網絡環境。首先是檢測IPv4可用且檢查到當前的IPv4地址是192.168.197.54。而後使用IPv6啓用socket。在某些沒法使用IPv6的環境上,須要強制指定使用IPv4,增長jvm啓動參數:-Djava.net.preferIPv4Stack=true 便可。

class: com.hazelcast.system
info: Hazelcast 3.6.2 (20160405 - 0f88699) starting at Address[192.168.197.54]:5701
class: com.hazelcast.system
info: [192.168.197.54]:5701 [dev] [3.6.2] Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.

     這一段輸出說明了當前實例的初始化端口號是5701。Hazelcast 默認使用5701端口。若是發現該端口被佔用,會+1查看5702是否可用,若是仍是不能用會繼續向後探查直到5800。Hazelcast 默認使用5700到5800的端口,若是都沒法使用會拋出啓動異常。

class: com.hazelcast.system
info: [192.168.197.54]:5701 [dev] [3.6.2] Configured Hazelcast Serialization version : 1
class: com.hazelcast.spi.OperationService
info: [192.168.197.54]:5701 [dev] [3.6.2] Backpressure is disabled
class: com.hazelcast.spi.impl.operationexecutor.classic.ClassicOperationExecutor
info: [192.168.197.54]:5701 [dev] [3.6.2] Starting with 2 generic operation threads and 4 partition operation threads.

     這一段說明了數據的序列化方式和啓用的線程。Hazelcast 在節點間傳遞數據有2種序列化方式,在後續的文章中會詳細介紹。Hazelcast 會控制多個線程執行不一樣的工做,有負責維持節點鏈接的、有負責數據分區管理的。

class: com.hazelcast.instance.Node
info: [192.168.197.54]:5701 [dev] [3.6.2] Creating MulticastJoiner
class: com.hazelcast.core.LifecycleService
info: [192.168.197.54]:5701 [dev] [3.6.2] Address[192.168.197.54]:5701 is STARTING
class: com.hazelcast.nio.tcp.nonblocking.NonBlockingIOThreadingModel
info: [192.168.197.54]:5701 [dev] [3.6.2] TcpIpConnectionManager configured with Non Blocking IO-threading model: 3 input threads and 3 output threads
class: com.hazelcast.cluster.impl.MulticastJoiner
info: [192.168.197.54]:5701 [dev] [3.6.2] 

     上面這一段輸出中,Creating MulticastJoiner表示使用組播協議來組建集羣。還建立了6個用於維護非擁塞信息輸出\輸出。

Members [1] {
    Member [192.168.197.54]:5701
    Member [192.168.197.54]:5702 this
}

class: com.hazelcast.core.LifecycleService
info: [192.168.197.54]:5701 [dev] [3.6.2] Address[192.168.197.54]:5701 is STARTED
class: com.hazelcast.partition.InternalPartitionService
info: [192.168.197.54]:5701 [dev] [3.6.2] Initializing cluster partition table arrangement...

    Members[2]表示當前集羣只有2個節點。2個節點都在ip爲192.168.197.54的這臺設備上,2個節點分別佔據了5701端口和5702端口。端口後面的this說明這是當前節點,而未標記this的是其餘接入集羣的節點。最後InternalPartitionService輸出的信息表示集羣初始化了「數據分片」,後面會介紹「數據分片」的概念和原理。

    上面就是Hazelcast在默認狀況下執行的啓動過程,能夠看出在初始化的過程當中咱們能夠有針對性的修改一些Hazelcast的行爲:

  1. 使用默認配置文檔 hazelcast-default.xml 來啓動集羣。所以咱們能夠自定義這個配置文件來影響Hazelcast 的行爲。
  2. 啓用IPv4或IPv6來創建集羣,所以能夠知道Hazelcast集羣的通訊是基於TCP、UDP,須要打開socket支持集羣交互。所以咱們能夠指定使用的通信方案。
  3. Hazelcast會啓動多個線程來執行不一樣的工做,有些負責維護數據、有些負責集羣通訊、有些負責一些基礎操做。所以咱們能夠配置和管理這些線程。
  4. Hazelcast默認使用MulitCast(組播協議)來組建集羣,所以在局域網環境他能夠無需配置本身完成集羣組建。所以咱們能夠指定使用TCP/IP或其餘通信協議。
  5. Hazelcast會本身探尋可使用的端口,默認狀況下會使用5700到5800間沒有被佔用的端口。所以咱們能夠配置這些端口如何使用。
  6. Hazelcast初始化一個名爲「數據分片」的方案來管理和存儲數據。所以咱們能夠調整和控制這些數據分片。

    以上全部紅色字體的部分均可以經過配置文件來影響。在後續的文章中會詳細介紹相關的 配置說明(待續)。

-----------------------------------亮瞎人的分割線-----------------------------------

    若是對Hazelcast的基本原理沒什麼興趣,就不用向下看「運行結構「和「數據分片原理」了,直接去下一篇瞭解如何使用Hazelcast吧。

Hazelcast運行結構

    Hazelcast的官網上列舉了2種運行模式,一種是p2p(點對點)模式、一種是在點對點模式上擴展的C/S模式。下圖是p2p模式的拓補結構。

    在p2p模式中,全部的節點(Node)都是集羣中的服務節點,提供相同的功能和計算能力。每一個節點都分擔集羣的整體性能,每增長一個節點均可以線性增長集羣能力。

    在p2p服務集羣的基礎上,咱們能夠增長許多客戶端接入到集羣中,這樣就造成了集羣的C/S模式,提供服務集羣視做S端,接入的客戶端視做C端。這些客戶端不會分擔集羣的性能,可是會使用集羣的各類資源。下圖的結構就是客戶端接入集羣的狀況。

    能夠爲客戶端提供特別的緩存功能,告知集羣讓那些它常常要使用的數存放在「離它最近」的節點。

Hazelcast分片概念與原理

   Hazelcast經過分片來存儲和管理全部進入集羣的數據,採用分片的方案目標是保證數據能夠快速被讀寫、經過冗餘保證數據不會因節點退出而丟失、節點可線性擴展存儲能力。下面將從理論上說明Hazelcast是如何進行分片管理的。

分片

    Hazelcast的每一個數據分片(shards)被稱爲一個分區(Partitions)。分區是一些內存段,根據系統內存容量的不一樣,每一個這樣的內存段都包含了幾百到幾千項數據條目,默認狀況下,Hazelcast會把數據劃分爲271個分區,而且每一個分區都有一個備份副本。當啓動一個集羣成員時,這271個分區將會一塊兒被啓動。

    下圖展現了集羣只有一個節點時的分區狀況。

                            

    從一個節點的分區狀況能夠看出,當只啓動一個節點時,全部的271個分區都存放在一個節點中。而後咱們啓動第二個節點。會出現下面這樣的分區方式。

                            

    二個節點的圖中,用黑色文字標記的表示主分區,用藍色文字標記的表示複製分區(備份分區)。第一個成員有135個主分區(黑色部分),全部的這些分區都會在第二個成員中有一個副本(藍色部分),一樣的,第一個成員也會有第二個成員的數據副本。

    當增長更多的成員時,Hazelcast會將主數據和備份數據一個接一個的遷移到新成員上,最終達成成員之間數據均衡且相互備份。當Hazelcast發生擴展的時候,只有最小數量的分區被移動。下圖呈現了4個成員節點的分區分佈狀況。

                           

    上面的幾個圖說明了的Hazelcast是如何執行分區的。一般狀況下,分區的分佈狀況是無序的,他們會隨機分佈在集羣中的各個節點中。最重要的是,Hazelcast會平均分配成員以前的分區,並均勻在的成員之間建立備份。

    在Hazelcast 3.6版本中,新增了一種集羣成員:「精簡成員」(lite members),他的特色是不擁有任何分區。「精簡成員」的目標是用於「高密度運算」任務(computationally-heavy task executions。估計是指CPU密集型運算)或者註冊監聽(listener) 。雖然「精簡成員」沒有本身的分區,可是他們一樣能夠訪問集羣中其餘成員的分區。

    總的來講,當集羣中的節點發送變更時(進入或退出),都會致使分區在節點中移動並再平衡,以確保數據均勻存儲。但如果「精簡節點」的進入或退出,並不會出現從新劃分分區狀況,由於精簡節點並不會保存任何分區。

數據分區管理

    建立了分區之後,Hazelcast會將全部的數據存放到每一個分區中。它經過哈希運算將數據分佈到每一個分區中。獲取存儲數據Key值(例如map)或value值(例如topic、list),而後進行如下處理:

  1. 將設定的key或value轉換成byte[];
  2. 對轉換後的byte[]進行哈希計算;
  3. 將哈希計算的結果和分區的數量(271)進行模運算(同餘運算、mod運算、%運算)。

    由於byte[]是和271進行同模運算,所以計算結果必定會在0~270之間,根據這個值能夠指定到用於存放數據的分區。

分區表

    當建立分區之後,集羣中的全部成員必須知道每一個分區被存儲到了什麼節點。所以集羣還須要維護一個分區表來追蹤這些信息。

    當啓動第一個節點時,一個分區表將隨之建立。表中包含分區的ID和標記了他所屬的集羣節點。分區表的目標就是讓集羣中全部節點(包括「精簡節點」)都能獲取到數據存儲信息,確保每一個節點都知道數據在哪。集羣中最老的節點(一般狀況下是第一個啓動的成員)按期發送分區表給全部的節點。以這種方式,當分區的全部權發生變更時,集羣中的全部節點都會被通知到。分區的全部權發生變更有不少種狀況,好比,新加入一個節點、或節點離開集羣等。若是集羣中最先啓動的節點被關閉,那麼隨後啓動的節點將會繼承發送分區表的任務,繼續將分區表發送給全部成員。

原文地址:https://my.oschina.net/chkui/blog/729698

相關文章
相關標籤/搜索