開發者在搭建第一個Ignite集羣時,經常會遇到各類障礙,社區在收集了各類常見問題後,整理了一份檢查清單幫助開發者,總之,本文的目的是幫助開發者在一開始就搭建一個正常的集羣,走在正確的道路上。node
準備啓動:設置日誌算法
首先,須要日誌,由於在解決不少問題的時候,須要每一個節點的日誌。數據庫
雖然Ignite默認是開啓日誌記錄的,可是默認爲QUIET
模式,會忽略掉INFO
和DEBUG
級別的日誌輸出,若是系統屬性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秒。
解決方案很簡單,不要指定過多的端口,在生產上,一個端口就夠了,固然也能夠禁用內部網絡的端口掃描保護。
第三個常見問題與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 format
或cannot 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
寫入了長度和數據:
TreeMap
的writeObject()
實現:
Binarylizable
的writeBinary()
和readBinary()
工做方式相似:BinaryTreeMap
將自身包裝到簡單的TreeMap
中並將其寫入OutputStream
,這種方式對於編碼來講微不足道,可是性能提高很是明顯。
BinaryTreeMap
的writeBinary()
實現:
正確地使用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
經過orgId
與Organization
進行關聯,這是一個典型的外鍵。可是僅僅這樣作,將兩個表關聯以後執行SQL查詢,若是集羣中有幾個節點,那麼並不會返回正確的結果。
這是由於默認的SQL關聯僅適用於單個節點,若是SQL遍歷整個集羣以收集數據並返回,會很是低效,這樣會失去分佈式系統的優點,因此Ignite默認只會在本地節點上檢索數據。
若是要得到正確的數據,須要對數據進行並置。要正確地關聯Person
和Organization
,相關的數據應該存儲在同一個節點上。
最簡單的作法是聲明一個關係鍵,該鍵會針對給定的值肯定實際的節點和分區。若是將Person
中的orgId
作爲關係鍵,則具備相同orgId
的人將位於相同的節點上。
若是因爲某種緣由沒法作到這一點,那麼還有另一個比較低效的解決方案,即啓用分佈式關聯。這是在API層面實現的,該過程取決於使用的是Java、JDBC仍是其它的什麼接口,雖然速度會變慢,可是至少會返回正確的結果。
下面要考慮如何定義關係鍵,即如何確認一個特定的ID和一個關聯的字段適合定義關聯性呢?若是定義全部具備相同orgId
的人都被並置,那麼orgId
就是一個不可分割的獨立的塊,就不能再將其分佈在不一樣的節點上。若是數據庫中有10個組織,那麼就能夠在10個節點上有10個不可分割的塊,若是集羣中有更多的節點,那麼就會有部分"多餘"的節點不屬於任何塊,這在運行時很難肯定,因此要提早規劃好。
若是有1個大的組織和9個小的組織,那麼塊的大小就會有所不一樣。可是Ignite在節點間分發數據時並不會考慮某個關係組中有多少記錄,它不會在一個節點上放1個大的塊,而後在另外一個節點上放9個塊來平衡負載,更可能的分配比例是5:5(或者6:4,甚至於7:3)。
如何讓數據分佈均勻呢?能夠看下面的維度:
則須要知足的條件是:
這裏>>
是遠大於,若是知足上圖的條件,數據就會均勻分佈。另外要指出的是,默認的分區數(P)爲1024。
可能分佈仍然不是很均勻,這是Ignite 1.x系列版本的問題。當時的算法爲FairAffinityFunction
,雖然運行正常可是節點間的流量過多,如今的算法是RendezvousAffinityFunction
,可是也不是絕對公平,偏差在正負5-10%。
總結一下,在搭建第一個Ignite集羣時,對於不熟悉Ignite的新人來講,要注意如下的注意事項: