負載均衡服務器最出名的當數 Nginx了。Nginx服務器經過異步的方式把鏈接轉發給內網和N個服務器,用來分解單臺應用服務器的壓力,瞭解了原理及場景後,用C#來實現一個。思路以下:css
1. 使用一個站點的 Application_BeginRequest 來接收鏈接,轉發鏈接。html
2. 對各種靜態資源作單獨處理,(可轉可不轉)nginx
3. 能夠轉發Get,Post,異步轉發。web
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 封裝了 類型轉換函數,方便使用。asp.net
代碼以下:異步
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 async
關於 WebHeaderCollection.IsRestricted ,是因爲一個錯誤引出的: 異常處理:必須使用適當的屬性或方法修改此標頭,文章地址: http://blog.useasp.net/archive/2013/09/03/the-methods-to-dispose-http-header-cannot-add-to-webrequest-headers.aspx,摘錄以下:ide
你能夠在這裏設置其餘限制的標頭.
注意:
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 屬性設置。
Status : OKStatus_Code : 200Connection : keep-aliveAccept-Ranges : bytesContent-Length : 2373825Content-Type : video/mp4Date : Sun, 17 Apr 2016 02:39:17 GMTLast-Modified : Fri, 15 Apr 2016 10:51:35 GMTServer : nginx/1.9.3