前段時(shí)間把公司的主數(shù)據(jù)庫切了,分成業(yè)務(wù)庫和報(bào)表庫,業(yè)務(wù)庫向報(bào)表庫進(jìn)行實(shí)時(shí)的Replication。這個(gè)項(xiàng)目的上線提升了系統(tǒng)的性能和可維護(hù)性,現(xiàn)在把設(shè)計(jì)時(shí)的考量和所做的工作重新回顧一下,作為備忘。
項(xiàng)目起源
在日常的開發(fā)過程中,功能總是先于性能被考慮。只有當(dāng)用戶抱怨系統(tǒng)性能時(shí),我們才開始頭痛醫(yī)頭,腳痛醫(yī)腳地來解決這些性能問題。
公司的CRM和ERP系統(tǒng)叫作Olite,完全是我們組開發(fā)的。從無到有,功能不斷擴(kuò)展,原先只有CRM模塊,后來加入了ERP模塊,Accounting功能和Report功能。近來出現(xiàn)的情況是當(dāng)某些用戶跑一個(gè)大Report時(shí),正在進(jìn)行業(yè)務(wù)操作的用戶感覺系統(tǒng)響應(yīng)非常慢。通過對系統(tǒng)的性能監(jiān)視發(fā)現(xiàn),在這些時(shí)刻,數(shù)據(jù)庫中產(chǎn)生了大量的鎖,同時(shí)服務(wù)器上出現(xiàn)了CPU和內(nèi)存資源消耗的尖峰。
系統(tǒng)結(jié)構(gòu)
性能問題源于系統(tǒng)的整體結(jié)構(gòu)和發(fā)展過程。Olite系統(tǒng)的Application是基于.NET平臺(tái)的Web Form程序,數(shù)據(jù)庫為SQL Server 2005。其主體結(jié)構(gòu)如下圖所示:
其Application端包括兩個(gè)網(wǎng)站:OliteBase和OliteReport,但連接的都是同一個(gè)數(shù)據(jù)庫。
Olite的Application端其實(shí)很薄,而把大量的業(yè)務(wù)邏輯包裝在存儲(chǔ)過程中,放在數(shù)據(jù)庫端來運(yùn)行。
這種結(jié)構(gòu)在起初性能很好,而且提供給用戶的Report是實(shí)時(shí)的業(yè)務(wù)數(shù)據(jù)。但隨著提供的業(yè)務(wù)模塊,特別是Report的增多(Report對應(yīng)的存儲(chǔ)過程連接的表多,計(jì)算量大,輸出的結(jié)果集大),數(shù)據(jù)庫就成為了瓶頸。
首先,我們做了存儲(chǔ)過程的優(yōu)化,通過創(chuàng)建Trace捕獲性能差的存儲(chǔ)過程,并對其進(jìn)行優(yōu)化。我們這么做了一段時(shí)間,但獲得的收效并不大。我們在優(yōu)化以往存儲(chǔ)過程的同時(shí),隨著系統(tǒng)新功能的上線,又有新的存儲(chǔ)過程進(jìn)入需要優(yōu)化的列表中。
其次,修改數(shù)據(jù)庫設(shè)計(jì),其中包括修改表結(jié)構(gòu)和優(yōu)化索引。在系統(tǒng)局部重構(gòu)表結(jié)構(gòu)與關(guān)系對于性能的提升還是比較明顯的,但這樣的修改會(huì)造成Application端的大量修改,工作量大,風(fēng)險(xiǎn)大,所以不能大規(guī)模實(shí)施。對于索引優(yōu)化又存在矛盾,業(yè)務(wù)模塊(OliteBase)要求數(shù)據(jù)庫中的索引不要太多,以支持高效的插入、修改和刪除,而報(bào)表模塊(OliteReport)則希望在數(shù)據(jù)庫中有更多的索引,以支持高效讀。
最后,我們還試圖提供晚一天的Report服務(wù),來分流主數(shù)據(jù)庫的壓力。每天通過把前一天的備份數(shù)據(jù)庫恢復(fù)在另一臺(tái)服務(wù)器上,并在此服務(wù)器上提供OliteReport2站點(diǎn),給用戶提供Report服務(wù)。但用戶并不喜歡使用OliteReport2,原因分析下來有3個(gè)方面:其一,有時(shí)用戶確實(shí)需要實(shí)時(shí)的Report。其二,OliteReport能存儲(chǔ)用戶的Report條件,而OliteReport2由于每天都會(huì)被刷新,無法保留這些條件。其三,用戶更習(xí)慣打開原來的Report鏈接。
項(xiàng)目需求
上述的各種優(yōu)化方案都沒有根本性的解決系統(tǒng)的性能問題。在這種的背景下我們有了把報(bào)表數(shù)據(jù)庫與業(yè)務(wù)數(shù)據(jù)庫分離的想法。
此項(xiàng)目的需求:
1. 提高用戶對整個(gè)系統(tǒng)性能的感受,Report模塊不要影響到業(yè)務(wù)模塊的運(yùn)行。
2. 用戶可以和原先一樣使用Report模塊,即不增加新的Report站點(diǎn)。
3. 用戶可以和原先一樣存儲(chǔ)填寫的Report條件,以供重復(fù)使用。
4. 盡可能提供最小延時(shí)的Report。
需求1是這個(gè)項(xiàng)目的主要目標(biāo),需求2、3、4是盡可能保證項(xiàng)目所帶來的改變對用戶是透明的。
方案選擇
對于原來的系統(tǒng)結(jié)構(gòu),其Application端已經(jīng)是兩個(gè)獨(dú)立的站點(diǎn)OliteBase和OliteReport。所以只要把OliteBase和OliteReport的數(shù)據(jù)庫進(jìn)行分離,在分離后的兩數(shù)據(jù)庫間進(jìn)行數(shù)據(jù)的同步就行了。這里的關(guān)鍵在于如何進(jìn)行數(shù)據(jù)庫間的同步。
微軟提供了很多種數(shù)據(jù)同步的選擇:1.集群;2.Log Shipping;3.Replication;4.Mirror;5.Integration Service。
微軟提供的這些方案中大部分都是用于做數(shù)據(jù)庫的高可用性的,而我們的項(xiàng)目是以高性能為目標(biāo)的。為了滿足我們自己的需求,應(yīng)選擇那種方案,并做哪些修改呢?
1.集群
這是第一個(gè)被我們否決的方案。配置SQL Server數(shù)據(jù)庫集群,對硬件有較多限制,而且配置相對其他方案復(fù)雜。我們的項(xiàng)目總共的服務(wù)器資源就兩臺(tái),除原先主數(shù)據(jù)庫服務(wù)器外,另一臺(tái)是虛擬機(jī)。
2.Log Shipping
Log Shipping把主數(shù)據(jù)庫的日志傳送到從數(shù)據(jù)庫,并在從數(shù)據(jù)庫上進(jìn)行回放來保證主、從數(shù)據(jù)庫間數(shù)據(jù)的一致,從數(shù)據(jù)庫為只讀。Log Shipping而且還有配置簡單的特點(diǎn),開始時(shí)是我們的一個(gè)候選方案,但在進(jìn)一步的實(shí)驗(yàn)過程中發(fā)現(xiàn)了兩個(gè)問題。第一、Log Shipping可設(shè)置的時(shí)間間隔最小單位為分鐘。第二、當(dāng)從數(shù)據(jù)庫進(jìn)行日志回放時(shí),連接此數(shù)據(jù)庫的連接需要被斷開。其中第二個(gè)問題是難以容忍的,這個(gè)方案也被淘汰了。
3.Replication
Replication的原理和Log Shipping有些相似,但其提供了更多的靈活性。Replication可以只多主數(shù)據(jù)庫的一些表、函數(shù)或存儲(chǔ)過程進(jìn)行,甚至可以對某些符合條件的記錄進(jìn)行。除此之外,其復(fù)制出來的數(shù)據(jù)庫可寫,而且復(fù)制的最小時(shí)間間隔可配置為concurrent(測試下來的時(shí)間延遲為秒級別),而且其配置也較為簡單。經(jīng)過一些實(shí)驗(yàn),我們最后選擇了它。后面會(huì)對其原理和配置進(jìn)一步討論。
4.Mirror
Mirror是SQL Server 2005提供的強(qiáng)大的高可用性方案。其鏡像數(shù)據(jù)庫不能直接讀取,這和我們的需求場景不符合,所以被否了。
5.Integration Service
Integration Service具有最大的靈活性,其可以為數(shù)據(jù)倉庫進(jìn)行數(shù)據(jù)抽取,轉(zhuǎn)換和裝載。但使用Integration Service需要有大量的開發(fā)與測試工作,所以我們也沒選用。
Replication方案細(xì)分
Replication方案又可以分為Snapshot Replication, Transactional Replication, Peer-2-Peer Replication, Merge Replication。
Snapshot Replication:一般用于對于數(shù)據(jù)庫的一次性的完全復(fù)制。
Transactional Replication:用于主數(shù)據(jù)庫向從數(shù)據(jù)庫的單向復(fù)制。
Peer-2-Peer Replication:能進(jìn)行二個(gè)或多個(gè)數(shù)據(jù)庫之間的互相復(fù)制,即從數(shù)據(jù)庫也能向主數(shù)據(jù)庫復(fù)制,這個(gè)功能很強(qiáng)大,但可能會(huì)引起沖突,需要特別關(guān)注保證各庫的數(shù)據(jù)完整性。
Merge Replication:可以把多個(gè)數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行合并后,復(fù)制到目標(biāo)數(shù)據(jù)庫。
對于我們的需求,我們選用了最單純的Transactional Replication。
Transactional Replication原理
在Transactional Replication中有3個(gè)角色:Publisher(發(fā)布者), Distributor(分發(fā)者), Subscriber(訂閱者)。其邏輯圖如下:
在進(jìn)行增量的Transactional Replication之前,Subscriber需要進(jìn)行初始化,使其包含和Publisher一樣的表結(jié)構(gòu)和初始數(shù)據(jù)。
Transactional Replication啟動(dòng)之后,Distributor上的Log Reader Agent會(huì)將讀取Publisher的Log信息,并分揀出被標(biāo)識為replication的INSERT, UPDATE, DELETE語句。此后復(fù)制這些Transaction到Distributor,并寫入distribution數(shù)據(jù)庫。最后Distribution Agent把Distributor上的Transaction運(yùn)送到Subscriber進(jìn)行重放。
注意:在圖中Distribution Agent運(yùn)行在Distributor上,這是在push(推)模式下的情況??梢耘渲脼閜ull(拉)模式,Distribution Agent將運(yùn)行在Subscriber上。
更多關(guān)于Transactional Replication的原理可參考:
http://msdn.microsoft.com/en-us/library/ms151706(SQL.90).aspx
項(xiàng)目中的配置與考量
在前文的系統(tǒng)結(jié)構(gòu)小節(jié),給出了原先的系統(tǒng)結(jié)構(gòu)。我們希望通過這次的項(xiàng)目得到如下所示的系統(tǒng)結(jié)構(gòu):
OliteReport能連接到一個(gè)由主數(shù)據(jù)庫復(fù)制出的單獨(dú)數(shù)據(jù)庫上,這樣這兩個(gè)庫之間的鎖就被隔離了。同時(shí)主數(shù)據(jù)庫與從數(shù)據(jù)庫安排在兩臺(tái)服務(wù)器上(項(xiàng)目中我們把復(fù)制出的數(shù)據(jù)庫放在了一臺(tái)虛擬機(jī)上),那么CPU資源與內(nèi)存資源的消耗也被隔離了。需要注意的是圖中OliteReport除了主要的讀操作外,還有少量的寫操作(這是因?yàn)橛脩艨梢源鎯?chǔ)Report條件)。我們把這些寫指回主數(shù)據(jù)庫,從數(shù)據(jù)庫在下一時(shí)刻的復(fù)制中得到這些數(shù)據(jù)。
在Transactional Replication中有三個(gè)邏輯角色,而項(xiàng)目中只有兩臺(tái)服務(wù)器。我們?nèi)绾蝸戆才胚@三個(gè)邏輯角色呢?
我們是選用Push模式還是Pull模式呢?
另一個(gè)需要考慮的問題是復(fù)制些什么?
還有一個(gè)需要考慮的是如何進(jìn)行從數(shù)據(jù)庫的初始化?
Transactional Replication有些什么前提條件?
如何來配置Transactional Replication?
后期維護(hù)
如何監(jiān)視Replication的性能與異常?
如何進(jìn)行以后的Release?
總結(jié)與設(shè)想
此項(xiàng)目已經(jīng)上線,基本達(dá)到了需求所提出的目標(biāo),但這只是開始,優(yōu)化后的結(jié)構(gòu)給將來系統(tǒng)的擴(kuò)展提供了一個(gè)基礎(chǔ)。
(全文完)