C#開發負載平衡伺服器執行個體

來源:互聯網
上載者:User

思路

負載平衡伺服器最出名的當數 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 屬性設定。


ASP.NET C# 如何做分布式負載平衡?具體思路?

曾在去年為一個客戶做過基於 nginx 的負載平衡,後來再加上一些新的實踐,總結了一下,願意分享。實際上,我目前接觸的項目,也並不算大,並沒有針對超大型項目(如事務和邏輯密集型、大量並發的應用)做過相關實踐,如果有人在相關領域有更好的方案,歡迎探討。分布式的程式與單個執行個體運行是有很大區別的,所以在分布式之前,請確保你的應用在開發階段最好有相關的準備。一個完全針對單伺服器環境開發的應用,要想在分布式均衡系統中完美運行,要做的工作之多會讓營運人員抓狂。IIS 提供了 Web Farm 部署的能力,即針對在同一台伺服器上的特定 Web 網站,為其應用程式集區配備多個進程。這種方式可以一定程式上類比應用在分布式環境下的運行情況,比如 Session、Cache 的共用和身份鑒權等。但由於分布式負載平衡通常使用反向 Proxy來完成入口伺服器與多個分布式運算伺服器之間的傳輸,因此在分布式負載平衡環境中,運行在不同伺服器(或相同伺服器上的不同 IIS 網站執行個體)可能在 Request URL 運算、檔案儲存體、相對路徑映射等這些方面與單一實例運行時存在差異。因此,在準備階段請先確定你已經解決了上述這些問題,在這裡我就不展開了,畢竟針對不同的應用會有不同的具體工作。而後,接下來的工作就顯得很從容了。分布式負載平衡的一般思路是整個系統僅有一個統一入口,再由這個統一入口 E 按配置的均衡規則將請求「反向」代理到多個運算伺服器 S,最後再將由 S 運算得到的結果輸出到用戶端。在每個 S 上,使用單一實例時一樣的配置:每一個 S 就是一個在普通部署方式下的正常伺服器:因此要將它們的資料庫連接串、Session 提供者、Cache 提供者、Form 鑒權所需的必要的 machineKey 等相關資源配備到統一位置或作統一處理,以保證所有 S 工作狀態的一致性(即當使用者的請求到達任何 S 時,其工作階段狀態、緩衝狀態等都一致)。在 E 的反向 Proxy軟體上,將所有 S 按策略配置完善(包括伺服器位置、連接埠與權值;代理 IP和原始 host 頭等)。出於安全考慮,可以將所有 S 的訪問限制為僅 E 能訪問,而 E 則應該配置為能夠公開訪問,最後將網域名稱解析到 E 上。注意,所有後續請求將先到達 E 上,然後再由 E 按照配置的策略分配給具體的某一個 S。因此,如果應用中有上傳檔案、自訂 MIME 類型、錯誤頁等可能與 IIS 預設限制/功能相關的特性,注意 E 與 S 在這些配置上的一致性。此外,關於 E 上要使用的反向 Proxy軟體,可以使用大家廣為熟悉的 nginx,也可以使用微軟與 IIS 整合的 Application Request Routing 組件(ARR)。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.