C#手動作一個負載均衡服務器

思路

負載均衡服務器最出名的當數 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

代碼以下:異步

(項目Svn地址: http://code.taobao.org/svn/MyMvcApp/MyProxy)

        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 屬性設置。
 
其中: Connection 設置會出錯,因此我注掉了。
 

流媒體返回頭特徵

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

相關文章
相關標籤/搜索