思路
负载均衡服务器最出名的当数 Nginx了。Nginx服务器通过异步的方式把连接转发给内网和N个服务器,用来分解单台应用服务器的压力,了解了原理及场景后,用C#来实现一个。思路如下:
1. 使用一个站点的 Application_BeginRequest 来接收连接,转发连接。
2. 对各类静态资源做单独处理,(可转可不转)
3. 可以转发Get,Post,异步转发。
4. 对指定的请求,转发到同一台服务器,保持使用者的登录状态。
实现
Vs2015建一个Mvc建站: localhost:1500/。修改Web.config,用于接收所有连接。
<system.webServer> <modules runAllManagedModulesForAllRequests="true"> </modules> </system.webServer>
引入 MyCmn.dll (http://code.taobao.org/svn/MyOql/libs4),MyHelper 封装了 类型转换函数,方便使用。
代码如下:
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 设置会出错,所以我注掉了。