網站高數據量訪問下數據庫瓶頸解決方案

數據庫一向是網站架構中最具挑戰性的,瓶頸通常出現在這裏。又拍網的照片數據量很大,數據庫也幾度出現嚴重的壓力問題。 因此,這裏我主要介紹一下又拍網在分庫設計這方面的一些嘗試。

又拍網是一個照片分享社區,從2005年6月至今積累了260萬用戶,1.1億張照片,目前的日訪問量爲200多萬。5年的發展歷程裏經歷過許多起 伏,也積累了一些經驗,在這篇文章裏,我要介紹一些我們在技術上的積累。

又拍網和大多數Web2.0站點一樣,構建於大量開源軟件之上,包括MySQL PHP nginx Python memcached redis Solr Hadoop RabbitMQ 等 等。又拍網的服務器端開發語言主要是PHP Python ,其 中PHP 用於編寫 Web邏輯(通過HTTP和用戶直接打交道), 而Python 則主要用於開發內部服務和後臺任務。在客戶端則使用了大量的Javascript, 這裏要感謝一下MooTools 這 個JS框架,它使得我們很享受前端開發過程。 另外,我們把圖片處理過程從PHP 進程裏獨立出來變成一個服務。這個服務基於nginx ,但是是作爲nginx 的一個模塊 而開放REST API。

開發語言

圖1: 開發語言

由於PHP 的 單線程模型,我們把耗時較久的運算和I/O操作從HTTP請求週期中分離出來, 交給由Python 實現的 任務進程來完成,以保證請求響應速度。這些任務主要包括:郵件發送、數據索引、數據聚合和好友動態推送(稍候會有介紹)等等。通常這些任務由用戶觸發,並 且,用戶的一個行爲可能會觸發多種任務的執行。 比如,用戶上傳了一張新的照片,我們需要更新索引,也需要向他的朋友推送一條新的動態。PHP 通過消息隊列(我們 用的是RabbitMQ ) 來觸發任務執行。

PHP和Python的協作

圖2: PHP和Python的協作

數據庫一向是網站架構中最具挑戰性的,瓶頸通常出現在這裏。又拍網的照片數據量很大,數據庫也幾度出現嚴重的壓力問題。 因此,這裏我主要介紹一下又拍網在分庫設計這方面的一些嘗試。

分庫設計

和很多使用MySQL 的 2.0站點一樣,又拍網的MySQL 集 羣經歷了從最初的一個主庫一個從庫、到一個主庫多個從庫、 然後到多個主庫多個從庫的一個發展過程。

數據庫的進化過程

最初是由一臺主庫和一臺從庫組成,當時從庫只用作備份和容災,當主庫出現故障時,從庫就手動變成主庫,一般情況下,從庫不作讀寫操作(同步除外)。 隨着壓力的增加,我們加上了memcached ,當時只用其緩存單行數據。 但是,單行數據的緩存並不能很好地解決壓力問題,因爲單行數據的查詢通常很快。所以我們把一些實時性要求不高的Query放到從庫去執行。後面又通過添加 多個從庫來分流查詢壓力,不過隨着數據量的增加,主庫的寫壓力也越來越大。

在參考了一些相關產品和其它網站的做法後,我們決定進行數據庫拆分。也就是將數據存放到不同的數據庫服務器中,一般可以按兩個緯度來拆分數據:

垂直拆分 :是指按功能模塊拆分,比如可以將羣組相關表和照片相關表存放在不同的數據庫中,這種方式多個數據庫之 間的表結構不同

水平拆分 :而水平拆分是將同一個表的數據進行分塊保存到不同的數據庫中,這些數據庫中的表結構 完全相同

拆分方式

一般都會先進行垂直拆分,因爲這種方式拆分方式實現起來比較簡單,根據表名訪問不同的數據庫就可以了。但是垂直拆分方式並不能徹底解決所有壓力問 題,另外,也要看應用類型是否合適這種拆分方式。如果合適的話,也能很好的起到分散數據庫壓力的作用。比如對於豆瓣 我覺得比較適合 採用垂直拆分, 因爲豆瓣 的 各核心業務/模塊(書籍、電影、音樂)相對獨立,數據的增加速度也比較平穩。不同的是,又拍網的核心業務對象是用戶上傳的照片,而照片數據的增加速度隨着 用戶量的增加越來越快。壓力基本上都在照片表上,顯然垂直拆分並不能從根本上解決我們的問題,所以,我們採用水平拆分的方式。

