Terracotta設計原理分析--(部分內容來自官方描述)


因為工作中歷史產品采用了terracotta作為分布式緩存線性擴展平台,因此不得不提前對其原理做了相關了解,當然其中很多的設計思想和oracle、memcached的設計相似,但也有自己的亮點,那就是JVM的懶加載細粒度拷貝以及線性擴展,使得序列化對象消耗大大降低,提高CPU使用率以及內存無縫線性擴展。

我在研究terracotta的時候,沒有急於去嘗試環境搭建以及demo實現,首先我去了解了一下為什么之前產品為什么會選型使用它、它是什么、能做什么、和其他相似的技術相比有什么優勢,最后才做了相關的demo驗證,以及相關產品中的調優,那么現在讓我們來揭開它的面紗吧(文章內容從官方、網絡、公司資源庫整理而來)!

Terracotta是什么
Terracotta是一種分布式java集群技術,它巧妙得隱藏了多個分布式JVM帶來的復雜性,使得java對象能夠透明得在多個JVM集群中進行分享和同步,並能夠進行持久化。從某種意義上講它類似於hadoop中的zookeeper,可以作為zookeeper之外的另外一種選擇。
Terracotta采用的是一種被稱之為中心輻射的架構。在這種架構里運行着分布式應用程序的JVM們在啟動時都會與一台中心Terracotta服務器相連。Terracotta服務器負責存儲DSO對象數據,協調JVM之間的並發線程。Terracotta庫位於應用程序JVM中,在類加載過程中,它們用來對類的字節碼進行增強,處理同步塊內的lock和unlock請求,處理應用JVM之間的wait(),notify()請求,處理運行時和Terracotta服務器的聯系等等

 

terracotta能做什么
是一個JVM級的開源群集框架,它最重要的一個功能就是DSO(Distributed Shared Object),通過DSO我們可以把那些被頻繁訪問的,重要的數據緩存在TC Server上,然后供集群里的不同JVM共享,這樣減輕了數據庫的負載,它同時還提供:HTTP Session復制,spring security整合、與Hibernate整合,分布式緩存(收購了java開源緩存項目Ehcache以及Java任務調度項目Quartz,並對其做了深度整合),POJO群集(比如spring框架中引入terracotta,那么spring中的bean就可以分布式集群了。bean被分布式了,那么我們可以無縫得擴展我們的web系統,我們的web系統也天然得有了故障轉移機制),跨越群集的JVM來實現分布式應用程序協調(采用代碼注入的方式,所以你不需要修改任何),同時TC Server本身也可以配置成集群的形式,宕機的TC Server中的活動自動而無縫地轉移到集群里的及其他TC Server上去


為什么要選擇terracotta
Terracotta是一個可以在不改變任何現有系統代碼的基礎上提供HA和HP的一個優秀解決方案,與其他集群相比:
1、 由於大多數Web應用服務器都采用Java序列化和數據廣播方式實現session數據的共享,導致任何一個節點對任何session數據進行修改,都造成大量的內存、CPU以及網絡帶寬的消耗。這種消耗隨着應用服務器節點的增加而成級數級增長。當節點數目超過4個以上的時候,經常由於session復制導致的消耗過高,使得整個集群的吞吐量反而開始下降。由於普通session復制機制性能和可用性的缺陷,很多web開發人員不得不通過數據保存和共享session相關的數據,因此又增加了對數據庫的壓力,形成新的性能瓶頸,Terracotta集群實現sessino數據的共享。不使用廣播機制,避免了Java序列化,只把被修改的字段的數據傳遞給服務器和使用節點,大大減少CPU和內存消耗
2、 利用服務器實現網絡擴展內存,使得有限內存的客戶端節點可以訪問遠大於其內存容量的數據結構,而不必擔心發生內存溢出的異常
3、 數據保存在服務器端,因此客戶端JVM宕機不會造成數據丟失
4、 增量數據傳遞,智能數據推送,最大限度減少對網絡的負擔,使得客戶端JVM可以橫向擴展
5、 服務器分片,實現服務器數據存儲及數據吞吐量橫向擴展
6、 通過服務器實現共享數據持久化,通過服務器集群實現容錯等
7、 無需學習新的API,大大降低開發成本
8、 廣泛支持各種應用服務器:WebLogic, WebSphere, Tomcat, JBoss, Jetty, Geronimo等等,自動session數據遷移,集群范圍內數據可視以及強大的管理和監控功能界面,大大方便對整合集群的共享數據、性能數據、軟硬件指標等的實施監控、調試、優化(通過JMX開放服務器監控信息)
9、 企業版的Terracotta服務器還提供了數據分片功能,使得集群吞吐量隨着服務器數量的增加達到線性增長

