{A}背景
在夏天(夏季是我们今年的最低点),我们从SQL Server 2000迁移到2005年。因为我们期望在今年秋天的流量大量增加,我们正在使我们的系统容错。这包括我们的公共Web应用程序的页面浏览量每天数以百万计的交通冗余的数据库服务器。这是定期更新的数据,我们使用事务复制,旧的"归档的数据,我们将介绍日志传送今年。
因为日志传送需要的数据库进行脱机恢复过程(SQL 2005标准版),我需要一种方法,以确定何时恢复过程中已定,所以我可以直接到另一台服务器的流量。所以,我扩大和写一个负载平衡器的想法。因为我们有越来越多的Web场,我不希望Web服务器状态坪水浸的数据库,所以我决定将作为服务运行负载平衡器和平安就在指定的时间间隔卫生统计的数据库。 Web服务器,然后ping服务数据库的状态。然后基础上的可用性和电流负载(CPU),每个服务器,Web服务器可以使用任何逻辑是必要的,以确定哪些可用的数据库使用。
这篇文章的目的是描述过程,从而弥补了负载平衡器:数据结构序列化方法"特权"的PerformanceCounter权限ASP.NET集成在Web应用程序的使用结果
本文假定读者熟悉以下下面这样的主题超出了本文的范围是:创建一个Windows服务System.Net.Sockets的ASP.Net的HttpModules的System.Threading异步方法
我已经试过这不是正在讨论,同时仍然给读者的逻辑感的文章删除代码。我拉重复的行,并列出catch块正在讨论的问题并不重要。继续我的写作和批判以及内容和享受什么对我来说是一个有趣的练习。数据结构
为了减少服务器之间的通信的"繁琐",我需要一个自定义的数据结构,以添加额外的数据,如果需要的话,我还可以自定义的组件之间传递。{S0}
没有真正的讨论是必要的,其他要注意,为简单起见,每个项目都有其自己的副本这个类。我们在生产中的应用,这个类将是一个更大的类库的一部分。序列化方法
我的经验。NET序列化方法已输出是臃肿。他们简化流程,使数据的自我描述的应用相对独立,但我不想洪水与如此多的数据,它影响应用程序的性能网络。所以我选择了在网络上使用System.IO.BinaryWriter和System.IO.BinaryReader序列数据。这使我减少的一个实例的状态数据大小小于网络平。public class DbState
{
//... member variables ...
public DbState(byte[] serializedData)
{
Deserialize(serializedData, 0);
}
public DbState(byte[] serializedData, long start, out long end)
{
end = Deserialize(serializedData, start);
}
// ... public properties ...
public byte[] Serialize()
{
byte[] data = new byte[256];
MemoryStream stream = new MemoryStream(data);
BinaryWriter writer = new BinaryWriter(stream);
long pos = 0;
try
{
writer.Write((byte)_status);
writer.Write(_cpu);
writer.Write(_server);
pos = writer.BaseStream.Position;
}
finally
{
writer.Close();
stream.Dispose();
}
byte[] output = new byte[pos];
for (int i = 0; i < pos; i++)
output[i] = data[i];
return output;
}
private long Deserialize(byte[] data, long offset)
{
MemoryStream stream = new MemoryStream(data);
BinaryReader reader = new BinaryReader(stream);
if (offset != 0)
reader.BaseStream.Position = offset;
try
{
_status = (DbStatus)reader.ReadByte();
_cpu = reader.ReadDouble();
_server = reader.ReadString();
offset = reader.BaseStream.Position;
}
finally
{
reader.Close();
stream.Dispose();
}
return offset;
}
}
有两个重载的构造函数允许两种方法反序列化。首先是一个简单的反序列化从流中的单个对象。第二,允许多个对象序列化到一个单一的流。流的开始包含一个单字节表示的对象序列化到流(我选择字节,因为服务是不太可能被监测的255个以上的数据库服务器)。"特权"的PerformanceCounter权限
服务器的性能指标最常见的处理器时间。所以,我需要能够远程查询处理器 - 处理器时间百分比在每个数据库服务器的性能计数器。但是,当我环顾四周,以确定所需的权限,我已经很难找到比其他任何方法:使用特权帐户,或使用模拟
使用权限的帐户简直是一个坏主意,并使用模拟Windows服务的实际。经过一番挖掘,我发现了一个。
总之,我创建了一个域组,每个数据库服务器上,可以增加当地的"性能计数器用户组,并添加以下到本地组的权限:HKLM \ SYSTEM \ CurrentControlSet \服务\ ControlSecurePipeServers \ WinReg项%SYSTEMROOT%\ SYSTEM32 \ Perfh.dat%SYSTEMROOT%\ SYSTEM32 \ Perfc.dat通讯
由于负载平衡器是作为Web应用程序的应用程序边界之外的服务运行,它是重要的,以确定如何thenbsp应用与服务进行通信。序列化的选择是基于我的计划,通过套接字通信。 Web应用程序发起的连接和数据服务的一个具体要求。该服务,然后返回到Web服务器的适当的反应,通过打开的套接字。{C}
LBRequestCmd枚举允许的服务,以应对不同类型的请求。例如,如果你想能够"平安"的服务,或要求服务回合刷新状态,或者如果你有一个具体的数据,你想。它给你无限的可能性,扩大范围和负载平衡器定制其输出。另外,服务站已被写入响应ping请求或要求所有监控数据库的完整状态。
为了减少对资源的影响和需要管理线程,异步套接字方法(BeginAccept,EndAccept,BeginReceive,EndReceive,BeginSend,EndSend)允许服务处理尽可能多的线索。一个很好的补充,会管理的数量插座,所以我们没有创造太多的连接。ASP.NET集成
负载平衡器的目标是有一个ASP.NET进程将调查数据库的状态,对任何一个特定的时间点的查询,并确定最佳的连接。对于这一点,我需要的应用程序变量中存储的服务器的状态。此外,我不想轮询每个页面的服务器的状态,使投票过程需要独立运行的页面。我创建了一个自定义HttpModule,将在指定的时间间隔轮询的负载平衡器,并存储在应用程序的状态结果。
为了减少阻塞的线程,自定义HttpModule也使用异步套接字连接,发送和接收操作的System.Net.Sockets(BeginConnect,EndConnect,BeginSend,EndSend,BeginReceive,EndReceive)定义的方法。
一旦服务器的状态已获得,并存储在应用程序的状态,它是Web应用程序,以确定它将如何利用这些数据来确定的轮询间隔之间的最佳连接。例如,通过比较每个服务器%的CPU标记为可用,应用程序将最有可能的选择比例最低的数据库。如果应用程序无法检索或从负载平衡器读取数据,应用程序变量将被销毁。缺点是,这需要该应用程序查找自身可用的连接。不过,我认为重要的是要防止虚假报告的应用程序。破坏应用程序变量的另一种方法是改变状态未知和0%的CPU。无论哪种方式,应用程序应写入恢复与负载平衡器连接失败。/// <SUMMARY>
/// Async callback for BeginRecevie(). Shuts down and closes connection,
/// then reads response from load balancer. The response is then stored
/// in application state for use by the application.
/// </SUMMARY>
/// <param name=""result""></param>
protected void OnReceive(IAsyncResult result)
{
AsyncState data = (AsyncState)result.AsyncState;
try
{
try
{
data.socket.EndReceive(result);
}
finally
{
/*make sure we shutdown AND disconnect (true) so we can
reuse the socket on each subsequent attempt.
This will prevent AddressAlreadyInUse socket exception. */
data.socket.Shutdown(SocketShutdown.Both);
data.socket.Close();
}
byte count = 0;
long pos = 0;
// .... read count from stream ....
if (count != 0)
{
//read DbState objects from response stream
DbState[] servers = new DbState[count];
for (byte i = 0; i < count; i++)
{
DbState state = new DbState(data.buffer, pos, out pos);
servers[i] = state;
}
//store DbState objects in application state
if (_app.Application["Databases"] == null)
_app.Application.Add("Databases", servers);
else
_app.Application["Databases"] = servers;
}
else
{
//an invalid command was sent and the load balancer did not
//respond.
_log.WriteEntry("There was a problem with the request and"
+ " the load balancer did not recognize the command"
+ " (OnReceive()).", EventLogEntryType.Error);
_app.Application.Remove("Databases");
}
}
catch
{
// .... handle exception code .....
}
//wait the selected interval
Thread.Sleep(_interval);
//try to reconnect to load balancer
data.socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
data.socket.BeginConnect(_endPoint, _connect, data);
}
为了防止需要重新加载应用程序域使用负载均衡,Web应用程序将继续调查,只要负载平衡器初始化成功。这当负载平衡器带回在线或两者之间的连接恢复,负载均衡,将立即恢复。
在Web应用程序的使用结果/// <SUMMARY>
/// BeginConnect callback method. Uses new connection to request status
/// data from load balancer then returns process to a wait state until
/// load balancer replies.</SUMMARY>
/// <param name=""result""></param>
protected void OnConnect(IAsyncResult result)
{
/*
TODO: If a connection doesn't work, then we should try another
connection. We should only loop through all configured load
balancers before putting thread to sleep.
*/
AsyncState data = (AsyncState) result.AsyncState;
try
{
data.socket.EndConnect(result);
try
{
//request db status from load balancer
byte[] requestStatus = new byte[1];
requestStatus[0] = (byte)LBRequestCmd.DBStatus;
data.socket.Send(requestStatus);
//reinitialize response buffer
data.buffer = new byte[4096];
//wait for response from load balancer
data.socket.BeginReceive(data.buffer, 0, data.buffer.Length,
SocketFlags.None, _receive, data);
}
catch
{
// ... handle exceptions code ....
}
}
catch (SocketException ex)
{
_log.WriteEntry("A socket exception occurred while trying to"
+ " connect: OnConnect() - (" + ex.SocketErrorCode + ") "
+ ex.Message, EventLogEntryType.Error);
if(_app.Application["Databases"] != null)
_app.Application.Remove("Databases");
//close socket
data.socket.Close();
//try reconnecting
Thread.Sleep(_interval);
//create a new socket and try again after interval
data.socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
data.socket.BeginConnect(_endPoint, _connect, data);
}
}
下面是一个简单的例子,你可能会如何使用在应用程序的负载平衡器的结果。
结论public SqlConnection GetConnection()
{
string connection = string.empty;
DbState[] dbs = new DbState[0];
DbState selected = null;
if(Application["Databases"] != null)
dbs = (DbState[])Application["Databases"];
for(int i = 0; i < dbs.length; i++)
{
if(dbs.Status == DbStatus.Available)
{
if(selected == null)
selected = dbs[i];
else
{
if(selected.CPU > dbs[i].Status)
selected = dbs[i];
}
}
}
if(selected != null)
connection = string.format("Server={0};Database=pubs;Trusted_Connection=Yes;",
selected.Server);
else
connection = "Server=SQL01;Database=pubs;Trusted_Connection=Yes;";
return new SqlConnection(connection);
}
有许多负载平衡器产品可提供平衡网络连接到一个资源的能力。然而,这种方法的好处,除其他外,包括:页面的整个生命周期一致的状态的能力,以确定自定义指标在决策过程中的应用控制成本低运行在任何硬件配置你可以在不同的服务器上安装多个实例容错(当前的HttpModule不支持此尚未)
两个重要的事情要注意。首先,如果你的应用程序维护状态信息在数据库中,你会被要求以确保状态数据是同步的每个数据库在负载平衡器树或储存独立的状态信息,并使用单独的连接状态数据逻辑。第二,如果你使用连接池(ADO.NET默认),将创建一个单独的游泳池为您的应用程序中的每个唯一的连接字符串。确保您是如何,它会影响您的应用程序池设置为内存池的大小和规划时,考虑到这一点。
我很感兴趣地听到从别人的反馈。我不是一个网络工程师,我也有很多的经验,不是说我们的应用程序使用Web服务器的负载平衡器和我与我们的网络工程师工作,以确定如何负载平衡器和更改其配置与负载平衡器会影响我们的应用程序。免责声明不谈,我相信这是一个可行的解决方案为生产环境,并希望本文可以帮助他人实施类似的解决方案。历史
,2006年7月24日:战后初期