Discuz!NT数据库读写分离方案-2
原文 http://www.cnblogs.com/daizhj/archive/2010/06/21/dbsnap_master_slave_database.html
当然DbSnapAppConfig作为DbSnapInfo列表的容器,其结构如下:
public class DbSnapAppConfig : Discuz.Config.IConfigInfo
{
private bool _appDbSnap;
/// <summary>
/// 是否启用快照,如不使用,则即使DbSnapInfoList已设置有效快照信息也不会使用。
/// </summary>
public bool AppDbSnap
{
get { return _appDbSnap; }
set { _appDbSnap = value; }
}
private int _writeWaitTime = 6 ;
/// <summary>
/// 写操作等待时间(单位:秒), 说明:在执行完写操作之后,在该时间内的sql请求依旧会被发往master数据库
/// </summary>
public int WriteWaitTime
{
get { return _writeWaitTime; }
set { _writeWaitTime = value; }
}
private string _loadBalanceScheduling = " WeightedRoundRobinScheduling " ;
/// <summary>
/// 负载均衡调度算法,默认为权重轮询调度算法 http://www.pcjx.com/Cisco/zhong/209068.html
/// </summary>
public string LoadBalanceScheduling
{
get { return _loadBalanceScheduling; }
set { _loadBalanceScheduling = value; }
}
private bool _recordeLog = false ;
/// <summary>
/// 是否记录日志
/// </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; }
}
}
通过这两个配置文件,就可以实现对数据访问层负载均衡的灵活配置了,不过上面的DbSnapAppConfig还有一个非常重要的
属性没有介绍清楚,就是‘LoadBalanceScheduling’,其接口声明如下:
/// 负载均衡调度接口
/// </summary>
public interface ILoadBalanceScheduling
{
/// <summary>
/// 获取应用当前负载均衡调度算法下的快照链接信息
/// </summary>
/// <returns></returns>
DbSnapInfo GetConnectDbSnap();
}
它就是负载均衡算法的实现接口,为了便于说明在Discuz.EntLib中内置的两个负载均衡算法的实现情况,请先看下图:
内置的两个负载均衡算法,一个是RoundRobinScheduling,即轮叫调度(Round Robin Scheduling)算法,它的实现比较简单,就是对从数据库链接列表的依次遍历,如下:
/// 轮叫调度(Round Robin Scheduling)算法
/// </summary>
public class RoundRobinScheduling : ILoadBalanceScheduling
{
private static object lockHelper = new object ();
/// <summary>
/// 当前的快照索引和权重信息
/// </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 ++ ];
}
}
}
而另一种负载均衡算法就相对负载了,不过它也更符合实际的应用场景,它使用了权重的方法来让性能优良的机器分到
更多的任务来均衡整个方案的性能,即权重轮询调度算法,实现代码如下:
/// 权重轮询调度算法
/// 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>
/// 当前的快照索引和权重信息
/// </summary>
static int curentSnapIndex, currentWeight;
/// <summary>
/// 快照权重列表中最大的权重值和最大公约数
/// </summary>
static int maxWeight, gcd;
static WeightedRoundRobinScheduling()
{
curentSnapIndex = - 1 ;
currentWeight = 0 ;
snapWeightList = GetSnapWeightList();
maxWeight = GetMaxWeight(snapWeightList);
gcd = GCD(snapWeightList);
}
/// <summary>
/// 获取应用当前负载均衡调度算法下的快照链接信息
/// </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>
/// 权重轮询调度算法
/// </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>
/// 获取最大权重
/// </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>
/// 获取权重的最大公约数
/// </summary>
/// <returns></returns>
static int GCD(List < int > snapWeightList)
{
// 排序,得到数字中最小的一个
snapWeightList.Sort( new WeightCompare());
int minNum = snapWeightList[ 0 ];
// 最大公约数肯定大于等于1,且小于等于最小的那个数。
// 依次整除,如果余数全部为0说明是一个约数,直到打出最大的那个约数
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接口,用于对数字列表进行排序
/// </summary>
private class WeightCompare : System.Collections.Generic.IComparer < int >
{
public int Compare( int weightA, int weightB)
{
return weightA - weightB;
}
}
}
到这里,主要的功能代码就介绍的差不多了,我们可以通过对dbsnap.config的相应节点配置,来灵活定制我们的负载均衡方案。同时,对一般开发者 而言,这种架构是透明的,大家可以完全在不了解它的情况下开发自己的数据访问功能,并通过相应开关来让自己的代码支持均衡负载。
当然这个方案还有一些没考虑到的问题比如:
1.对‘主从数据库的健康度检查’,即如果主或从数据库出现故障的时候该如何处理,当然在sqlserver中还提供了镜像功能
来解决类似问题,所以它也可做为一个备选方案。
2.当主数据库被发布出去后,主数据库的表和存储过程就会被‘锁定’,其不允许被再次修改了,所以还要继续研究如何解决这一问题。