上述應該能夠清楚的知道它是什么、做什么用了,那么下面我們就來通過簡單的原理設計思想來提高自己設計的能力,我們應該如何做才能使其優勢最大化

 

注入與字節碼
普通的應用程序是怎么獲得這種分布式的集群行為呢?當應用程序類在被JVM加載的時候,它們通過字節碼增強技術被偷偷注入了分布式集群行為(通過配置文件配置后獲得)。這種字節碼增強注入技術其實很常用,在很多AOP框架中都得以采用,比如AspectJ和AspectWerkz。類的字節碼在加載的時候由Terracotta庫進行解析和檢查。然后這些字節碼會被傳遞到JVM重新構造成一個類,在此之前這些字節碼會根據配置被修改。
為了維護對象的修改,PUTFIELD和GETFIELD字節指令被進行了重載。PUTFIELD指令被替換了,能夠存儲對一個分布式對象的各個域的修改。GETFILED字節指令重載后能夠在需要的時候從服務器獲取對象的域數據,但這么做的前提是此時它還沒有從服務器的查詢中獲取到被這個域所引用的對象,此時該域引用的對象還沒有在JVM堆中被實例化。也就是說GETFIELD是一個lazy initialize模式,如果域為空才會加載域數據,否則不會加載。
為了管理線程之間的協調,MONITORENTER和MONITOREXIT字節碼指令也被重載了,INVOKEVIRTUAL指令也會被重載,這些指令會被各種各樣的object.wait()和objecti.notify()方法用到。MONITORENTER意味着某一個線程對某一個對象monitor的請求。一個線程會阻塞在這條指令上,直到它獲得了對該對象的鎖。一旦獲得了鎖,那么線程就會持有該對象的排他鎖,直至針對該對象的MONITOREXIT指令被執行。如果在MONITORENTER請求查詢monitor的時候這個對象碰巧是一個集群對象-DSO,Terracotta會保證:除了請求這個對象在本地JVM里的本地鎖之外,線程還會在這個DSO對象上的整個JVM集群上的排他鎖,在此之前,該線程會一直阻塞。當線程釋放本地JVM上對該DSO對象的本地鎖的時候,他也會釋放相應的整個JVM集群上的鎖。
在Terracotta的應用程序中,所有的synchronized方法和synchronized塊往往會被被配置成“autolocking”(我們可以看到在測試的例子中運用到了synchronized,配置文件中對其進行”autolocking”配置:

),這就意味着MONITORENTER和MONITOREXIT方法被進行了字節碼增強處理。當然有些開發人員可能不太願意用顯式的synchronized關鍵字,那么可以在Terracotta配置文件中聲明一個方法為一個locked方法,從而使得應用程序獲得集群同步特性(如上圖配置)。

對象wait()和notify()方法相應的字節碼指令也會被進行字節碼增強。當某一個共享對象的wait()方法被調用時,terracotta服務器會把調用這個wait()方法的線程加入到一個線程隊列中去,這個線程隊列記錄了整個JVM集群中所有等待該對象鎖的所有線程。當這個對象的notify方法被調用時,服務器會確保整個集群中所有阻塞在該對象上的線程會被通知到。一旦該對象的notify在一個JVM中被調用時,terracotta服務器會選擇一個阻塞在該對象上的線程,然后喚醒通知它。當notifyAll被調用時,terracotta服務器會讓所有JVM中等待在該DSO上的所有線程都被喚醒。

 

ROOT、集群對象圖

集群對象從一個共享對象圖中的根開始,這個root可以通過Terracotta配置文件中的一個或多個域進行配置的,當一個root被首先實例化時,這個root對象和這個root所能到達的所有的對象就變成了集群對象:

他們的各個域的數據會被傳遞到服務器上由服務器來存儲,在任何JVM中一旦一個root對象被創建,那么該root對象創建時所對應的那個域就會忽略本地堆對象的分配,取而代之的是分配一個服務器集群對象。這種情況往往發生在第二個應用程序實例創建root對象的時刻,由於root對象已經由第一個應用程序實例化創建了,那么其他應用程序中的,盡管這些root域按照代碼的要求是要通過構造函數來生成對象的,但這些指令都被忽略了,取而代之的是,Terracotta客戶端庫會從服務器獲取root對象,然后在本地堆中實例化它,然后把這個引用賦給相應的域,這些工作都是透明得進行的,被terracotta的庫隱藏了。terracotta的工作機制給我們的應用程序帶來的最主要也是最有價值的地方也就在於此。一旦某一個對象變成集群對象了,那么他就會被分配一個整個集群范圍內唯一的object id,並且在剩下的生命周期內一直保持集群特性。一旦某一個集群對象突然變成了任何root對象都不可達的狀態,並且在整個集群JVM中都沒有它的任何實例,那么這個集群對象會被terracotta的服務器GC進行回收。

 