拆分規則

水平拆分實現起來相對複雜,我們要先確定一個拆分規則,也就是按什麼條件將數據進行切分。 一般2.0網站都以用戶爲中心,數據基本都跟隨用戶,比如用戶的照片、朋友和評論等等。因此一個比較自然的選擇是根據用戶來切分。每個用戶都對應一個數據 庫,訪問某個用戶的數據時, 我們要先確定他/她所對應的數據庫,然後連接到該數據庫進行實際的數據讀寫。

那麼,怎麼樣對應用戶和數據庫呢?我們有這些選擇:

按算法對應

最簡單的算法是按用戶ID的奇偶性來對應,將奇數ID的用戶對應到數據庫A,而偶數ID的用戶則對應到數據庫B。這個方法的最大問題是,只能分成兩 個庫。另一個算法是按用戶ID所在區間對應,比如ID在0-10000之間的用戶對應到數據庫A, ID在10000-20000這個範圍的對應到數據庫B,以此類推。按算法分實現起來比較方便,也比較高效,但是不能滿足後續的伸縮性要求,如果需要增加 數據庫節點,必需調整算法或移動很大的數據集, 比較難做到在不停止服務的前提下進行擴充數據庫節點。

按索引 / 映射表對應

這種方法是指建立一個索引表,保存每個用戶的ID和數據庫ID的對應關係,每次讀寫用戶數據時先從這個表獲取對應數據庫。新用戶註冊後,在所有可用 的數據庫中隨機挑選一個爲其建立索引。這種方法比較靈活,有很好的伸縮性。一個缺點是增加了一次數據庫訪問,所以性能上沒有按算法對應好。

比較之後,我們採用的是索引表的方式,我們願意爲其靈活性損失一些性能,更何況我們還有memcached , 因爲索引數據基本不會改變的緣故,緩存命中率非常高。所以能很大程度上減少了性能損失。

數據訪問過程

圖4: 數據訪問過程

索引表的方式能夠比較方便地添加數據庫節點,在增加節點時,只要將其添加到可用數據庫列表裏即可。 當然如果需要平衡各個節點的壓力的話,還是需要進行數據的遷移,但是這個時候的遷移是少量的,可以逐步進行。要遷移用戶A的數據,首先要將其狀態置爲遷 移數據中 ,這個狀態的用戶不能進行寫操作,並在頁面上進行提示。 然後將用戶A的數據全部複製到新增加的節點上後,更新映射表,然後將用戶A的狀態置爲正常 ,最後將原來對應的數據庫上 的數據刪除。這個過程通常會在臨晨進行,所以,所以很少會有用戶碰到遷移數據中 的情況。

當然,有些數據是不屬於某個用戶的,比如系統消息、配置等等,我們把這些數據保存在一個全局庫中。

問題

分庫會給你在應用的開發和部署上都帶來很多麻煩。

不能執行跨庫的關聯查詢

如果我們需要查詢的數據分佈於不同的數據庫,我們沒辦法通過JOIN的方式查詢獲得。比如要獲得好友的最新照片,你不能保證所有好友的數據都在同一 個數據庫裏。一個解決辦法是通過多次查詢,再進行聚合的方式。我們需要儘量避免類似的需求。有些需求可以通過保存多份數據來解決,比如User-A和 User-B的數據庫分別是DB-1和DB-2, 當User-A評論了User-B的照片時,我們會同時在DB-1和DB-2中保存這條評論信息,我們首先在DB-2中的photo_comments表 中插入一條新的記錄,然後在DB-1中的user_comments表中插入一條新的記錄。這兩個表的結構如下圖所示。這樣我們可以通過查詢 photo_comments表得到User-B的某張照片的所有評論, 也可以通過查詢user_comments表獲得User-A的所有評論。另外可以考慮使用全文檢索工具來解決某些需求, 我們使用Solr 來 提供全站標籤檢索和照片搜索服務。

評論表結構

圖5: 評論表結構

不能保證數據的一致 / 完整性

跨庫的數據沒有外鍵約束,也沒有事務保證。比如上面的評論照片的例子, 很可能出現成功插入photo_comments表,但是插入user_comments表時卻出錯了。一個辦法是在兩個庫上都開啓事務,然後先插入 photo_comments,再插入user_comments, 然後提交兩個事務。這個辦法也不能完全保證這個操作的原子性。

所有查詢必須提供數據庫線索

比如要查看一張照片,僅憑一個照片ID是不夠的,還必須提供上傳這張照片的用戶的ID(也就是數據庫線索),才能找到它實際的存放位置。因此,我們 必須重新設計很多URL地址,而有些老的地址我們又必須保證其仍然有效。我們把照片地址改成/photos/{username}/{photo_id} /的形式,然後對於系統升級前上傳的照片ID, 我們又增加一張映射表,保存photo_id和user_id的對應關係。當訪問老的照片地址時,我們通過查詢這張表獲得用戶信息, 然後再重定向到新的地址。

自增 ID

如果要在節點數據庫上使用自增字段,那麼我們就不能保證全局唯一。這倒不是很嚴重的問題,但是當節點之間的數據發生關係時,就會使得問題變得比較麻 煩。我們可以再來看看上面提到的評論的例子。如果photo_comments表中的comment_id的自增字段,當我們在DB- 2.photo_comments表插入新的評論時, 得到一個新的comment_id,假如值爲101,而User-A的ID爲1,那麼我們還需要在DB-1.user_comments表中插入(1, 101 ...)。 User-A是個很活躍的用戶,他又評論了User-C的照片,而User-C的數據庫是DB-3。 很巧的是這條新評論的ID也是101,這種情況很用可能發生。那麼我們又在DB-1.user_comments表中插入一行像這樣(1, 101 ...)的數據。 那麼我們要怎麼設置user_comments表的主鍵呢(標識一行數據)?可以不設啊,不幸的是有的時候(框架、緩存等原因)必需設置。那麼可以以 user_id、 comment_id和photo_id爲組合主鍵,但是photo_id也有可能一樣(的確很巧)。看來只能再加上photo_owner_id了, 但是這個結果又讓我們實在有點無法接受,太複雜的組合鍵在寫入時會帶來一定的性能影響,這樣的自然鍵看起來也很不自然。所以,我們放棄了在節點上使用自增 字段,想辦法讓這些ID變成全局唯一。爲此增加了一個專門用來生成ID的數據庫,這個庫中的表結構都很簡單,只有一個自增字段id。 當我們要插入新的評論時,我們先在ID庫的photo_comments表裏插入一條空的記錄,以獲得一個唯一的評論ID。 當然這些邏輯都已經封裝在我們的框架裏了,對於開發人員是透明的。 爲什麼不用其它方案呢,比如一些支持incr操作的Key-Value數據庫。我們還是比較放心把數據放在MySQL裏。 另外,我們會定期清理ID庫的數據,以保證獲取新ID的效率。

實現

我們稱前面提到的一個數據庫節點爲Shard,一個Shard由兩個臺物理服務器組成, 我們稱它們爲Node-A和Node-B,Node-A和Node-B之間是配置成Master-Master相互複製的。 雖然是Master-Master的部署方式,但是同一時間我們還是隻使用其中一個,原因是複製的延遲問題, 當然在Web應用裏,我們可以在用戶會話裏放置一個A或B來保證同一用戶一次會話裏只訪問一個數據庫, 這樣可以避免一些延遲問題。但是我們的Python 任務是 沒有任何狀態的,不能保證和PHP 應 用讀寫相同的數據庫。那麼爲什麼不配置成Master-Slave呢?我們覺得只用一臺太浪費了,所以我們在每臺服務器上都創建多個邏輯數據庫。 如下圖所示,在Node-A和Node-B上我們都建立了shard_001和shard_002兩個邏輯數據庫, Node-A上的shard_001和Node-B上的shard_001組成一個Shard,而同一時間只有一個邏輯數據庫處於Active狀態。 這個時候如果需要訪問Shard-001的數據時,我們連接的是Node-A上的shard_001, 而訪問Shard-002的數據則是連接Node-B上的shard_002。以這種交叉的方式將壓力分散到每臺物理服務器上。 以Master-Master方式部署的另一個好處是,我們可以不停止服務的情況下進行表結構升級, 升級前先停止複製,升級Inactive的庫,然後升級應用,再將已經升級好的數據庫切換成Active狀態, 原來的Active數據庫切換成Inactive狀態,然後升級它的表結構,最後恢復複製。 當然這個步驟不一定適合所有升級過程,如果表結構的更改會導致數據複製失敗,那麼還是需要停止服務再升級的。

Database Layout

圖6: 數據庫佈局

前面提到過添加服務器時,爲了保證負載的平衡,我們需要遷移一部分數據到新的服務器上。爲了避免短期內遷移的必要,我們在實際部署的時候,每臺機器 上部署了8個邏輯數據庫, 添加服務器後,我們只要將這些邏輯數據庫遷移到新服務器就可以了。最好是每次添加一倍的服務器, 然後將每臺的1/2邏輯數據遷移到一臺新服務器上,這樣能很好的平衡負載。當然,最後到了每臺上只有一個邏輯庫時,遷移就無法避免了,不過那應該是比較久 遠的事情了。

我們把分庫邏輯都封裝在我們的PHP框架裏了,開發人員基本上不需要被這些繁瑣的事情困擾。下面是使用我們的框架進行照片數據的讀寫的一些例子:

 

$Photos = new ShardedDBTable('Photos', 'yp_photos', 'user_id', array(
                'photo_id'    => array('type' => 'long', 'primary' => true, 'global_auto_increment' => true),
                'user_id'     => array('type' => 'long'),
                'title'       => array('type' => 'string'),
                'posted_date' => array('type' => 'date'),
            ));

    $photo = $Photos->new_object(array('user_id' => 1, 'title' => 'Workforme'));
    $photo->insert();

    // 加載ID爲10001的照片,注意第一個參數爲用戶ID
    $photo = $Photos->load(1, 10001);

    // 更改照片屬性
    $photo->title = 'Database Sharding';
    $photo->update();

    // 刪除照片
    $photo->delete();

    // 獲取ID爲1的用戶在2010-06-01之後上傳的照片
    $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));
?>

首先要定義一個ShardedDBTable對象,所有的API都是通過這個對象開放。第一個參數是對象類型名稱, 如果這個名稱已經存在,那麼將返回之前定義的對象。你也可以通過get_table('Photos')這個函數來獲取之前定義的Table對象。 第二個參數是對應的數據庫表名,而第三個參數是數據庫線索字段,你會發現在後面的所有API中全部需要指定這個字段的值。 第四個參數是字段定義,其中photo_id字段的global_auto_increment屬性被置爲true,這就是前面所說的全局自增ID, 只要指定了這個屬性,框架會處理好ID的事情。

如果我們要訪問全局庫中的數據,我們需要定義一個DBTable對象。

 

<?php
    $Users = new DBTable('Users', 'yp_users', array(
                'user_id'  => array('type' => 'long', 'primary' => true, 'auto_increment' => true),
                'username' => array('type' => 'string'),
            ));
?>

DBTable是ShardedDBTable的父類,除了定義時參數有些不同(DBTable不需要指定數據庫線索字段),它們提供一樣的 API。

緩存

我們的框架提供了緩存功能,對開發人員是透明的。

 

<?php
    $photo = $Photos->load(1, 10001);
?>

比如上面的方法調用,框架先嚐試以Photos-1-10001爲Key在緩存中查找,未找到的話再執行數據庫查詢並放入緩存。當更改照片屬性或刪 除照片時,框架負責從緩存中刪除該照片。這種單個對象的緩存實現起來比較簡單。稍微麻煩的是像下面這樣的列表查詢結果的緩存。

 

<?php
    $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));
?>

我們把這個查詢分成兩步,第一步先查出符合條件的照片ID,然後再根據照片ID分別查找具體的照片信息。 這麼做可以更好的利用緩存。第一個查詢的緩存Key爲Photos-list-{shard_key}-{md5(查詢條件SQL語句)}, Value是照片ID列表(逗號間隔)。其中shard_key爲user_id的值1。目前來看,列表緩存也不麻煩。 但是如果用戶修改了某張照片的上傳時間呢,這個時候緩存中的數據就不一定符合條件了。所以,我們需要一個機制來保證我們不會從緩存中得到過期的列表數據。 我們爲每張表設置了一個revision,當該表的數據發生變化時(調用insert/update/delete方法), 我們就更新它的revision,所以我們把列表的緩存Key改爲Photos-list-{shard_key}-{md5(查詢條件SQL語 句)}-{revision}, 這樣我們就不會再得到過期列表了。

