#
1.3. 分區表索引的使用: 分區表和一般表一樣可以建立索引,分區表可以創建局部索引和全局索引。當分區中出現許多事務并且要保證所有分區中的數據記錄的唯一性時采用全局索引。
1.3.1. 局部索引分區的建立:
以下為引用的內容: SQL> create index dinya_idx_t on dinya_test(item_id) 2 local 3 ( 4 partition idx_1 tablespace dinya_space01, 5 partition idx_2 tablespace dinya_space02, 6 partition idx_3 tablespace dinya_space03 7 ); Index created. SQL> |
看查詢的執行計劃,從下面的執行計劃可以看出,系統已經使用了索引:
以下為引用的內容: SQL> select * from dinya_test partition(part_01) t where t.item_id=12; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=187) 1 0 TABLE ACCESS (BY LOCAL INDEX ROWID) OF ’DINYA_TEST’ (Cost= 2 Card=1 Bytes=187) 2 1 INDEX (RANGE SCAN) OF ’DINYA_IDX_T’ (NON-UNIQUE) (Cost=1 Card=1) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 4 consistent gets 0 physical reads 0 redo size 334 bytes sent via SQL*Net to client 309 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client
1 sorts (memory) 0 sorts (disk) 2 rows processed SQL> |
1.3.2. 全局索引分區的建立
全局索引建立時global 子句允許指定索引的范圍值,這個范圍值為索引字段的范圍值:
以下為引用的內容: SQL> create index dinya_idx_t on dinya_test(item_id) 2 global partition by range(item_id) 3 ( 4 partition idx_1 values less than (1000) tablespace dinya_space01, 5 partition idx_2 values less than (10000) tablespace dinya_space02, 6 partition idx_3 values less than (maxvalue) tablespace dinya_space03 7 ); Index created. SQL> |
本例中對表的item_id字段建立索引分區,當然也可以不指定索引分區名直接對整個表建立索引,如:
以下為引用的內容: SQL> create index dinya_idx_t on dinya_test(item_id); Index created. SQL> |
同樣的,對全局索引根據執行計劃可以看出索引已經可以使用:
以下為引用的內容: SQL> select * from dinya_test t where t.item_id=12; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=3 Bytes=561) 1 0 TABLE ACCESS (BY GLOBAL INDEX ROWID) OF ’DINYA_TEST’ (Cost =2 Card=3 Bytes=561) 2 1 INDEX (RANGE SCAN) OF ’DINYA_IDX_T’ (NON-UNIQUE) (Cost=1 Card=3) Statistics ---------------------------------------------------------- 5 recursive calls 0 db block gets 10 consistent gets 0 physical reads
0 redo size 420 bytes sent via SQL*Net to client 309 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 3 sorts (memory) 0 sorts (disk) 5 rows processed SQL> |
1.4. 分區表的維護:
了解了分區表的建立、索引的建立、表和索引的使用后,在應用的還要經常對分區進行維護和管理。日常維護和管理的內容包括:增加一個分區,合并一個分區及刪除分區等等。下面以范圍分區為例說明增加、合并、刪除分區的一般操作:
1.4.1. 增加一個分區:
以下為引用的內容: SQL> alter table dinya_test 2 add partition part_04 values less than(to_date(’2012-01-01’,’yyyy-mm-dd’)) tablespace dinya_spa ce03; Table altered. SQL> |
增加一個分區的時候,增加的分區的條件必須大于現有分區的最大值,否則系統將提示ORA-14074 partition bound must collate higher than that of the last partition 錯誤。
1.4.2. 合并一個分區:
以下為引用的內容: SQL> alter table dinya_test merge partitions part_01,part_02 into partition part_02; Table altered. SQL> |
在本例中將原有的表的part_01分區和part_02分區進行了合并,合并后的分區為part_02,如果在合并的時候把合并后的分區定為part_01的時候,系統將提示ORA-14275 cannot reuse lower-bound partition as resulting partition 錯誤。
1.4.3. 刪除分區:
以下為引用的內容: SQL> alter table dinya_test drop partition part_01; Table altered. SQL> |
刪除分區表的一個分區后,查詢該表的數據時顯示,該分區中的數據已全部丟失,所以執行刪除分區動作時要慎重,確保先備份數據后再執行,或將分區合并。
1.5. 總結: 需要說明的是,本文在舉例說名分區表事務操作的時候,都指定了分區,因為指定了分區,系統在執行的時候則只操作該分區的記錄,提高了數據處理的速度。不要指定分區直接操作數據也是可以的。在分區表上建索引及多索引的使用和非分區表一樣。此外,因為在維護分區的時候可能對分區的索引會產生一定的影響,可能需要在維護之后重建索引,相關內容請參考分區表索引部分的文檔
1.2. 分區表操作
以上了解了三種分區表的建表方法,下面將使用實際的數據并針對按日期的范圍分區來測試分區表的數據記錄的操作。
1.2.1. 插入記錄:
以下為引用的內容: SQL> insert into dinya_test values(1,12,’BOOKS’,sysdate); 1 row created. SQL> insert into dinya_test values(2,12, ’BOOKS’,sysdate+30); 1 row created.
SQL> insert into dinya_test values(3,12, ’BOOKS’,to_date(’2006-05-30’,’yyyy-mm-dd’)); 1 row created. SQL> insert into dinya_test values(4,12, ’BOOKS’,to_date(’2007-06-23’,’yyyy-mm-dd’)); 1 row created. SQL> insert into dinya_test values(5,12, ’BOOKS’,to_date(’2011-02-26’,’yyyy-mm-dd’)); 1 row created. SQL> insert into dinya_test values(6,12, ’BOOKS’,to_date(’2011-04-30’,’yyyy-mm-dd’)); 1 row created. SQL> commit; Commit complete. SQL> |
按上面的建表結果,2006年前的數據將存儲在第一個分區part_01上,而2006年到2010年的交易數據將存儲在第二個分區part_02上,2010年以后的記錄存儲在第三個分區part_03上。
1.2.2. 查詢分區表記錄:
以下為引用的內容: SQL> select * from dinya_test partition(part_01); TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE -------------------------------------------------------------------------------- 1 12 BOOKS 2005-1-14 14:19: 2 12 BOOKS 2005-2-13 14:19: SQL> SQL> select * from dinya_test partition(part_02); TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE -------------------------------------------------------------------------------- 3 12 BOOKS 2006-5-30 4 12 BOOKS 2007-6-23 SQL> SQL> select * from dinya_test partition(part_03); TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE -------------------------------------------------------------------------------- 5 12 BOOKS 2011-2-26 6 12 BOOKS 2011-4-30 SQL> |
從查詢的結果可以看出,插入的數據已經根據交易時間范圍存儲在不同的分區中。這里是指定了分區的查詢,當然也可以不指定分區,直接執行select * from dinya_test查詢全部記錄。
在也檢索的數據量很大的時候,指定分區會大大提高檢索速度。
1.2.3. 更新分區表的記錄:
以下為引用的內容: SQL> update dinya_test partition(part_01) t set t.item_description=’DESK’ where t.transaction_id=1; 1 row updated. SQL> commit; Commit complete. SQL> |
這里將第一個分區中的交易ID=1的記錄中的item_description字段更新為“DESK”,可以看到已經成功更新了一條記錄。但是當更新的時候指定了分區,而根據查詢的記錄不在該分區中時,將不會更新數據,請看下面的例子:
以下為引用的內容: SQL> update dinya_test partition(part_01) t set t.item_description=’DESK’ where t.transaction_id=6; 0 rows updated. SQL> commit; Commit complete. SQL> |
指定了在第一個分區中更新記錄,但是條件中限制交易ID為6,而查詢全表,交易ID為6的記錄在第三個分區中,這樣該條語句將不會更新記錄。
1.2.4. 刪除分區表記錄:
以下為引用的內容: SQL> delete from dinya_test partition(part_02) t where t.transaction_id=4; 1 row deleted. SQL> commit; Commit complete. SQL> |
上面例子刪除了第二個分區part_02中的交易記錄ID為4的一條記錄,和更新數據相同,如果指定了分區,而條件中的數據又不在該分區中時,將不會刪除任何數據。
摘要:在大量業務數據處理的項目中,可以考慮使用分區表來提高應用系統的性能并方便數據管理,本文詳細介紹了分區表的使用。
在大型的企業應用或企業級的數據庫應用中,要處理的數據量通??梢赃_到幾十到幾百GB,有的甚至可以到TB級。雖然存儲介質和數據處理技術的發展也很快,但是仍然不能滿足用戶的需求,為了使用戶的大量的數據在讀寫操作和查詢中速度更快,Oracle提供了對表和索引進行分區的技術,以改善大型應用系統的性能。
使用分區的優點:
·增強可用性:如果表的某個分區出現故障,表在其他分區的數據仍然可用;
·維護方便:如果表的某個分區出現故障,需要修復數據,只修復該分區即可;
·均衡I/O:可以把不同的分區映射到磁盤以平衡I/O,改善整個系統性能;
·改善查詢性能:對分區對象的查詢可以僅搜索自己關心的分區,提高檢索速度。
Oracle數據庫提供對表或索引的分區方法有三種:
·范圍分區
·Hash分區(散列分區)
·復合分區
下面將以實例的方式分別對這三種分區方法來說明分區表的使用。為了測試方便,我們先建三個表空間。
以下為引用的內容: create tablespace dinya_space01 datafile ’/test/demo/oracle/demodata/dinya01.dnf’ size 50M create tablespace dinya_space01 datafile ’/test/demo/oracle/demodata/dinya02.dnf’ size 50M create tablespace dinya_space01 datafile ’/test/demo/oracle/demodata/dinya03.dnf’ size 50M |
1.1. 分區表的創建 1.1.1. 范圍分區
范圍分區就是對數據表中的某個值的范圍進行分區,根據某個值的范圍,決定將該數據存儲在哪個分區上。如根據序號分區,根據業務記錄的創建日期進行分區等。
需求描述:有一個物料交易表,表名:material_transactions。該表將來可能有千萬級的數據記錄數。要求在建該表的時候使用分區表。這時候我們可以使用序號分區三個區,每個區中預計存儲三千萬的數據,也可以使用日期分區,如每五年的數據存儲在一個分區上。
根據交易記錄的序號分區建表:
以下為引用的內容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null, 5 item_description varchar2(300), 6 transaction_date date not null 7 ) 8 partition by range (transaction_id) 9 ( 10 partition part_01 values less than(30000000) tablespace dinya_space01, 11 partition part_02 values less than(60000000) tablespace dinya_space02, 12 partition part_03 values less than(maxvalue) tablespace dinya_space03 13 ); Table created. |
建表成功,根據交易的序號,交易ID在三千萬以下的記錄將存儲在第一個表空間dinya_space01中,分區名為:par_01,在三千萬到六千萬之間的記錄存儲在第二個表空間:
dinya_space02中,分區名為:par_02,而交易ID在六千萬以上的記錄存儲在第三個表空間dinya_space03中,分區名為par_03.
根據交易日期分區建表:
以下為引用的內容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null,
5 item_description varchar2(300), 6 transaction_date date not null 7 ) 8 partition by range (transaction_date) 9 ( 10 partition part_01 values less than(to_date(’2006-01-01’,’yyyy-mm-dd’)) tablespace dinya_space01, 11 partition part_02 values less than(to_date(’2010-01-01’,’yyyy-mm-dd’)) tablespace dinya_space02, 12 partition part_03 values less than(maxvalue) tablespace dinya_space03 13 ); Table created. |
這樣我們就分別建了以交易序號和交易日期來分區的分區表。每次插入數據的時候,系統將根據指定的字段的值來自動將記錄存儲到制定的分區(表空間)中。
當然,我們還可以根據需求,使用兩個字段的范圍分布來分區,如partition by range ( transaction_id ,transaction_date), 分區條件中的值也做相應的改變,請讀者自行測試。
1.1.2. Hash分區(散列分區)
散列分區為通過指定分區編號來均勻分布數據的一種分區類型,因為通過在I/O設備上進行散列分區,使得這些分區大小一致。如將物料交易表的數據根據交易ID散列地存放在指定的三個表空間中:
以下為引用的內容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null, 5 item_description varchar2(300), 6 transaction_date date 7 ) 8 partition by hash(transaction_id) 9 ( 10 partition part_01 tablespace dinya_space01, 11 partition part_02 tablespace dinya_space02, 12 partition part_03 tablespace dinya_space03 13 ); Table created. |
建表成功,此時插入數據,系統將按transaction_id將記錄散列地插入三個分區中,這里也就是三個不同的表空間中。
1.1.3. 復合分區
有時候我們需要根據范圍分區后,每個分區內的數據再散列地分布在幾個表空間中,這樣我們就要使用復合分區。復合分區是先使用范圍分區,然后在每個分區內再使用散列分區的一種分區方法,如將物料交易的記錄按時間分區,然后每個分區中的數據分三個子分區,將數據散列地存儲在三個指定的表空間中:
以下為引用的內容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null, 5 item_description varchar2(300), 6 transaction_date date 7 ) 8 partition by range(transaction_date)subpartition by hash(transaction_id) 9 subpartitions 3 store in (dinya_space01,dinya_space02,dinya_space03) 10 ( 11 partition part_01 values less than(to_date(’2006-01-01’,’yyyy-mm-dd’)), 12 partition part_02 values less than(to_date(’2010-01-01’,’yyyy-mm-dd’)), 13 partition part_03 values less than(maxvalue) 14 ); Table created. |
該例中,先是根據交易日期進行范圍分區,然后根據交易的ID將記錄散列地存儲在三個表空間中。
三種策略
Hibernate支持三種基本的繼承映射策略:
-
每個類分層結構一張表(table per class hierarchy)
-
每個子類一張表(table per subclass)
-
每個具體類一張表(table per concrete class)
此外,Hibernate還支持第四種稍有不同的多態映射策略:
對于同一個繼承層次內的不同分支,可以采用不同的映射策略,然后用隱式多 態來完成跨越整個層次的多態。但是在同一個<class>
根元素 下,Hibernate不支持混合了元素<subclass>
、 <joined-subclass>
和<union-subclass>
的映射。在同一個<class>
元素下,可以混合使用 “每個類分層結構一張表”(table per hierarchy) 和“每個子類一張表”(table per subclass) 這兩種映射策略,這是通過結合元素<subclass>
和 <join>
來實現的(見后)。
在多個映射文件中,可以直接在hibernate-mapping
根下定義subclass
,union-subclass
和joined-subclass
。也就是說,你可以僅加入一個新的映射文件來擴展類層次。你必須在subclass的映射中指明extends
屬性,給出一個之前定義的超類的名字。注意,在以前,這一功能對映射文件的順序有嚴格的要求,從Hibernate 3開始,使用extends關鍵字的時侯,對映射文件的順序不再有要求;但在每個映射文件里,超類必須在子類之前定義。
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>
9.1.1. 每個類分層結構一張表(Table per class hierarchy)
假設我們有接口Payment
和它的幾個實現類: CreditCardPayment
, CashPayment
, 和ChequePayment
。則“每個類分層結構一張表”(Table per class hierarchy)的映射代碼如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<property name="creditCardType" column="CCTYPE"/>
...
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
采用這種策略只需要一張表即可。它有一個很大的限制:要求那些由子類定義的字段, 如CCTYPE
,不能有非空(NOT NULL)
約束。
9.1.2. 每個子類一張表(Table per subclass)
對于上例中的幾個類而言,采用“每個子類一張表”的映射策略,代碼如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
</class>
需要四張表。三個子類表通過主鍵關聯到超類表(因而關系模型實際上是一對一關聯)。
9.1.3. 每個子類一張表(Table per subclass),使用辨別標志(Discriminator)
注意,對“每個子類一張表”的映射策略,Hibernate的實現不需要辨別字段,而其他 的對象/關系映射工具使用了一種不同于Hibernate的實現方法,該方法要求在超類 表中有一個類型辨別字段(type discriminator column)。Hibernate采用的方法更 難實現,但從關系(數據庫)的角度來看,按理說它更正確。若你愿意使用帶有辨別字 段的“每個子類一張表”的策略,你可以結合使用<subclass>
與<join>
,如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
<join table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
<join table="CHEQUE_PAYMENT" fetch="select">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
</class>
可選的聲明fetch="select"
,是用來告訴Hibernate,在查詢超類時, 不要使用外部連接(outer join)來抓取子類ChequePayment
的數據。
9.1.4. 混合使用“每個類分層結構一張表”和“每個子類一張表”
你甚至可以采取如下方法混和使用“每個類分層結構一張表”和“每個子類一張表”這兩種策略:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
對上述任何一種映射策略而言,指向根類Payment
的 關聯是使用<many-to-one>
進行映射的。
<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>
9.1.5. 每個具體類一張表(Table per concrete class)
對于“每個具體類一張表”的映射策略,可以采用兩種方法。第一種方法是使用 <union-subclass>
。
<class name="Payment">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="sequence"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</union-subclass>
<union-subclass name="CashPayment" table="CASH_PAYMENT">
...
</union-subclass>
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
...
</union-subclass>
</class>
這里涉及三張與子類相關的表。每張表為對應類的所有屬性(包括從超類繼承的屬性)定義相應字段。
這種方式的局限在于,如果一個屬性在超類中做了映射,其字段名必須與所有子類 表中定義的相同。(我們可能會在Hibernate的后續發布版本中放寬此限制。) 不允許在聯合子類(union subclass)的繼承層次中使用標識生成器策略(identity generator strategy), 實際上, 主鍵的種子(primary key seed)不得不為同一繼承層次中的全部被聯合子類所共用.
假若超類是抽象類,請使用abstract="true"
。當然,假若它不是抽象的,需要一個額外的表(上面的例子中,默認是PAYMENT
),來保存超類的實例。
9.1.6. Table per concrete class, using implicit polymorphism
9.1.6. Table per concrete class, using implicit polymorphism
另一種可供選擇的方法是采用隱式多態:
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CREDIT_AMOUNT"/>
...
</class>
<class name="CashPayment" table="CASH_PAYMENT">
<id name="id" type="long" column="CASH_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CASH_AMOUNT"/>
...
</class>
<class name="ChequePayment" table="CHEQUE_PAYMENT">
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</class>
注意,我們沒有在任何地方明確的提及接口Payment
。同時注意 Payment
的屬性在每個子類中都進行了映射。如果你想避免重復, 可以考慮使用XML實體(例如:位于DOCTYPE
聲明內的 [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]
和映射中的&allproperties;
)。
這種方法的缺陷在于,在Hibernate執行多態查詢時(polymorphic queries)無法生成帶 UNION
的SQL語句。
對于這種映射策略而言,通常用<any>
來實現到 Payment
的多態關聯映射。
<any name="payment" meta-type="string" id-type="long">
<meta-value value="CREDIT" class="CreditCardPayment"/>
<meta-value value="CASH" class="CashPayment"/>
<meta-value value="CHEQUE" class="ChequePayment"/>
<column name="PAYMENT_CLASS"/>
<column name="PAYMENT_ID"/>
</any>
對這一映射還有一點需要注意。因為每個子類都在各自獨立的元素<class>
中映射(并且Payment
只是一個接口),每個子類可以很容易的成為另一 個繼承體系中的一部分!(你仍然可以對接口Payment
使用多態查詢。)
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="CREDIT_CARD" type="string"/>
<property name="amount" column="CREDIT_AMOUNT"/>
...
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
<subclass name="VisaPayment" discriminator-value="VISA"/>
</class>
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
<id name="id" type="long" column="TXN_ID">
<generator class="native"/>
</id>
...
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CASH_AMOUNT"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</joined-subclass>
</class>
我們還是沒有明確的提到Payment
。 如果我們針對接口Payment
執行查詢 ——如from Payment
—— Hibernate 自動返回CreditCardPayment
(和它的子類,因為 它們也實現了接口Payment
)、 CashPayment
和Chequepayment
的實例, 但不返回NonelectronicTransaction
的實例。
JVM優化配置《一》
OOM這個縮寫就是Java程序開發過程中讓人最頭痛的問題:Out of Memory。在很多開發人員的開發過程中,或多或少的都會遇到這類問題,這類問題定位比較困難,往往需要根據經驗來判斷可能出現問題的代碼。原因主要是 兩個:對象沒有被釋放(多種情況引起,往往是比較隱蔽的引用導致被Hold而無法被回收)。另一種就是真的Memory不夠用了,需要增加JVM的 Heap來滿足應用程序的需求。最近有同事發的關于解決OOM的問題,讓我了解了原來OOM除了在JVM Heap不夠時會發生,在Native Heap不夠的時候也會發生,同時JVM Heap和Native Heap存在著相互影響和平衡的關系,因此就仔細的去看了關于OOM和JVM配置優化的內容。
OOM
在 其他語言類似于C,Delphi等等由于內存都是由自己分配和管理,因此內存泄露的問題比較常見,同時也是很頭痛的一件事情。而Java的對象生命周期管 理都是JVM來做的,簡化了開發人員的非業務邏輯的處理,但是這種自動管理回收機制也是基于一些規則的,而違背了這些規則的時候,就會造成所謂的 “Memory Leak”。
OOM(Java Heap)
錯誤提示:java.lang.OutOfMemoryError。
這 類OOM是由于JVM分配的給應用的Heap Memory已經被耗盡,可能是因為應用在高負荷的情況下的卻需要很大的內存,因此可以通過修改JVM參數來增加Java Heap Memory(不過也不能無限制增加,后面那種OOM有可能就是因為這個原因而產生)。另一種情況是因為應用程序使用對象或者資源沒有釋放,導致內存消耗 持續增加,最后出現OOM,這類問題引起的原因往往是應用已不需要的對象還被其他有效對象所引用,那么就無法釋放,可能是業務代碼邏輯造成的(異常處理不 夠例如IO等資源),也可能是對于第三方開源項目中資源釋放了解不夠導致使用以后資源沒有釋放(例如JDBC的ResultSet等)。
幾個容易出現問題的場景:
1.應用的緩存或者Collection:如果應用要緩存Java對象或者是在一個Collection中保存對象,那么就要確定是否會有大量的對象存入,要做保護,以防止在大數據量下大量內存被消耗,同時要保證Cache的大小不會無限制增加。
2.生命周期較長的對象:盡量簡短對象的生命周期,現在采用對象的創建釋放代價已經很低,同時作了很好的優化,要比創建一個對象長期反復使用要好。如果能夠設置超時的情景下,盡量設置超時。
3.類似于JDBC的Connection Pool,在使用Pool中的對象以后需要釋放并返回,不然就會造成Pool的不斷增大,在其他Pool中使用也是一樣。同樣ResultSet,IO這類資源的釋放都需要注意。
解決的方法就是查找錯誤或者是增加Java Heap Memory。對于此類問題檢測工具相當多,這里就不做介紹了。
OOM(Native Heap)
錯誤提示:requested XXXX bytes for ChunkPool::allocate. Out of swap space。
Native Heap Memory是JVM 內部使用的Memory,這部分的Memory可以通過JDK提供的JNI的方式去訪問,這部分Memory效率很高,但是管理需要自己去做,如果沒有把 握最好不要使用,以防出現內存泄露問題。JVM 使用Native Heap Memory用來優化代碼載入(JTI代碼生成),臨時對象空間申請,以及JVM內部的一些操作。這次同事在壓力測試中遇到的問題就是這類OOM,也就是 這類Memory耗盡。同樣這類OOM產生的問題也是分成正常使用耗盡和無釋放資源耗盡兩類。無釋放資源耗盡很多時候不是程序員自身的原因,可能是引用的 第三方包的缺陷,例如很多人遇到的Oracle 9 JDBC驅動在低版本中有內存泄露的問題。要確定這類問題,就需要去觀察Native Heap Memory的增長和使用情況,在服務器應用起來以后,運行一段時間后JVM對于Native Heap Memory的使用會達到一個穩定的階段,此時可以看看什么操作對于Native Heap Memory操作頻繁,而且使得Native Heap Memory增長,對于Native Heap Memory的情況我還沒有找到辦法去檢測,現在能夠看到的就是為JVM啟動時候增加-verbose:jni參數來觀察對于Native Heap Memory的操作。另一種情況就是正常消耗Native Heap Memory,對于Native Heap Memory的使用主要取決于JVM代碼生成,線程創建,用于優化的臨時代碼和對象產生。當正常耗盡Native Heap Memory時,那么就需要增加Native Heap Memory,此時就會和我們前面提到增加java Heap Memory的情況出現矛盾。
應用內存組合
對 于應用來說,可分配的內存受到OS的限制,不同的OS對進程所能訪問虛擬內存地址區間直接影響對于應用內存的分配,32位的操作系統通常最大支持4G的內 存尋址,而Linux一般為3G,Windows為2G。然而這些大小的內存并不會全部給JVM的Java Heap使用,它主要會分成三部分:Java Heap,Native Heap,載入資源和類庫等所占用的內存。那么由此可見,Native Heap和 Java Heap大小配置是相互制約的,哪一部分分配多了都可能會影響到另外一部分的正常工作,因此如果通過命令行去配置,那么需要確切的了解應用使用情況,否則 采用默認配置自動監測會更好的優化應用使用情況。
同樣要注意的就是進程的虛擬內存和機器的實際內存還是有區別的,對于機器來說實際內存以及硬盤提供的虛擬內存都是提供給機器上所有進程使用的,因此在設置JVM參數時,它的虛擬內存絕對不應該超過實際內存的大小。
《二》
這 里首先要說明的是這里提到的JVM是Sun的HotSpot JVM 5和以上的版本。性能優化在應用方面可以有很多手段,包括Cache,多線程,各種算法等等。通常情況下是不建議在沒有任何統計和分析的情況下去手動配置 JVM的參數來調整性能,因為在JVM 5以上已經作了根據機器和OS的情況自動配置合適參數的算法,基本能夠滿足大部分的情況,當然這種自動適配只是一種通用的方式,如果說真的要達到最優,那 么還是需要根據實際的使用情況來手動的配置各種參數設置,提高性能。
JVM能夠對性能產生影響的最大部分就是對于內存的管理。從jdk 1.5以后內存管理和分配有了很多的改善和提高。
內存分配以及管理的幾個基本概念和參數說明:
Java Hotspot Mode:
server 和 client兩種模式,如果不配置,JVM會根據應用服務器硬件配置自動選擇模式,server模式啟動比較慢,但是運行期速度得到了優化,client啟動比較快,但是運行期響應沒有server模式的優化,適合于個人PC的服務開發和測試。
Garbage Collector Policy:
在Jdk 1.5的時候已經提供了三種GC,除了原來提供的串行GC(SerialGC)以外,還提供了兩種新的GC:ParallelGC和 ConcMarkSweepGC。ParallelGC采用了多線程并行管理和回收垃圾對象,提高了回收效率,提高了服務器的吞吐量,適合于多處理器的服 務器。ConcMarkSweepGC采用的是并發方式來管理和回收垃圾對象,降低垃圾回收產生的響應暫停時間。這里說一下并發和并行的區別,并發指的是 多個進程并行執行垃圾回收,那么可以很好的利用多處理器,而并行指的是應用程序不需要暫??梢院屠厥站€程并發工作。串行GC適合小型應用和單處理器系 統(無需多線程交互,效率比較高),后兩者適合大型系統。
使用方式就是在參數配置中增加-XX:+UseParallelGC等方式來設置。
對于這部分的配置在網上有很多的實例可以參考,不過最終采用哪一種GC還是要根據具體的情況來分析和選擇。
Heap:
OOM的 各種經歷已經讓每一個架構師開發人員看到了了解Heap的重要性。OOM已經是Heap的臨界點,不得不引起注意,然而Heap對于性能的潛在影響并未被 引起重視,不過和GC配置一樣,在沒有對使用情況作仔細分析和研究的情況下,貿然的去修改Heap配置,可能適得其反,這里就來看一下Heap的一些概念 和對于性能的影響。
我們的應用所能夠得到的最大的Heap受三部分因素的制約:數據處理 模型(32位或者64位操作系統),系統地虛擬內存總數和系統的物理內存總數。首先Heap的大小不能超過不同操作系統的進程尋址范圍,當前大部分系統最 高限度是4G,Windows通常是2G,Linux通常是3G。系統的虛擬內存也是分配的依據,首先是不能超過,然后由于操作系統支持硬盤來做部分的虛 擬內存,如果設置過大,那么對于應用響應來說勢必有影響。再則就是要考慮同一臺服務器上運行多個Java虛擬機所消耗的資源總合也不能超過可用資源。就和 前面OOM分析中的一樣,其實由于OS的數據處理模型的限制,機器本身的硬件內存資源和虛擬內存資源并不一定會匹配,那么在有限的資源下如何調整好資源分 配,對于應用來說尤為重要。
關于Heap的幾個參數設置:
說了Heap的有限資源問題以后,就來看看如何通過配置去改變JVM對于Heap的分配。下面所說的主要是對于Java Heap的分配,那么在申請了Java Heap以后,剩下的可用資源就會被使用到Native Heap。
Xms: java heap初始化時的大小。默認情況是機器物理內存的1/64。這個主要是根據應用啟動時消耗的資源決定,分配少了申請起來會降低啟動速度,分配多了也浪費。
Xmx:java heap的 最大值,默認是機器物理內存的1/4,最大也就到1G。這個值決定了最多可用的Java Heap Memory,分配過少就會在應用需要大量內存作緩存或者零時對象時出現OOM的問題,如果分配過大,那么就會產生上文提到的第二類OOM。所以如何配置 還是根據運行過程中的分析和計算來確定,如果不能確定還是采用默認的配置。
Xmn:java heap新 生代的空間大小。在GC模型中,根據對象的生命周期的長短,產生了內存分代的設計:青年代(內部也分成三部分,類似于整體劃分的作用,可以通過配置來設置 比例),老年代,持久代。每一代的管理和回收策略都不相同,最為活躍的就是青年代,同時這部分的內存分配和管理效率也是最高。通常情況下,對于內存的申請 優先在新生代中申請,當內存不夠時會整理新生代,當整理以后還是不能滿足申請的內存,就會向老年代移動一些生命周期較長的對象。這種整理和移動會消耗資 源,同時降低系統運行響應能力,因此如果青年代設置的過小,就會頻繁的整理和移動,對性能造成影響。那是否把年青代設置的越大越好,其實不然,年青代采用 的是復制搜集算法,這種算法必須停止所有應用程序線程,服務器線程切換時間就會成為應用響應的瓶頸(當然永遠不用收集那么就不存在這個問題)。老年代采用 的是串行標記收集的方式,并發收集可以減少對于應用的影響。
Xss:線程堆棧最大值。允許更多的虛擬內存空間地址被Java Heap使用。
以下是sun公司的性能優化白皮書中提到的幾個例子:
1.對于吞吐量的調優。機器配置:4G的內存,32個線程并發能力。
java
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-Xmx3800m -Xms3800m
配置了最大Java Heap來充分利用系統內存。
-Xmn2g
創建足夠大的青年代(可以并行被回收)充分利用系統內存,防止將短期對象復制到老年代。
-Xss128
減少默認最大的線程棧大小,提供更多的處理虛擬內存地址空間被進程使用。
-XX:+UseParallelGC
采用并行垃圾收集器對年青代的內存進行收集,提高效率。
-XX:ParallelGCThreads=20
減少垃圾收集線程,默認是和服務器可支持的線程最大并發數相同,往往不需要配置到最大值。
2
.嘗試采用對老年代并行收集
java
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-Xmx3550m -Xms3550m
內存分配被減小,因為ParallelOldGC會增加對于Native Heap的需求,因此需要減小Java Heap來滿足需求。
-XX:+UseParallelOldGC
采用對于老年代并發收集的策略,可以提高收集效率。
3
.提高吞吐量,減少應用停頓時間
java
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
選擇了并發標記交換收集器,它可以并發執行收集操作,降低應用停止時間,同時它也是并行處理模式,可以有效地利用多處理器的系統的多進程處理。
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31
表示在青年代中Eden和Survivor比例,設置增加了Survivor的大小,越大的survivor空間可以允許短期對象盡量在年青代消亡。
-XX:TargetSurvivorRatio=90
允許90%的空間被占用,超過默認的50%,提高對于survivor的使用率。
類似的例子網上很多,這兒就不在列下來了,最終是否采取自己配置來替換默認配置還是要根據虛擬機的使用情況來分析和配置。
Spring配置文件中關于事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。
DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問時,DataSource實際為SessionFactory,TransactionManager的實現為HibernateTransactionManager。
具體如下圖:
_thumb.jpg)
根據代理機制的不同,總結了五種Spring事務的配置方式,配置文件如下:
第一種方式:每個Bean都有一個代理
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定義事務管理器(聲明式的事務) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<!– 配置DAO –>
<bean id=”userDaoTarget” class=”com.bluesky.spring.dao.UserDaoImpl”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”userDao”
class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean”>
<!– 配置事務管理器 –>
<property name=”transactionManager” ref=”transactionManager” />
<property name=”target” ref=”userDaoTarget” />
<property name=”proxyInterfaces” value=”com.bluesky.spring.dao.GeneratorDao” />
<!– 配置事務屬性 –>
<property name=”transactionAttributes”>
<props>
<prop key=”*”>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
第二種方式:所有Bean共享一個代理基類
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定義事務管理器(聲明式的事務) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”transactionBase”
class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean”
lazy-init=”true” abstract=”true”>
<!– 配置事務管理器 –>
<property name=”transactionManager” ref=”transactionManager” />
<!– 配置事務屬性 –>
<property name=”transactionAttributes”>
<props>
<prop key=”*”>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!– 配置DAO –>
<bean id=”userDaoTarget” class=”com.bluesky.spring.dao.UserDaoImpl”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”userDao” parent=”transactionBase” >
<property name=”target” ref=”userDaoTarget” />
</bean>
</beans>
第三種方式:使用攔截器
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定義事務管理器(聲明式的事務) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”transactionInterceptor”
class=”org.springframework.transaction.interceptor.TransactionInterceptor”>
<property name=”transactionManager” ref=”transactionManager” />
<!– 配置事務屬性 –>
<property name=”transactionAttributes”>
<props>
<prop key=”*”>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class=”org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator”>
<property name=”beanNames”>
<list>
<value>*Dao</value>
</list>
</property>
<property name=”interceptorNames”>
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!– 配置DAO –>
<bean id=”userDao” class=”com.bluesky.spring.dao.UserDaoImpl”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
</beans>
第四種方式:使用tx標簽配置的攔截器
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd”>
<context:annotation-config />
<context:component-scan base-package=”com.bluesky” />
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定義事務管理器(聲明式的事務) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<tx:advice id=”txAdvice” transaction-manager=”transactionManager”>
<tx:attributes>
<tx:method name=”*” propagation=”REQUIRED” />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id=”interceptorPointCuts”
expression=”execution(* com.bluesky.spring.dao.*.*(..))” />
<aop:advisor advice-ref=”txAdvice”
pointcut-ref=”interceptorPointCuts” />
</aop:config>
</beans>
第五種方式:全注解
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd”>
<context:annotation-config />
<context:component-scan base-package=”com.bluesky” />
<tx:annotation-driven transaction-manager=”transactionManager”/>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定義事務管理器(聲明式的事務) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
</beans>
此時在DAO上需加上@Transactional注解,如下:
package com.bluesky.spring.dao;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;
import com.bluesky.spring.domain.User;
@Transactional
@Component(“userDao”)
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public List<User> listUsers() {
return this.getSession().createQuery(“from User”).list();
}

}
集群概念
1. 兩大關鍵特性
集群是一組協同工作的服務實體,用以提供比單一服務實體更具擴展性與可用性的服務平臺。在客戶端看來,一個集群就象是一個服務實體,但事實上集群由一組服務實體組成。與單一服務實體相比較,集群提供了以下兩個關鍵特性:
· 可擴展性--集群的性能不限于單一的服務實體,新的服務實體可以動態地加入到集群,從而增強集群的性能。
· 高可用性--集群通過服務實體冗余使客戶端免于輕易遇到out of service的警告。在集群中,同樣的服務可以由多個服務實體提供。如果一個服務實體失敗了,另一個服務實體會接管失敗的服務實體。集群提供的從一個出錯的服務實體恢復到另一個服務實體的功能增強了應用的可用性。
2. 兩大能力
為了具有可擴展性和高可用性特點,集群的必須具備以下兩大能力:
· 負載均衡--負載均衡能把任務比較均衡地分布到集群環境下的計算和網絡資源。
· 錯誤恢復--由于某種原因,執行某個任務的資源出現故障,另一服務實體中執行同一任務的資源接著完成任務。這種由于一個實體中的資源不能工作,另一個實體中的資源透明的繼續完成任務的過程叫錯誤恢復。
負載均衡和錯誤恢復都要求各服務實體中有執行同一任務的資源存在,而且對于同一任務的各個資源來說,執行任務所需的信息視圖(信息上下文)必須是一樣的。
3. 兩大技術
實現集群務必要有以下兩大技術:
· 集群地址--集群由多個服務實體組成,集群客戶端通過訪問集群的集群地址獲取集群內部各服務實體的功能。具有單一集群地址(也叫單一影像)是集群的一個基本特征。維護集群地址的設置被稱為負載均衡器。負載均衡器內部負責管理各個服務實體的加入和退出,外部負責集群地址向內部服務實體地址的轉換。有的負載均衡器實現真正的負載均衡算法,有的只支持任務的轉換。只實現任務轉換的負載均衡器適用于支持ACTIVE-STANDBY的集群環境,在那里,集群中只有一個服務實體工作,當正在工作的服務實體發生故障時,負載均衡器把后來的任務轉向另外一個服務實體。
· 內部通信--為了能協同工作、實現負載均衡和錯誤恢復,集群各實體間必須時常通信,比如負載均衡器對服務實體心跳測試信息、服務實體間任務執行上下文信息的通信。
具有同一個集群地址使得客戶端能訪問集群提供的計算服務,一個集群地址下隱藏了各個服務實體的內部地址,使得客戶要求的計算服務能在各個服務實體之間分布。內部通信是集群能正常運轉的基礎,它使得集群具有均衡負載和錯誤恢復的能力。
集群分類
Linux集群主要分成三大類( 高可用集群, 負載均衡集群,科學計算集群)
高可用集群( High Availability Cluster)
負載均衡集群(Load Balance Cluster)
科學計算集群(High Performance Computing Cluster)
================================================
具體包括:
Linux High Availability 高可用集群
(普通兩節點雙機熱備,多節點HA集群,RAC, shared, share-nothing集群等)
Linux Load Balance 負載均衡集群
(LVS等....)
Linux High Performance Computing 高性能科學計算集群
(Beowulf 類集群....)
分布式存儲
其他類linux集群
(如Openmosix, rendering farm 等..)
詳細介紹
1. 高可用集群(High Availability Cluster)
常見的就是2個節點做成的HA集群,有很多通俗的不科學的名稱,比如"雙機熱備", "雙機互備", "雙機".
高可用集群解決的是保障用戶的應用程序持續對外提供服務的能力。 (請注意高可用集群既不是用來保護業務數據的,保護的是用戶的業務程序對外不間斷提供服務,把因軟件/硬件/人為造成的故障對業務的影響降低到最小程度)。
2. 負載均衡集群(Load Balance Cluster)
負載均衡系統:集群中所有的節點都處于活動狀態,它們分攤系統的工作負載。一般Web服務器集群、數據庫集群和應用服務器集群都屬于這種類型。
負載均衡集群一般用于相應網絡請求的網頁服務器,數據庫服務器。這種集群可以在接到請求時,檢查接受請求較少,不繁忙的服務器,并把請求轉到這些服務器上。從檢查其他服務器狀態這一點上看,負載均衡和容錯集群很接近,不同之處是數量上更多。
3. 科學計算集群(High Performance Computing Cluster)
高性能計算(High Perfermance Computing)集群,簡稱HPC集群。這類集群致力于提供單個計算機所不能提供的強大的計算能力。
高性能計算分類
高吞吐計算(High-throughput Computing)
有一類高性能計算,可以把它分成若干可以并行的子任務,而且各個子任務彼此間沒有什么關聯。象在家搜尋外星人( SETI@HOME -- Search for Extraterrestrial Intelligence at Home )就是這一類型應用。這一項目是利用Internet上的閑置的計算資源來搜尋外星人。SETI項目的服務器將一組數據和數據模式發給Internet上參加SETI的計算節點,計算節點在給定的數據上用給定的模式進行搜索,然后將搜索的結果發給服務器。服務器負責將從各個計算節點返回的數據匯集成完整的數據。因為這種類型應用的一個共同特征是在海量數據上搜索某些模式,所以把這類計算稱為高吞吐計算。所謂的Internet計算都屬于這一類。按照Flynn的分類,高吞吐計算屬于SIMD(Single Instruction/Multiple Data)的范疇。
分布計算(Distributed Computing)
另一類計算剛好和高吞吐計算相反,它們雖然可以給分成若干并行的子任務,但是子任務間聯系很緊密,需要大量的數據交換。按照Flynn的分類,分布式的高性能計算屬于MIMD(Multiple Instruction/Multiple Data)的范疇。
4. 分布式(集群)與集群的聯系與區別
分布式是指將不同的業務分布在不同的地方。
而集群指的是將幾臺服務器集中在一起,實現同一業務。
分布式中的每一個節點,都可以做集群。
而集群并不一定就是分布式的。
舉例:就比如新浪網,訪問的人多了,他可以做一個群集,前面放一個響應服務器,后面幾臺服務器完成同一業務,如果有業務訪問的時候,響應服務器看哪臺服務器的負載不是很重,就將給哪一臺去完成。
而分布式,從窄意上理解,也跟集群差不多, 但是它的組織比較松散,不像集群,有一個組織性,一臺服務器垮了,其它的服務器可以頂上來。
分布式的每一個節點,都完成不同的業務,一個節點垮了,哪這個業務就不可訪問了。
設計模式遵循的一般原則:
1.開-閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開發,對修改關閉.說的是,再設計一個模塊的時候,應當使這個模塊可以在不被修改的前提下被擴展.換言之,應當可以在不必修改源代碼的情況下改變這個模塊的行為,在保持系統一定穩定性的基礎上,對系統進行擴展。這是面向對象設計(OOD)的基石,也是最重要的原則。
2.里氏代換原則(Liskov Substitution Principle,常縮寫為.LSP)
(1).由Barbar Liskov(芭芭拉.里氏)提出,是繼承復用的基石。
(2).嚴格表達:如果每一個類型為T1的對象o1,都有類型為T2的對象o2,使得以T1定義的所有程序P在所有的對象o1都代換稱o2時,程序P的行為沒有變化,那么類型T2是類型T1的子類型.
換言之,一個軟件實體如果使用的是一個基類的話,那么一定適用于其子類,而且它根本不能察覺出基類對象和子類對象的區別.只有衍生類可以替換基類,軟件單位的功能才能不受影響,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新功能。
(3).反過來的代換不成立
(4).<墨子.小取>中說:"白馬,馬也; 乘白馬,乘馬也.驪馬(黑馬),馬也;乘驪馬,乘馬也."
(5).該類西方著名的例程為:正方形是否是長方形的子類(答案是"否")。類似的還有橢圓和圓的關系。
(6).應當盡量從抽象類繼承,而不從具體類繼承,一般而言,如果有兩個具體類A,B有繼承關系,那么一個最簡單的修改方案是建立一個抽象類C,然后讓類A和B成為抽象類C的子類.即如果有一個由繼承關系形成的登記結構的話,那么在等級結構的樹形圖上面所有的樹葉節點都應當是具體類;而所有的樹枝節點都應當是抽象類或者接口.
(7)."基于契約設計(Design By Constract),簡稱DBC"這項技術對LISKOV代換原則提供了支持.該項技術Bertrand Meyer伯特蘭做過詳細的介紹:
使用DBC,類的編寫者顯式地規定針對該類的契約.客戶代碼的編寫者可以通過該契約獲悉可以依賴的行為方式.契約是通過每個方法聲明的前置條件(preconditions)和后置條件(postconditions)來指定的.要使一個方法得以執行,前置條件必須為真.執行完畢后,該方法要保證后置條件為真.就是說,在重新聲明派生類中的例程(routine)時,只能使用相等或者更弱的前置條件來替換原始的前置條件,只能使用相等或者更強的后置條件來替換原始的后置條件.
3.依賴倒置原則(Dependence Inversion Principle),要求客戶端依賴于抽象耦合.
(1)表述:抽象不應當依賴于細節,細節應當依賴于抽象.(Program to an interface, not an implementaction)
(2)表述二:針對接口編程的意思是說,應當使用接口和抽象類進行變量的類型聲明,參量的類型聲明,方法的返還類型聲明,以及數據類型的轉換等.不要針對實現編程的意思就是說,不應當使用具體類進行變量的類型聲明,參量類型聲明,方法的返還類型聲明,以及數據類型的轉換等.
要保證做到這一點,一個具體的類應等只實現接口和抽象類中聲明過的方法,而不應當給出多余的方法.
只要一個被引用的對象存在抽象類型,就應當在任何引用此對象的地方使用抽象類型,包括參量的類型聲明,方法返還類型的聲明,屬性變量的類型聲明等.
(3)接口與抽象的區別就在于抽象類可以提供某些方法的部分實現,而接口則不可以,這也大概是抽象類唯一的優點.如果向一個抽象類加入一個新的具體方法,那么所有的子類型一下子就都得到得到了這個新的具體方法,而接口做不到這一點.如果向一個接口加入了一個新的方法的話,所有實現這個接口的類就全部不能通過編譯了,因為它們都沒有實現這個新聲明的方法.這顯然是接口的一個缺點.
(4)一個抽象類的實現只能由這個抽象類的子類給出,也就是說,這個實現處在抽象類所定義出的繼承的登記結構中,而由于一般語言都限制一個類只能從最多一個超類繼承,因此將抽象作為類型定義工具的效能大打折扣.
反過來,看接口,就會發現任何一個實現了一個接口所規定的方法的類都可以具有這個接口的類型,而一個類可以實現任意多個接口.
(5)從代碼重構的角度上講,將一個單獨的具體類重構成一個接口的實現是很容易的,只需要聲明一個接口,并將重要的方法添加到接口聲明中,然后在具體類定義語句中加上保留字以繼承于該接口就行了.
而作為一個已有的具體類添加一個抽象類作為抽象類型不那么容易,因為這個具體類有可能已經有一個超類.這樣一來,這個新定義的抽象類只好繼續向上移動,變成這個超類的超類,如此循環,最后這個新的抽象類必定處于整個類型等級結構的最上端,從而使登記結構中的所有成員都會受到影響.
(6)接口是定義混合類型的理想工具,所為混合類型,就是在一個類的主類型之外的次要類型.一個混合類型表明一個類不僅僅具有某個主類型的行為,而且具有其他的次要行為.
(7)聯合使用接口和抽象類:
由于抽象類具有提供缺省實現的優點,而接口具有其他所有優點,所以聯合使用兩者就是一個很好的選擇.
首先,聲明類型的工作仍然接口承擔的,但是同時給出的還有一個抽象類,為這個接口給出一個缺省實現.其他同屬于這個抽象類型的具體類可以選擇實現這個接口,也可以選擇繼承自這個抽象類.如果一個具體類直接實現這個接口的話,它就必須自行實現所有的接口;相反,如果它繼承自抽象類的話,它可以省去一些不必要的的方法,因為它可以從抽象類中自動得到這些方法的缺省實現;如果需要向接口加入一個新的方法的話,那么只要同時向這個抽象類加入這個方法的一個具體實現就可以了,因為所有繼承自這個抽象類的子類都會從這個抽象類得到這個具體方法.這其實就是缺省適配器模式(Defaule Adapter).
(8)什么是高層策略呢?它是應用背后的抽象,是那些不隨具體細節的改變而改變的真理. 它是系統內部的系統____隱喻.
4.接口隔離原則(Interface Segregation Principle, ISP)
(1)一個類對另外一個類的依賴是建立在最小的接口上。
(2)使用多個專門的接口比使用單一的總接口要好.根據客戶需要的不同,而為不同的客戶端提供不同的服務是一種應當得到鼓勵的做法.就像"看人下菜碟"一樣,要看客人是誰,再提供不同檔次的飯菜.
(3)胖接口會導致他們的客戶程序之間產生不正常的并且有害的耦合關系.當一個客戶程序要求該胖接口進行一個改動時,會影響到所有其他的客戶程序.因此客戶程序應該僅僅依賴他們實際需要調用的方法.
5.合成/聚合復用原則(Composite/Aggregate Reuse Principle,CARP)
在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分;新的對象通過這些向對象的委派達到復用已有功能的目的.這個設計原則有另一個簡短的表述:要盡量使用合成/聚合,盡量不要使用繼承.
6.迪米特法則(Law of Demeter LoD)又叫做最少知識原則(Least Knowledge Principle,LKP),就是說,一個對象應當對其他對象有盡可能少的了了解.
迪米特法則最初是用來作為面向對象的系統設計風格的一種法則,與1987年秋天由Ian Holland在美國東北大學為一個叫做迪米特(Demeter)的項目設計提出的,因此叫做迪米特法則[LIEB89][LIEB86].這條法則實際上是很多著名系統,比如火星登陸軟件系統,木星的歐羅巴衛星軌道飛船的軟件系統的指導設計原則.
沒有任何一個其他的OO設計原則象迪米特法則這樣有如此之多的表述方式,如下幾種:
(1)只與你直接的朋友們通信(Only talk to your immediate friends)
(2)不要跟"陌生人"說話(Don't talk to strangers)
(3)每一個軟件單位對其他的單位都只有最少的知識,而且局限于那些本單位密切相關的軟件單位.
就是說,如果兩個類不必彼此直接通信,那么這兩個類就不應當發生直接的相互作用,如果其中的一個類需要調用另一個類的某一個方法的話,可以通過第三者轉發這個調用。
7.單一職責原則(Simple responsibility pinciple SRP)
就一個類而言,應該僅有一個引起它變化的原因,如果你能想到多于一個的動機去改變一個類,那么這個類就具有多于一個的職責.應該把多于的指責分離出去,分別再創建一些類來完成每一個職責.
另外:常說的OO五大原則就是指其中的 :1、單一職責原則;2、開放閉合原則;3、里氏替換原則;4、依賴倒置原則;5、接口隔離原則。