細粒度的更改復制

包含DSO對象變化的transaction只包含那些已經發生變化的域的數據。這些transaction會被發送到Terracotta服務器和其他集群JVM上從而保持集群一致性。服務器不是廣播式的將transaction發送到所有其它的JVM上,這些transaction只會被發送到特定的JVM上,這些JVM包含transaction代表的對象,並且這些對象在這些JVM的堆上進行了實例化。也就是說,terracotta服務器只發送其它JVM必須使用到的transaction的一部分。比如:一個線程改變了對象a中的域p和對象b中的域q,那么只有a.p和b.q的域數據會被放到transaction中並被發送到服務器。更改某個DSO的多個相關的域在terracotta中一定是原子的,一定是要用synchronized關鍵字進行同步的,根據前面的定義,那么更改DSO的這些域也就是transaction,被發送到服務器上。(只更改某個DSO的單個域本身就是原子的,不需要用synchronized進行顯式同步,也就是說某個DSO單個域的修改本身就是一個transaction)。Terracotta服務器會決定哪些JVM含有a和b的實例。如果一個JVM的本地堆只含有對象a的實例而並沒有對象b的實例,那么這個JVM只會收到a.p的數據,而不會收到b.q的數據

 

對象的標識和序列化

 

由於對象的變化的歷史記錄只是局限在對象的域層次並且transaction只包含的是DSO的片段而不是整個對象圖,因此terracotta不會使用Java序列化來復制傳播對象的變化。舉個例子,我們更改一個product對象的price域,那么我們需要發送到集群上就是發生變化的對象的ID,對象發生變化的域的ID,和包含price域數據的字節。Product對象的其余部分就被忽略了。如果我們采用object序列化技術,那么product對象的每個域都需要被序列化,而各個域又會引用到其它對象,這樣最后的結果是僅僅是對product對象的一個double域的修改就會導致整個對象圖都會被序列化,Terracotta目前采用的做法相比java序列化而言更加高效,因為它只發送了發生改變的對象而不是整個對象圖。但是,除了效率,利用對象域作為改變的基本單位還有另外的好處:保持對象的唯一性,如果采用java序列化來在集群間轉移對象的變化,那么JVM集群的客戶端應用程序需要對發生改變的對象進行反序列化並且不得不替換已有的對象實例。這就是為什么許多其他的集群和分布式技術會要求提供PUT/GET API,因為一個集群對象從集群中被獲取出來一定需要一個GET調用,當對象發生變化時,它一定需要一個PUT調用把發生改變的對象放回集群上去,Terracotta沒有這樣的限制。一個集群對象也像普通對象一樣在JVM堆中生存着。當對象是被JVM本地進行修改的,那么這些修改直接作用在JVM的堆上。如果這些修改是通過遠程的在另外一個JVM上的這個DSO對象的引用進行的,那么本地的JVM就會收到這個transaction並且直接將transaction作用在已在本地堆中存在的對象上。這意味着針對某個DSO,在任何給定的時刻一個JVM在堆中只可能擁有一個對它的實例引用。

有了terracotta,你不必考慮每個JVM實際上存放的是一個對象的copy,也不必考慮當本地進行完修改時再把對象的copy放回集群中去。由於沒有對象拷貝的概念,一個集群對象就是一個在集群堆中的普通對象,行為也和普通對象沒有什么區別,任何對集群對象的修改對任何擁有該集群對象引用的對象也是有效的。由於保留了對象的唯一性,這使得集群的,多JVM的應用在行為表現上和普通的,單JVM的應用沒有什么區別。在集群中保持對象唯一性帶來的簡潔和強大使得分布式特性從應用程序的設計和實施中剝離出來。分布式行為被推給了terracotta服務器,已經融入了基礎架構。就像Java的GC使得內存管理的代碼從應用層代碼中完全消失了,terracotta使得分布式計算行為也從應用代碼中消失。

 

虛擬堆/網絡內存

 

 

 

除了在多個JVM之間分享和同步對象,Terracotta也能夠針對非常大的對象圖有效得使用本地堆。隨着共享對象圖不斷增大,可能它已經不能夠放在單個JVM的堆中了,Terracotta會保持一個對分布式對象圖的配置窗口,這樣當分布式對象對堆的使用超過一定閾值后就會按照一定的策略被flush out出去。當這些被flush out的對象片段又被使用時,再從terracotta服務器中取出來放到JVM的堆中。你可以把terracotta服務器看成一個無限大的虛擬堆或者網絡內存,由於terracotta可以看成一個巨大的可以無限擴展的網絡內存,你可以裝進整個對象,使之成為一個分布式的對象圖,而不必關心它的大小,對象只需要裝載一次,這大大減小了應用程序實例的啟動的時間。

文章引自:http://yale.iteye.com/blog/1541612


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM