此係統主要分爲幾個低偶合的層次組裝而成,具有多IDC分佈的特性。從底往上依次爲DB層,MC池子層,Nginx+PHP Fast-CGI層,LVS層,而後經過DNS接入用戶。每一層都具有良好的擴展性以及災備能力。php
DNS:html
從大的結構上來講,此係統分佈在4個主要IDC,網通電信各2。 機器數量按照網通:電信 1:2的比例配置,同一運營商下的兩IDC機器數量等同。這樣在宕掉一個IDC的狀況下,能夠經過切換DNS,臨時訪問相近的IDC,達到IDC間災備。mysql
LVS層:nginx
每一個IDC的接入層經過LVS做四層負載均衡。IDC內部任何接入機器宕掉,能夠經過failover機制在一分鐘內自動摘除。程序員
Nginx+PHP Fast-CGI層:算法
經過fpm管理PHP Fast-CGI進程,Nginx通unix域協議與fpm通訊。轉發 *.php的請求以及響應。使用APC做爲OP代碼加速器,加速php響應。PHP直接與MC池子或Mysql層進行交互。sql
MC池子層:shell
每一個IDC內部的多個MC機是做爲一個總體來管理的,也就是說,假如你有100個數據,4個MC機,那麼每一個MC上只會存25個數據,而不會每一個都存100個。好處是,得到了至關於之前4倍的內存池, 增大了緩存命中率。更額外的好處是,減小了,本IDC內部各MC間數據同步的時間開銷,使得本IDC任何一服務器更新的數據,同IDC內其餘服務器當即可見。也使得IDC之間MC的同步更爲節約,每一個IDC一個池子,每一個池子只須要寫一份數據。編程
MC池子增減機器的問題:若是是傳統的hash算法根據key值,將數據平均的hash到4臺機器上,那麼按照新的hash規則,增長一臺機器後,將會有近80%的緩存失效,形成大量數據遷移。因此咱們改用Consistent Hashing,首先求出memcached服務器(節點)的哈希值,並將其配置到0~2的32次方的圓上。而後用一樣的方法求出存儲數據的鍵的哈希值,並映射到圓上。而後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。若是超過2的32次方仍然找不到服務器,就會保存到第一臺memcached服務器上。它能最大限度地抑制鍵的從新分佈。並且,有的實現還採用了虛擬節點的思想,使得分佈更加均勻。由服務器臺數(m)和增長的服務器臺數(n)計算增長服務器後的命中率計算公式以下:(1 - n/(n+m) ) * 100
宕機重啓後初始化的問題:若是初始化的數據只從一臺DB上得到,那麼在高峯期間必將壓垮DB,因此,咱們須要將這個壓力,分散到本IDC的多臺DB上。細節會在基礎類庫中進行講述。
異地MC池子的數據同步:已經封裝在基礎類庫中,更新MC時指定特定的參數,就會在其餘異地MC池子上也進行更新。因此咱們若是在北京添了一個數據,那麼在深圳也會馬上看到。
DB層:
主從結構集羣,主庫可切換,從庫互爲備份。從DB宕掉一臺,能夠自動跳過,不影響服務,目前經過基礎類庫實現,之後會採用內部DNS。全部從庫直接和主庫相連,結構簡單,方便管理。不過寫的數據量相對比較小的,並且咱們容許DB延遲(由MC來保證明時性,或者異地原本就能夠接受短暫的延遲),這裏將不是問題。
MC和DB寫隊列:
在專線中斷時不影響寫操做(讀都是本地的,固然也不受影響),並且在更新數據時可以快速返回(由於不須要遠程通訊)。
具體來講就是將MC的異地寫,以及DB的寫操做封裝成直接追加寫本地隊列文件。每一個機器上有個守護進程,每0.1秒檢查一次,有則rename,而後開始將隊列數據逐條往深圳數據中心post,同時每成功一條記錄當前offset,以便意外宕掉後接着上次的後面處理。(優化點的能夠改成批量確認,冒進點的,可使用內存盤優化寫速度)。若是線路不通,或者數據中心寫失敗,post程序將sleep and retry,線路恢復則繼續,不會丟數據。這裏有個細節須要注意,就是往數據中心同步的時候,儘可能發往同一臺機器,也即綁定,以保證序列的順序,這個保證能夠經過對post守護進程的pid取mod來實現,若是綁定的機器出了故障,則應該選定另一臺機器綁定,一旦檢測到之前的機器好了,則應該切回來,以保證應有的負載均衡。另外,最外層的接入端也一樣須要注意這個問題,咱們對lvs作了會話保持,以確保同一用戶在至關長的一段時間內,會訪問到同一臺機器。對於僅僅使用DNS輪詢,又沒有使用長鏈接的服務,若是在短期內作數據更新,確定會出現亂序的問題。除非你各服務器時間都很精準,並且,每一個記錄寫的時候打上精確到ms的時間戳。
而後,深圳數據中心接收到post的數據後,根據消息類型按必定規則的命名分別存放,這樣即便增長一種新的消息,也能夠方便的使用隊列。好比收到MC的消息後,要寫三份,分別發往三個IDC,各自不受影響。DB,能夠根據不一樣的port實例子來建立隊列,省得有一個port宕了,影響其餘。具體實現細節能夠本身調整,一樣得有個程序按期(好比每0.1秒,0.01秒也無所謂,對cpu的消耗很是小)檢查隊列文件存在就rename,而後往master裏寫。
以上則能保證,DB的master宕掉,或者專線的中斷狀況下,用戶服務基本不受影響,只是異地的同步會延遲一些。額外收穫是未來作DB升級調整的時候,能夠用隊列分流,分別作不一樣的處理。
四,環境配置以及底層基礎類庫
PHP網絡版環境:每IDC之間差別化的Nginx環境變量配置,使得相同的應用程序在不一樣的IDC運行時,使用各自IDC內部的MC以及DB等資源。
PHP
PHP
好比/path/php/bin/php test.php
或在php程序第一行加上
#!/path/php/bin/php
而後賦予php腳本可執行權限,使做爲shell程序運行。
因爲 php 做爲 shell 運行時,沒法繼承 nginx 配置的環境變量。因此它必須依賴一個獨立的配置文件。
底層基礎類庫:
底層基礎類庫,起到粘合劑的做用,將環境配置,服務器資源等所有結合起來,使得這些資源以及配置信息對上層開發人員透明,無須考慮。總的來講有如下一些功能。
1,
[DB_stock]
host = m3306_sz_gtimg_cn
類庫目前除支持模擬DNS外,還直接兼容真實域名以及IP地址,方便未來進行數據遷移。之因此沒有直接使用 DNS是也有歷史緣由的。
2,
在沒有內部DNS的狀況下,將DB讀寫分離,賬戶選擇,鏈接的創建(包括 什麼時候候真正創建鏈接,創建長鏈接,仍是短鏈接,鏈接的綁定,以及生命週期),負載均衡以及failvoer等封裝成對用戶透明的以下簡單用法:
//指定以讀(r) 或 寫(w) 的方式打開一個庫。不指定的狀況下,默認是」r」方式打開。
$db_r =new MYSQL(「testdb」,「r」);
<?
require_once (dirname(__FILE__).'/../mysql.php');
$db_w =new MYSQL("test","w");
$arr = array(
if( $db_w->insert("test",$arr) )
{
}
else
{
}
echo "read ............<br>";
$db_r =new MYSQL("test","r");
$sql = "select * from test";
if( $result = $db_r->query($sql))
{
}
else
{
}
3,
簡化MC池子使用方法,支持異地MC數據同步。
//if rset=1,the mc data will sync to other idcs, default 0.
function set($key, $value, $flag, $expire, $r_set=0)
<?php
require_once (dirname(__FILE__).'/../memcache.php');
$mc = new MC("test");
for($i=0;$i<2;$i++)
{
}
for($i=0;$i<2;$i++)
{
}
注意:我在引用頭文件時候,都會使用相似require_once (dirname(__FILE__).'/../mysql.php'); 的方法。我暫且管它叫動態絕對路徑。它的好處是,
跟相對路徑比:當一個頭文件被多層引用時,目錄結構又不一致,不會找不到。
也不會去搜索全部可能的目錄,執行多餘的fstat以及open操做。
跟絕對路徑相比:若是我將整個項目,包括頭文件所有平移到別的目錄,不須要挨個修改文件。
固然,你也能夠採用另一個方法,將本項目相關的配置信息絕對路徑放在一個統一的文件裏,而後經過任何一種方法引用那個文件。