revision信息也是存放在緩存裏的,Key爲Photos-revision。這樣做看起來不錯,但是好像列表緩存的利用率不會太高。因爲我 們是以整個數據類型的revision爲緩存Key的後綴,顯然這個revision更新的非常頻繁,任何一個用戶修改或上傳了照片都會導致它的更新,哪 怕那個用戶根本不在我們要查詢的Shard裏。要隔離用戶的動作對其他用戶的影響,我們可以通過縮小revision的作用範圍來達到這個目的。 所以revision的緩存Key變成Photos-{shard_key}-revision,這樣的話當ID爲1的用戶修改了他的照片信息時, 只會更新Photos-1-revision這個Key所對應的revision。

因爲全局庫沒有shard_key,所以修改了全局庫中的表的一行數據,還是會導致整個表的緩存失效。 但是大部分情況下,數據都是有區域範圍的,比如我們的幫助論壇的主題帖子, 帖子屬於主題。修改了其中一個主題的一個帖子,沒必要使所有主題的帖子緩存都失效。 所以我們在DBTable上增加了一個叫isolate_key的屬性。

 

<?php
$GLOBALS['Posts'] = new DBTable('Posts', 'yp_posts', array(
        'topic_id'    => array('type' => 'long', 'primary' => true),
        'post_id'     => array('type' => 'long', 'primary' => true, 'auto_increment' => true),
        'author_id'   => array('type' => 'long'),
        'content'     => array('type' => 'string'),
        'posted_at'   => array('type' => 'datetime'),
        'modified_at' => array('type' => 'datetime'),
        'modified_by' => array('type' => 'long'),
    ), 'topic_id');
?>

注意構造函數的最後一個參數topic_id就是指以字段topic_id作爲isolate_key,它的作用和shard_key一樣用於隔離 revision的作用範圍。

ShardedDBTable繼承自DBTable,所以也可以指定isolate_key。 ShardedDBTable指定了isolate_key的話,能夠更大幅度縮小revision的作用範圍。 比如相冊和照片的關聯表yp_album_photos,當用戶往他的其中一個相冊裏添加了新的照片時, 會導致其它相冊的照片列表緩存也失效。如果我指定這張表的isolate_key爲album_id的話, 我們就把這種影響限制在了本相冊內。

我們的緩存分爲兩級,第一級只是一個PHP數組,有效範圍是Request。而第二級是memcached。這麼做的原因是,很多數據在一個 Request週期內需要加載多次,這樣可以減少memcached的網絡請求。另外我們的框架也會儘可能的發送memcached的gets命令來獲取 數據, 從而減少網絡請求。

總結

這個架構使得我們在很長一段時間內都不必再爲數據庫壓力所困擾。我們的設計很多地方參考了netlog flickr 的實現,因此非常感謝他們將一些實現細節發佈出來。

 

 

接下來就可以根據這些分析來設計應 用系統的查詢框架。在J2EE架構下,對於大數據量的查詢主要採取以下兩種方法:

基於緩存的方式:

從數據庫得到全部(部分)數據,並 將其在服務器端進行緩存,接下來的客戶端請求,將直接從緩存中取得需要的數據。這其實就是經典的Value List Handler模式的原理(如下 圖),該模式創建一個ValueListHandler對象來控制查詢的執行以及結果集的緩存,它通過DAO(Data Access Object)來 執行查詢,並將數據庫返回的結果集(傳輸對象Transfer Object的集合)緩存起來,接下來的客戶端查詢請求將直接從緩存中獲得。它的特點主要 體現在兩點:服務器端緩存數據,每次只返回客戶端本次操作所需的數據,通過這兩個措施來減少數據庫的訪問次數以及增加客戶端的響應速度,達到最優的查詢效 果。它主要適用於數據量不是非常大,變化不是很頻繁(或者變化頻繁但是有規律)且不具有成長性的情況,比如人事系統的大部分查詢就非常適合採取這種方式。

 

採用這種方式,要特別注意第一次查 詢問題,避免響應性能達不到要求,因爲每個查詢第一次都需要連接數據庫,從中獲取數據並緩存起來,所以第一次查詢會比接下來的查詢都顯得更慢一些。

對於數據的緩存,有以下幾種實現方 式:

直接緩存在服務器 端

Value List Handler 模式就採取這種方式,並且可以根據不同的情況採取不同的緩存策略,比如Transfer Object集合,CachedRowSet等,這取決於你的 DAO實現策略。

用臨時表來保存查 詢結果

