目前在Discuz!NT這個(gè)産品中,數(shù)據庫作(zuò)為(wèi)數(shù)據持久化工具,必定在并發訪問頻繁且負載壓力較大(dà)的情況下成 為(wèi)系統性能的‘瓶頸’。即使使用本地緩存等方式來(lái)解決頻繁訪問數(shù)據庫的問題,但(dàn)仍舊(jiù)會(huì)有(yǒu)大(dà)量的并發請(qǐng)求要訪問動态數(shù)據,雖然 SQL2005及2008以上(shàng)版本中性能不斷提升,查詢計(jì)劃和(hé)存儲過程運行(xíng)得(de)越來(lái)越高(gāo)效,但(dàn)最終還(hái)是 要面臨‘瓶頸’這一問 題。當然這也是許多(duō)大(dà)型網站(zhàn)不斷研究探索各式各樣的方案來(lái)有(yǒu)效降低(dī)數(shù)據訪問負荷的原 因, 其中的‘讀寫分離’方案就是一種被廣泛采用的方案。
目前在Discuz!NT這個(gè)産品中,數(shù)據庫作(zuò)為(wèi)數(shù)據持久化工具,必定在并發訪問頻繁且負載壓力較大(dà)的情況下成 為(wèi)系統性能的‘瓶頸’。即使使用本地緩存等方式來(lái)解決頻繁訪問數(shù)據庫的問題,但(dàn)仍舊(jiù)會(huì)有(yǒu)大(dà)量的并發請(qǐng)求要訪問動态數(shù)據,雖然 SQL2005及2008以上(shàng)版本中性能不斷提升,查詢計(jì)劃和(hé)存儲過程運行(xíng)得(de)越來(lái)越高(gāo)效,但(dàn)最終還(hái)是 要面臨‘瓶頸’這一問 題。當然這也是許多(duō)大(dà)型網站(zhàn)不斷研究探索各式各樣的方案來(lái)有(yǒu)效降低(dī)數(shù)據訪問負荷的原 因, 其中的‘讀寫分離’方案就是一種被廣泛采用的方案。
Discuz!NT這個(gè)産品在其企業版中提供了對‘讀寫分離’機制(zhì)的支持,使對CPU及內(nèi)存消耗嚴重的操作(zuò)(CUD)被 分離到一台或幾台性能很(hěn)高(gāo)的機器(qì)上(shàng),而将頻繁讀取的操作(zuò)(select)放到幾台配置較低(dī)的機器(qì)上(shàng),然後通(tōng)過‘事務 發布訂閱機制(zhì)’,實現了在多(duō)個(gè)sqlserver數(shù)據庫之間(jiān)快速高(gāo)效同步數(shù)據,從而達到了将‘讀寫請(qǐng)求’按實際負載 情況進行(xíng)均衡分布的效果。
下面就簡要介紹一下其實現思路。注:有(yǒu)關數(shù)據同步的工具已在sqlserver中自帶了,可(kě)以參考這篇文章。
将相應的數(shù)據由Master(主)數(shù)據庫中‘發布’出來(lái),然後使用推送的方式(注:事務發布可(kě)以指定是‘通(tōng)過主 數(shù)據庫推送’ 還(hái)是‘訂閱服務器(qì)去獲取’)發送到訂閱它的數(shù)據庫中,就實現了數(shù)據同步功能。
下面就介紹一下如何通(tōng)過改變既有(yǒu)代碼來(lái)實現在‘幾個(gè)從數(shù)據庫(類似快照)’間(jiān)進行(xíng)讀取數(shù)據的負載均衡。
原有(yǒu)的代碼中因為(wèi)使用了分層機制(zhì),所以我們隻要在‘數(shù)據訪問層’動一下心思就可(kě)以了。在這裏我的一個(gè)設 計(jì)思路就是不改變已有(yǒu)的數(shù)據庫訪問接口(包括參數(shù)等)的前提下,實現底層自動将現有(yǒu)的數(shù)據訪問操作(zuò)進行(xíng)負載 均衡。這樣做(zuò)的好處不用多(duō)說了,同時(shí)也讓這個(gè)負載均衡功能與數(shù)據訪問層相分離,不要耦合的太緊密,同時(shí)如果不曉得(de)底層 的實現原理(lǐ)也可(kě)以隻通(tōng)過一個(gè)開(kāi)關(後面會(huì)介紹),就可(kě)以讓自己的sql語句自動實現動态負載均衡。
說到這裏,我來(lái)對照代碼進一步闡述:
首先就是(Discuz.Data\DbHelper.cs)代碼,主要變動如下(新增方法部分):
代碼
/// <summary>
/// 獲取使用的數(shù)據庫(或快照)鏈接串
/// </summary>
/// <param name="commandText">存儲過程名或都SQL命令文本</param>
/// <returns></returns>
public static string GetRealConnectionString(string commandText)
{
if (DbSnapConfigs.GetConfig() != null && DbSnapConfigs.GetConfig().AppDbSnap)
{
commandText = commandText.Trim().ToLower();
if (commandText.StartsWith("select") || ((commandText.StartsWith(BaseConfigs.GetTablePrefix) && UserSnapDatabase(commandText))))
{
DbSnapInfo dbSnapInfo = GetLoadBalanceScheduling.GetConnectDbSnap();
if (DbSnapConfigs.GetConfig().RecordeLog && snapLogList.Capacity > snapLogList.Count)
snapLogList.Add(string.Format("{{'SouceID' : {0}, 'DbconnectString' : '{1}', 'CommandText' : '{2}', 'PostDateTime' : '{3}'}},",
dbSnapInfo.SouceID,
dbSnapInfo.DbconnectString,
commandText.Replace("'",""),
Discuz.Common.Utils.GetDateTime()));
return dbSnapInfo.DbconnectString;
}
}
return ConnectionString;
}
上(shàng)面的方法将會(huì)對傳入的sql語句進行(xíng)分析,找出其中是CUD操作(zuò)還(hái)是SELECT操作(zuò),來(lái)區(qū)别是讀還(hái)是寫操作(zuò)。而snapLogList列表則是之前所配置的‘事務發布訂閱’模式下的相關‘從數(shù)據庫’(Slave Database)鏈接串的列表,例如(dbsnap.config文件的DbSnapInfoList節點):
代碼
<?xml version="1.0"?>
<DbSnapAppConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AppDbSnap>true</AppDbSnap>
<WriteWaitTime>1</WriteWaitTime>
<LoadBalanceScheduling>RoundRobinScheduling</LoadBalanceScheduling> --WeightedRoundRobinScheduling
<RecordeLog>false</RecordeLog>
<DbSnapInfoList>
<DbSnapInfo>
<SouceID>1</SouceID>
<Enable>true</Enable>
<DbconnectString>Data Source=DAIZHJ\DNT_DAIZHJ;User ID=sa;Password=123123;Initial Catalog=dnt_snap;Pooling=true</DbconnectString>
<Weight>4</Weight>
</DbSnapInfo>
<DbSnapInfo>
<SouceID>2</SouceID>
<Enable>true</Enable>
<DbconnectString>Data Source=DAIZHJ-PC\2222;User ID=sa;Password=123;Initial Catalog=tabletest;Pooling=true</DbconnectString>
<Weight>3</Weight>
</DbSnapInfo>
</DbSnapInfoList>
</DbSnapAppConfig>
有(yǒu)關相應配置節點和(hé)負載均衡算(suàn)法會(huì)在後面提到,這裏為(wèi)了保持文章內(nèi)容的連續性暫且跳(tiào)過,下面接着浏覽一下上(shàng)面調用的‘UserSnapDatabase’方法:
代碼
/// <summary>
/// 是否使用快照數(shù)據庫
/// </summary>
/// <param name="commandText">查詢</param>
/// <returns></returns>
private static bool UserSnapDatabase(string commandText)
{
// 如果上(shàng)次刷新cookie間(jiān)隔小(xiǎo)于5分鍾, 則不刷新數(shù)據庫最後活動時(shí)間(jiān)
if (commandText.StartsWith(BaseConfigs.GetTablePrefix + "create"))
{
Utils.WriteCookie("JumpAfterWrite", Environment.TickCount.ToString());
return false;
}
else if (!String.IsNullOrEmpty(Utils.GetCookie("JumpAfterWrite")) && (Environment.TickCount - TypeConverter.StrToInt(Utils.GetCookie("JumpAfterWrite"), Environment.TickCount)) < DbSnapConfigs.GetConfig().WriteWaitTime * 1000)
return false;
else if (!commandText.StartsWith(BaseConfigs.GetTablePrefix + "get"))
return false;
return true;
}
該方法的作(zuò)用很(hěn)簡單,就是當數(shù)據庫有(yǒu)CUD操作(zuò)時(shí),通(tōng)過寫cookie的方式向客戶端寫一個(gè)鍵值‘JumpAfterWrite’,這個(gè)鍵值很(hěn)重要,就是提供一個(gè)标簽(flag)來(lái)指示:‘當前用戶執行(xíng)cud操作(zuò)時(shí),頁面跳(tiào)轉到其它頁面而主數(shù)據庫還(hái)沒來(lái)得(de)及将數(shù)據推送到從數(shù)據庫’這一情況而造成的‘數(shù)據不同步’問題。
舉了例子,當在一個(gè)版塊中‘發表主題’後系統自動跳(tiào)轉到‘顯示該主題頁面’時(shí),如果主數(shù)據庫中插入了一個(gè)新主題而從數(shù)據庫沒有(yǒu)被及時(shí)更新這一主題信息時(shí),就會(huì)報‘主題不存在’這個(gè)錯誤。所以這裏加了一個(gè)設置,就是下面這一行(xíng):
(Environment.TickCount - TypeConverter.StrToInt(Utils.GetCookie("JumpAfterWrite"), Environment.TickCount)) < DbSnapConfigs.GetConfig().WriteWaitTime * 1000)
它所做(zuò)的就是确保用戶cud操作(zuò)之後,在規定的時(shí)間(jiān)內(nèi)還(hái)是訪問主數(shù)據庫,當時(shí)間(jiān)超過時(shí),才将當前用戶的訪問請(qǐng)求(select)均衡到其它從數(shù)據庫中。
當然,在GetRealConnectionString()方法中,還(hái)有(yǒu)一行(xíng)代碼很(hěn)重要,就是下面這一行(xíng):
DbSnapInfo dbSnapInfo = GetLoadBalanceScheduling.GetConnectDbSnap();
它的作(zuò)用就是加載配置文件信息,其中最主要的就是相應的‘負載均衡算(suàn)法實例’來(lái)獲取相應的從數(shù)據庫鏈接串,下面先看一
下‘靜态屬性’GetLoadBalanceScheduling的相關信息:
代碼
/// <summary>
/// 負載均衡調度接口
/// </summary>
private static ILoadBalanceScheduling m_loadBalanceSche;
/// <summary>
/// 初始化負載均衡調度接口實例
/// </summary>
private static ILoadBalanceScheduling GetLoadBalanceScheduling
{
get
{
if (m_loadBalanceSche == null)
{
try
{
m_loadBalanceSche = (ILoadBalanceScheduling)Activator.CreateInstance(Type.GetType(string.Format("Discuz.EntLib.{0}, Discuz.EntLib", DbSnapConfigs.GetConfig().LoadBalanceScheduling), false, true));
}
catch
{
throw new Exception("請(qǐng)檢查config/dbsnap.config中配置是否正确");
}
}
return m_loadBalanceSche;
}
}
它主要是通(tōng)過反射的方法将Discuz.EntLib.dll文件中的相應負載均衡算(suàn)法實例進行(xíng)綁定,然後以m_loadBalanceSche這個(gè)靜态變量進行(xíng)保存,而m_loadBalanceSche本身就是ILoadBalanceScheduling接口變量,該接口即是相應負載均衡算(suàn)法的實現接口。同樣因為(wèi)文章內(nèi)容的連續性,這裏先不深挖相應的實現算(suàn)法,我會(huì)在後面進行(xíng)介紹。下面再來(lái)看一下GetRealConnectionString()中還(hái)有(yǒu)一段代碼,如下:
代碼
if (DbSnapConfigs.GetConfig().RecordeLog && snapLogList.Capacity > snapLogList.Count)
snapLogList.Add(string.Format("{{'SouceID' : {0}, 'DbconnectString' : '{1}', 'CommandText' : '{2}', 'PostDateTime' : '{3}'}},",
dbSnapInfo.SouceID,
dbSnapInfo.DbconnectString,
commandText.Replace("'",""),
Discuz.Common.Utils.GetDateTime()));
return dbSnapInfo.DbconnectString;
上(shàng)面代碼将當前的負載均衡得(de)到的鏈接串保存到一個(gè)snapLogList列表中,該列表聲明(míng)如下:
List<string> snapLogList = new List<string>(400)
為(wèi)什麽要提供這個(gè)列表并進行(xíng)記錄?主要是為(wèi)了考查負載均衡算(suàn)法的工作(zuò)情況,因為(wèi)在數(shù)據訪問層獲取相應鏈接串信息并進行(xíng)記錄很(hěn)不方便,所以我用這個(gè)變量記錄大(dà)約400條‘負載均衡’數(shù)據鏈接串,以便在相應的Discuz.EntLib.ToolKit工具包中進行(xíng)觀察,監視(shì)其‘工作(zuò)情況’。這裏我們隻要知道(dào)通(tōng)過GetRealConnectionString()方法就實現了對sql語句或存儲過程進行(xíng)分析并進行(xíng)負載均衡的效果了(注:該操作(zuò)可(kě)能會(huì)耗時(shí),所以在DbSnapConfigs中提供了一個(gè)開(kāi)關‘RecordeLog’來(lái)進行(xíng)控制(zhì),後面會(huì)介紹)。
下面再來(lái)簡單介紹一下,如何改造DbHelper.cs中原有(yǒu)方法,使其支持負載均衡功能。這裏強調一點,就是:
GetRealConnectionString()方法隻是造了一個(gè)房(fáng)子,裏面的家(jiā)具還(hái)是要自己搬。
而家(jiā)具就是那(nà)些(xiē)老的方法,比如:
代碼
public static object ExecuteScalar(DbConnection connection, CommandType commandType, string commandText, params DbParameter[] commandParameters)
{
if (connection == null) throw new ArgumentNullException("connection");
//connection.Close();
connection.ConnectionString = GetRealConnectionString(commandText);//負載均衡改造完成的方法
connection.Open();
// 創建DbCommand命令,并進行(xíng)預處理(lǐ)
DbCommand cmd = Factory.CreateCommand();
bool mustCloseConnection = false;
PrepareCommand(cmd, connection, (DbTransaction)null, commandType, commandText, commandParameters, out mustCloseConnection);
// 執行(xíng)DbCommand命令,并返回結果.
object retval = cmd.ExecuteScalar();
// 清除參數(shù),以便再次使用.
cmd.Parameters.Clear();
if (mustCloseConnection)
connection.Close();
return retval;
}
上(shàng)面的 ‘connection.ConnectionString =’之前綁定的ConnectionString這個(gè)靜态屬性,而這個(gè)屬性鏈接的就是‘主數(shù)據庫’,
這裏我們隻要将GetRealConnectionString(commandText)賦值給它就可(kě)以了,還(hái)是那(nà)句話(huà),在GetRealConnectionString()就實現了
數(shù)據庫鏈接串的負載均衡,呵呵。類似上(shàng)面的變動在DbHelper.cs還(hái)有(yǒu)幾處,好在變化不太大(dà),當然更不需要改變原有(yǒu)的數(shù)據訪問層
(比如IDataProvider.cs文件)了。
其實本文中介紹的數(shù)據庫層負載均衡實現方法在MYSQL中早有(yǒu)相應的插件實現了,參見這篇文章。
該文章中的LUA腳本實現方式與本文類似,如下:
代碼
--發送所有(yǒu)的非事務性SELECT到一個(gè)從數(shù)據庫
if is_in_transaction == 0 and packet:byte() == proxy.COM_QUERY and packet:sub(2, 7) == "SELECT" then
local max_conns = -1
local max_conns_ndx = 0
for i = 1, #proxy.servers do
local s = proxy.servers[i]
-- 選擇一個(gè)擁有(yǒu)空(kōng)閑連接的從數(shù)據庫
if s.type == proxy.BACKEND_TYPE_RO and s.idling_connections > 0 then
if max_conns == -1 or s.connected_clients < max_conns then
max_conns = s.connected_clients
max_conns_ndx = i
end
end
end
.....
接着,我再介紹一下相應的配置文件和(hé)負載均衡算(suàn)法的實現情況:)
配置文件(比如:Discuz.EntLib.ToolKit\config\dbsnap.config):
代碼
<?xml version="1.0"?>
<DbSnapAppConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AppDbSnap>true</AppDbSnap>
<WriteWaitTime>1</WriteWaitTime>
<LoadBalanceScheduling>RoundRobinScheduling</LoadBalanceScheduling> --WeightedRoundRobinScheduling
<RecordeLog>false</RecordeLog>
<DbSnapInfoList>
<DbSnapInfo>
<SouceID>1</SouceID>
<Enable>true</Enable>
<DbconnectString>Data Source=DAIZHJ\DNT_DAIZHJ;User ID=sa;Password=123123;Initial Catalog=dnt_snap;Pooling=true</DbconnectString>
<Weight>4</Weight>
</DbSnapInfo>
<DbSnapInfo>
<SouceID>2</SouceID>
<Enable>true</Enable>
<DbconnectString>Data Source=DAIZHJ-PC\2222;User ID=sa;Password=123;Initial Catalog=tabletest;Pooling=true</DbconnectString>
<Weight>3</Weight>
</DbSnapInfo>
<DbSnapInfo>
<SouceID>3</SouceID>
<Enable>true</Enable>
<DbconnectString>Data Source=DAIZHJ-PC\333333;User ID=sa;Password=123;Initial Catalog=tabletest;Pooling=true</DbconnectString>
<Weight>2</Weight>
</DbSnapInfo>
<DbSnapInfo>
<SouceID>4</SouceID>
<Enable>true</Enable>
<DbconnectString>Data Source=DAIZHJ-PC\44444444;User ID=sa;Password=123;Initial Catalog=tabletest;Pooling=true</DbconnectString>
<Weight>2</Weight>
</DbSnapInfo>
</DbSnapInfoList>
</DbSnapAppConfig>
上(shàng)面的DbSnapInfoList就是相應的slave數(shù)據庫鏈接列表,其中它的相應節點信息說明(míng)如下(Discuz.Config\DbSnapInfo.cs):
代碼
[Serializable]
public class DbSnapInfo
{
/// <summary>
/// 源ID,用于唯一标識快照在數(shù)據庫負載均衡中的信息
/// </summary>
private int _souceID;
/// <summary>
/// 源ID,用于唯一标識快照在數(shù)據庫負載均衡中的信息
/// </summary>
public int SouceID
{
get { return _souceID; }
set { _souceID = value; }
}
/// <summary>
/// 快照是否有(yǒu)效
/// </summary>
private bool _enable;
/// <summary>
/// 是否有(yǒu)效
/// </summary>
public bool Enable
{
get { return _enable; }
set { _enable = value; }
}
/// <summary>
/// 快照鏈接
/// </summary>
private string _dbConnectString;
/// <summary>
/// 快照鏈接
/// </summary>
public string DbconnectString
{
get { return _dbConnectString; }
set { _dbConnectString = value; }
}
/// <summary>
/// 權重信息,該值越高(gāo)則意味着被輪循到的次數(shù)越多(duō)
/// </summary>
private int _weight;
/// <summary>
/// 權重信息,該值越高(gāo)則意味着被輪循到的次數(shù)越多(duō)
/// </summary>
public int Weight
{
get { return _weight; }
set { _weight = value; }
}
}
當然DbSnapAppConfig作(zuò)為(wèi)DbSnapInfo列表的容器(qì),其結構如下:
代碼
[Serializable]
public class DbSnapAppConfig : Discuz.Config.IConfigInfo
{
private bool _appDbSnap;
/// <summary>
/// 是否啓用快照,如不使用,則即使DbSnapInfoList已設置有(yǒu)效快照信息也不會(huì)使用。
/// </summary>
public bool AppDbSnap
{
get { return _appDbSnap; }
set { _appDbSnap = value; }
}
private int _writeWaitTime = 6;
/// <summary>
/// 寫操作(zuò)等待時(shí)間(jiān)(單位:秒(miǎo)), 說明(míng):在執行(xíng)完寫操作(zuò)之後,在該時(shí)間(jiān)內(nèi)的sql請(qǐng)求依舊(jiù)會(huì)被發往master數(shù)據庫
/// </summary>
public int WriteWaitTime
{
get { return _writeWaitTime; }
set { _writeWaitTime = value; }
}
private string _loadBalanceScheduling = "WeightedRoundRobinScheduling";
/// <summary>
/// 負載均衡調度算(suàn)法,默認為(wèi)權重輪詢調度算(suàn)法 http://www.pcjx.com/Cisco/zhong/209068.html
/// </summary>
public string LoadBalanceScheduling
{
get { return _loadBalanceScheduling; }
set { _loadBalanceScheduling = value; }
}
private bool _recordeLog = false;
/// <summary>
/// 是否記錄日志(zhì)
/// </summary>
public bool RecordeLog
{
get { return _recordeLog; }
set { _recordeLog = value; }
}
private List<DbSnapInfo> _dbSnapInfoList;
/// <summary>
/// 快照輪循列表
/// </summary>
public List<DbSnapInfo> DbSnapInfoList
{
get { return _dbSnapInfoList; }
set { _dbSnapInfoList = value; }
}
}
通(tōng)過這兩個(gè)配置文件,就可(kě)以實現對數(shù)據訪問層負載均衡的靈活配置了,不過上(shàng)面的DbSnapAppConfig還(hái)有(yǒu)一個(gè)非常重要的
屬性沒有(yǒu)介紹清楚,就是‘LoadBalanceScheduling’,其接口聲明(míng)如下:
代碼
/// <summary>
/// 負載均衡調度接口
/// </summary>
public interface ILoadBalanceScheduling
{
/// <summary>
/// 獲取應用當前負載均衡調度算(suàn)法下的快照鏈接信息
/// </summary>
/// <returns></returns>
DbSnapInfo GetConnectDbSnap();
}
它就是負載均衡算(suàn)法的實現接口,為(wèi)了便于說明(míng)在Discuz.EntLib中內(nèi)置的兩個(gè)負載均衡算(suàn)法的實現情況,請(qǐng)先看下圖:
內(nèi)置的兩個(gè)負載均衡算(suàn)法,一個(gè)是RoundRobinScheduling,即輪叫調度(Round Robin Scheduling)算(suàn)法,它的實現比較簡單,就是對從數(shù)據庫鏈接列表的依次遍曆,如下:
代碼
/// <summary>
/// 輪叫調度(Round Robin Scheduling)算(suàn)法
/// </summary>
public class RoundRobinScheduling : ILoadBalanceScheduling
{
private static object lockHelper = new object();
/// <summary>
/// 當前的快照索引和(hé)權重信息
/// </summary>
static int curentSnapIndex = 0;
static RoundRobinScheduling()
{}
public DbSnapInfo GetConnectDbSnap()
{
lock (lockHelper)
{
if (curentSnapIndex >= DbSnapConfigs.GetEnableSnapList().Count)
curentSnapIndex = (curentSnapIndex) % DbSnapConfigs.GetEnableSnapList().Count;
return DbSnapConfigs.GetEnableSnapList()[curentSnapIndex++];
}
}
}
而另一種負載均衡算(suàn)法就相對負載了,不過它也更符合實際的應用場(chǎng)景,它使用了權重的方法來(lái)讓性能優良的機器(qì)分到
更多(duō)的任務來(lái)均衡整個(gè)方案的性能,即權重輪詢調度算(suàn)法,實現代碼如下:
代碼
/// <summary>
/// 權重輪詢調度算(suàn)法
/// http://www.pcjx.com/Cisco/zhong/209068.html
/// http://id-phatman.spaces.live.com/blog/cns!CA763CA8DB2378D1!627.entry
/// </summary>
public class WeightedRoundRobinScheduling : ILoadBalanceScheduling
{
private static object lockHelper = new object();
/// <summary>
/// 快照的權重列表
/// </summary>
static List<int> snapWeightList = new List<int>();
/// <summary>
/// 當前的快照索引和(hé)權重信息
/// </summary>
static int curentSnapIndex, currentWeight;
/// <summary>
/// 快照權重列表中最大(dà)的權重值和(hé)最大(dà)公約數(shù)
/// </summary>
static int maxWeight, gcd;
static WeightedRoundRobinScheduling()
{
curentSnapIndex = -1;
currentWeight = 0;
snapWeightList = GetSnapWeightList();
maxWeight = GetMaxWeight(snapWeightList);
gcd = GCD(snapWeightList);
}
/// <summary>
/// 獲取應用當前負載均衡調度算(suàn)法下的快照鏈接信息
/// </summary>
/// <returns></returns>
public DbSnapInfo GetConnectDbSnap()
{
lock (lockHelper)
{
DbSnapInfo current = RoundRobinScheduling();
if (current != null)
return current;
else
return DbSnapConfigs.GetEnableSnapList()[0];
}
}
/// <summary>
/// 獲取快照權重的列表
/// </summary>
/// <returns></returns>
static List<int> GetSnapWeightList()
{
List<int> snapWeightList = new List<int>();
foreach (DbSnapInfo dbSnapInfo in DbSnapConfigs.GetEnableSnapList())
{
snapWeightList.Add(dbSnapInfo.Weight);
}
return snapWeightList;
}
/// <summary>
/// 權重輪詢調度算(suàn)法
/// </summary>
static DbSnapInfo RoundRobinScheduling()
{
while (true)
{
curentSnapIndex = (curentSnapIndex + 1) % DbSnapConfigs.GetEnableSnapList().Count;
if (curentSnapIndex == 0)
{
currentWeight = currentWeight - gcd;
if (currentWeight <= 0)
{
currentWeight = maxWeight;
if (currentWeight == 0)
return null;
}
}
if (DbSnapConfigs.GetEnableSnapList()[curentSnapIndex].Weight >= currentWeight)
return DbSnapConfigs.GetEnableSnapList()[curentSnapIndex];
}
}
/// <summary>
/// 獲取最大(dà)權重
/// </summary>
/// <param name="snapList"></param>
/// <returns></returns>
static int GetMaxWeight(List<int> snapWeightList)
{
int maxWeight = 0;
foreach (int snapWeight in snapWeightList)
{
if (maxWeight < snapWeight)
maxWeight = snapWeight;
}
return maxWeight;
}
/// <summary>
/// 獲取權重的最大(dà)公約數(shù)
/// </summary>
/// <returns></returns>
static int GCD(List<int> snapWeightList)
{
// 排序,得(de)到數(shù)字中最小(xiǎo)的一個(gè)
snapWeightList.Sort(new WeightCompare());
int minNum = snapWeightList[0];
// 最大(dà)公約數(shù)肯定大(dà)于等于1,且小(xiǎo)于等于最小(xiǎo)的那(nà)個(gè)數(shù)。
// 依次整除,如果餘數(shù)全部為(wèi)0說明(míng)是一個(gè)約數(shù),直到打出最大(dà)的那(nà)個(gè)約數(shù)
int gcd = 1;
for (int i = 1; i <= minNum; i++)
{
bool isFound = true;
foreach (int snapWeight in snapWeightList)
{
if (snapWeight % i != 0)
{
isFound = false;
break;
}
}
if (isFound)
gcd = i;
}
return gcd;
}
/// <summary>
/// 實現IComparer接口,用于對數(shù)字列表進行(xíng)排序
/// </summary>
private class WeightCompare : System.Collections.Generic.IComparer<int>
{
public int Compare(int weightA, int weightB)
{
return weightA - weightB;
}
}
}
到這裏,主要的功能代碼就介紹的差不多(duō)了,我們可(kě)以通(tōng)過對dbsnap.config的相應節點配置,來(lái)靈活定制(zhì)我們的負載均衡方案。同時(shí),對一般開(kāi)發者而言,這種架構是透明(míng)的,大(dà)家(jiā)可(kě)以完全在不了解它的情況下開(kāi)發自己的數(shù)據訪問功能,并通(tōng)過相應開(kāi)關來(lái)讓自己的代碼支持均衡負載。
當然這個(gè)方案還(hái)有(yǒu)一些(xiē)沒考慮到的問題比如:
1.對‘主從數(shù)據庫的健康度檢查’,即如果主或從數(shù)據庫出現故障的時(shí)候該如何處理(lǐ),當然在sqlserver中還(hái)提供了鏡像功能來(lái)解決類似問題,所以它也可(kě)做(zuò)為(wèi)一個(gè)備選方案。
2.當主數(shù)據庫被發布出去後,主數(shù)據庫的表和(hé)存儲過程就會(huì)被‘鎖定’,其不允許被再次修改了,所以還(hái)要繼續研究如何解決這一問題。
重慶中技互聯網信息咨詢有限公司
重慶網站(zhàn)建設事業部官方網:www.zjcoo.com
電(diàn)子商務建站(zhàn)事業部咨詢電(diàn)話(huà):023-67742189
門(mén)戶網站(zhàn)品牌加盟推廣電(diàn)話(huà):023-67742189
7*24小(xiǎo)時(shí)服務電(diàn)話(huà):023-67742189
媒體(tǐ)合作(zuò)電(diàn)話(huà):13883323406
投資合作(zuò)電(diàn)話(huà):13896068183
QQ及郵件地址:446515345@qq.com
如沒特殊注明(míng),文章均為(wèi)中技(jì)互聯原創,轉載請(qǐng)注明(míng)來(lái)自www.zjcoo.com