转载

C#手动做一个负载均衡服务器

思路

负载均衡服务器最出名的当数 Nginx了。Nginx服务器通过异步的方式把连接转发给内网和N个服务器,用来分解单台应用服务器的压力,了解了原理及场景后,用C#来实现一个。思路如下:

1. 使用一个站点的 Application_BeginRequest 来接收连接,转发连接。

2. 对各类静态资源做单独处理,(可转可不转)

3. 可以转发Get,Post,异步转发。

4. 对指定的请求,转发到同一台服务器,保持使用者的登录状态。

实现

Vs2015建一个Mvc建站。修改Web.config,用于接收所有连接。

<system.webServer>      <modules runAllManagedModulesForAllRequests="true">     </modules>  </system.webServer>

引入 MyCmn.dll (http://code.taobao.org/svn/MyOql/libs4),MyHelper 封装了 HTTP 的基本操作,方便使用。

代码如下:

public class RequestWrap {  public HttpWebRequest Request { get; set; }  private ManualResetEvent Event { get; set; }  private Action<HttpWebResponse> Action { get; set; }  public RequestWrap(HttpWebRequest request)  {   Event = new ManualResetEvent(false);   this.Request = request;  }  public void Run(Action<HttpWebResponse> act)  {   this.Action = act;   Request.BeginGetResponse(new AsyncCallback(GetResponseCallback), this);   this.Event.WaitOne(15000);  }  private static void GetResponseCallback(IAsyncResult asyncResult)  {   RequestWrap wrap = (RequestWrap)asyncResult.AsyncState;   HttpWebResponse response = null;   try   {    response = wrap.Request.EndGetResponse(asyncResult) as HttpWebResponse;   }   catch (WebException ex)   {    response = ex.Response as HttpWebResponse;   }   wrap.Action(response);   wrap.Event.Set();  } } private void Application_BeginRequest(Object source, EventArgs e) {  var lastExtName = "";  {   var lastIndex = Request.Url.LocalPath.LastIndexOf('.');   if (lastIndex > 0)   {    lastExtName = Request.Url.LocalPath.Slice(lastIndex);   }  }  // <modules runAllManagedModulesForAllRequests="true"> 设置之后,静态资源就进来了。  if (lastExtName.IsIn(".js", ".css", ".html", ".htm", ".png", ".jpg", ".gif"))  {   return;  }  HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(GetTransferHost() + Request.RawUrl);  myRequest.Proxy = null;  myRequest.Timeout = 15000;  myRequest.ReadWriteTimeout = 3000;  myRequest.Method = Request.HttpMethod;  Request.Headers.AllKeys.All(k =>  {   if (!WebHeaderCollection.IsRestricted(k))   {    myRequest.Headers.Add(k, Request.Headers[k]);   }   else   {    var val = Request.Headers[k];    if (k.Is("Range"))    {     myRequest.AddRange(val.AsInt());    }    else if (k.Is("If-Modified-Since"))    {     myRequest.IfModifiedSince = val.AsDateTime();    }    else if (k.Is("Accept"))    {     myRequest.Accept = val;    }    else if (k.Is("Content-Type"))    {     myRequest.ContentType = val;    }    else if (k.Is("Expect"))    {     myRequest.Expect = val;    }    else if (k.Is("Date"))    {     myRequest.Date = val.AsDateTime();    }    else if (k.Is("Host"))    {     myRequest.Host = val;    }    else if (k.Is("Referer"))    {     myRequest.Referer = val;    }    else if (k.Is("Transfer-Encoding"))    {     myRequest.TransferEncoding = val;    }    else if (k.Is("User-Agent"))    {     myRequest.UserAgent = val;    }    //else if (k.Is("Connection"))    //{    // o.Connection = val;    //}    else if (k.Is("KeepAlive"))    {     myRequest.KeepAlive = val.AsBool();    }   }   return true;  });  using (var readStream = Request.InputStream)  {   while (true)   {    byte[] readBuffer = new byte[1024];    var readLength = readStream.Read(readBuffer, 0, readBuffer.Length);    if (readLength == 0) break;    myRequest.GetRequestStream().Write(readBuffer, 0, readLength);   }  }  new RequestWrap(myRequest).Run(myResponse =>  {   myResponse.Headers.AllKeys.All(k =>   {    if (k.Is("X-Powered-By"))    {     return true;    }    Response.Headers[k] = myResponse.Headers[k];    return true;   });   using (var readStream = myResponse.GetResponseStream())   {    while (true)    {     byte[] readBuffer = new byte[1024];     var read = readStream.Read(readBuffer, 0, readBuffer.Length);     if (read == 0) break;     Response.OutputStream.Write(readBuffer, 0, read);    }   }   Response.StatusCode = myResponse.StatusCode.AsInt();  });  Response.End(); } public static string GetClientIPAddress() {  if (HttpContext.Current == null)   return string.Empty;  HttpContext context = HttpContext.Current;//System.Web.HttpContext.Current;  string ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];  if (!string.IsNullOrEmpty(ipAddress))  {   string[] addresses = ipAddress.Split(',');   if (addresses.Length != 0)   {    return addresses[0];   }  }  return context.Request.ServerVariables["REMOTE_ADDR"]; //+ " host " + context.Request.UserHostAddress; } private string GetTransferHost() {  string[] hosts = new string[] { "http://localhost" };  var index = GetClientIPAddress().Last() % hosts.Length ;  return hosts[index]; } 

解释

其中, RequestWrap 是对异步请求包装的请求类。封装了一个 Run 方法进行异步调用。过滤了应用服务器的回发头 X-Powered-By

关于 WebHeaderCollection.IsRestricted ,是由于一个错误引出的: 异常处理:必须使用适当的属性或方法修改此标头,文章地址: http://blog.useasp.net/archive/2013/09/03/the-methods-to-dispose-http-header-cannot-add-to-webrequest-headers.aspx,摘录如下:

你可以在这里设置其他限制的标头. 注意: Range HTTP标头是通过AddRange来添加 If-Modified-Since HTTP标头通过IfModifiedSince 属性设置 Accept由 Accept 属性设置。 Connection由 Connection 属性和 KeepAlive 属性设置。 Content-Length由 ContentLength 属性设置。 Content-Type由 ContentType 属性设置。 Expect由 Expect 属性设置。 Date由 Date属性设置,默认为系统的当前时间。 Host由系统设置为当前主机信息。 Referer由 Referer 属性设置。 Transfer-Encoding由 TransferEncoding 属性设置(SendChunked 属性必须为 true)。 User-Agent由 UserAgent 属性设置。

其中: Connection 设置会出错,所以我注掉了。

正文到此结束
Loading...