WLDJ(www.sys- con.com/weblogic/)雜誌2004年第7期上有一篇名爲“Handling Large Database Result Sets”的 文章,它詳細介紹瞭如何利用臨時表來改良Value List Handler模式以支持大型的J2EE應用。

 

基於查詢的方式:

不進行數據緩存,客戶端的每次數據 請求都需要進行實際的數據庫查詢,這種方式適用於量大,具有成長性,變化頻繁的數據。該方式的特點是每次查詢的時間都大致相等,不會存在基於緩存的方式的 第一次查詢問題,但後續的操作會比緩存方式的查詢慢一些。採取這種方式的查詢框架設計更具有可擴展性以及對數據變化更好的應變能力,在大部分的業務系統中 都推薦使用該方式。

使用這種方法,每次查詢應該只從數 據庫獲得客戶端所需的數據,這樣就涉及到如何獲得部分數據的問題。一種是查詢出符合條件的所有記錄,然後遍歷該記錄集,根據上次查詢結果來比較記錄中的某 些字段獲取本次查詢需要的部分數據,由於要對記錄集進行遍歷,效率不高,一般都不推薦使用,而往往採用另一種增加sql查詢語句條件的方式。

 

數據庫連接池

Java的數據庫連接通常利用 JDBC(Java Database Connectivity)來做的,JDBC是一個Java擴展API,它爲編程者提供了基於SQL查詢的數據訪問能力。但是,通過JDBC連接訪問數據 庫服務器的能力,對於有大數據量訪問的應用程序還是不夠的。引入連接池的目的就是改善依賴於數據庫的java服務器代碼的性能和併發性,解決三層架構中的 中間層與第三層之間的開銷。

數據庫連接是一種關鍵的有限的昂貴 的資源,這一點在多用戶的網頁應用程序中體現得尤爲突出。對數據庫連接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫連 接池正是針對這個問題提出來的。

連接池是數據庫連接的緩衝區,它位 於內存中,由於使用了連接池,所以連接可以被重複使用,而無需在每次使用完後,都需要進行創建和撤銷操作。所以採用連接池具有減少工作量,更加容易移植和 總體性能更高的優點。

數據庫連接池負責分配、管理和釋放 數據庫連接,它允許應用程序重複使用一個現有的數據庫連接,而再不是重新建立一個;釋放空閒時間超過最大空閒時間的數據庫連接來避免因爲沒有釋放數據庫連 接而引起的數據庫連接遺漏。這項技術能明顯提高對數據庫操作的性能。

數據庫連接池在初始化時將創建一定 數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這麼多的 連接數量。連接池的最大數據庫連接數量限定了這個連接池能佔有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待 隊列中。數據庫連接池的最小連接數和最大連接數的設置要考慮到下列幾個因素:

1) 最小連接數是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費;

2) 最大連接數是連接池能申請的最大連接數,如果數據庫連接請求超過此數,後面的數據庫連接請求將被加入到等待隊列中,這會影響之後的數據庫操作。

3) 如果最小連接數與最大連接數相差太大,那麼最先的連接請求將會獲利,之後超過最小連接數量的連接請求等價於建立一個新的數據庫連接。不過,這些大於最小連 接數的數據庫連接在使用完不會馬上被釋放,它將被放到連接池中等待重複使用或是空閒超時後被釋放。

基於Web的中間件產品較多,它們 都可以實現池的作用,在我們中南電力設計院的質量管理信息系統,電子媒體查詢系統和OA系統中選用Tomcat作爲數據庫中間件進行數據庫連接池管理,利 用Tomcat自身的管理機制來監視數據庫連接的數量、使用情況, 程序效率得到顯著提高。(限於篇幅,連接池配置代碼略)                                       

 

結論

目前隨着企業網絡應用的發展,大數 據量訪問的效率問題已經越來越突出,甚至在某些地方成了企業發展的瓶頸。在中南電力設計院的MIS軟件開發過程中也遇到同樣的問題,曾經出現在網頁上生成 一個樹狀結構需要等待30秒左右的情況,而那時後臺數據還不到一萬條,經過對軟件的數據訪問效率的研究之後,提出了這套解決方案,同樣的一個樹狀結構用戶 最多只需要等待3秒鐘就可以完成。經過幾年的運行與調試,目前各系統總體體能表現非常地穩定,能夠滿足企業用戶需要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章