C#开发负载均衡服务器实例

思路

负载均衡服务器最出名的当数 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 的共享和身份鉴权等。但由于分布式负载均衡通常使用反向代理来完成入口服务器与多个分布式运算服务器之间的传输,因此在分布式负载均衡环境中,运行在不同服务器(或相同服务器上的不同 IIS 站点实例)可能在 Request URL 运算、文件存储、相对路径映射等这些方面与单实例运行时存在差异。因此,在准备阶段请先确定你已经解决了上述这些问题,在这里我就不展开了,毕竟针对不同的应用会有不同的具体工作。而后,接下来的工作就显得很从容了。分布式负载均衡的一般思路是整个系统仅有一个统一入口,再由这个统一入口 E 按配置的均衡规则将请求「反向」代理到多个运算服务器 S,最后再将由 S 运算得到的结果输出到客户端。在每个 S 上,使用单实例时一样的配置:每一个 S 就是一个在普通部署方式下的正常服务器:因此要将它们的数据库连接串、Session 提供程序、Cache 提供程序、Form 鉴权所需的必要的 machineKey 等相关资源配备到统一位置或作统一处理,以保证所有 S 工作状态的一致性(即当用户的请求到达任何 S 时,其会话状态、缓存状态等都一致)。在 E 的反向代理软件上,将所有 S 按策略配置完善(包括服务器位置、端口与权值;代理 IP和原始 host 头等)。出于安全考虑,可以将所有 S 的访问限制为仅 E 能访问,而 E 则应该配置为能够公开访问,最后将域名解析到 E 上。注意,所有后续请求将先到达 E 上,然后再由 E 按照配置的策略分配给具体的某一个 S。因此,如果应用中有上传文件、自定义 MIME 类型、错误页等可能与 IIS 默认限制/功能相关的特性,注意 E 与 S 在这些配置上的一致性。此外,关于 E 上要使用的反向代理软件,可以使用大家广为熟悉的 nginx,也可以使用微软与 IIS 集成的 Application Request Routing 组件(ARR)。

时间: 2024-08-16 12:14:22

C#开发负载均衡服务器实例的相关文章

如何使用 Weave 以及 Docker 搭建 Nginx 反向代理/负载均衡服务器

Hi, 今天我们将会学习如何使用 Weave 和 Docker 搭建 Nginx 的反向代理/负载均衡服务器.Weave 可以创建一个虚拟网络将 Docker 容器彼此连接在一起,支持跨主机部署及自动发现.它可以让我们更加专注于应用的开发,而不是基础架构.Weave 提供了一个如此棒的环境,仿佛它的所有容器都属于同个网络,不需要端口/映射/连接等的配置.容器中的应用提供的服务在 weave 网络中可以轻易地被外部世界访问,不论你的容器运行在哪里.在这个教程里我们将会使用 weave 快速并且简单

tomcat集群-为什么apache+tomcat搭建的负载均衡服务器集群在部署项目访问请求获取不到数据

问题描述 为什么apache+tomcat搭建的负载均衡服务器集群在部署项目访问请求获取不到数据 为什么apache+tomcat搭建的负载均衡服务器集群在部署项目后的项目路径不对呢,请求获取不到数据我是将项目设置在tomcat的根目录访问路径,输入localhost:8080即可访问到项目的首页,显示正常.Apache弄的是IP访问嘛,通过控制分配请求给集群下的tomcat服务器来访问项目,但是直接使用IP访问请求都获取不到数据,尝试通过IP/项目名也达不到效果.这是什么原因呢. 我当时是按照

centos下配置Nginx反向代理负载均衡服务器教程

准备: Proxy-Server:1.1.1.22(负载均衡服务器) Nginx-Server23:1.1.1.23(web23) Nginx-Server24:1.1.1.24(web24) 环境版本: Linux Nginx-Proxy 2.6.32-358.el6.x86_64 #1 SMP Fri Feb 22 00:31:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux ①:安装配置完成Nginx WEB服务器预配置: [root@Nginx-Ser

windows使用nginx实现网站负载均衡测试实例_win服务器

如果你关注过nginx,必定知道nginx这个软件有什么用的,如果你的网站访问量越来越高,一台服务器已经没有办法承受流量压力,那就增多几台服务器来做负载吧.做网站负载可以买硬件设备来实现,比如F5,不过价格就几十万到上百万,够贵,本文介绍做网站负载的软件是免费的,nginx目前好多门户网站与大访问量的网站都在使用做为HTTP服务器,所以nginx是非常优秀的,下面介绍做负载测试吧.环境:(2台服务器)第一台: CPU:Inter(R) Pentium(R) 4 CPU 2.8G 内存:1G 系统

PHP开发负载均衡指南_php技巧

今天,'大型服务器'模式已经过去,取而代之的是大量的小服务器,使用各种各样的负载均衡技术.这是一种更可行的方法,将使硬件成本降至最低. '更多小服务器'的优势超过过去的'大型服务器'模式体现在两个方面: 1. 如果服务器宕机,那么负载均衡系统将停止请求到宕机的服务器,转而分发负载到其他正常运行的服务器上. 2. 扩展你的服务器更加容易.你要做的仅仅是加入新的服务器到负载均衡系统.不需要中断你的应用运行. 所以,把握住这个机会:). 当然,代价就是这要求你的应用开发时增加一点复杂度.这就是本文要覆

windows 2008负载均衡服务器的部署

项目环境: 两台Windows Server 2008 均为双网卡 IP地址 192.168.19.100(外网1) 99.99.99.99(内网1专用) 192.168.19.101(外网2) 99.99.99.100(内网2专用) 其中192.168.19.100是主DNS 101是备份DNS 虚拟IP地址192.168.19.125 思路: 首先在W2K08上面安装负载均衡服务(NLB),IIS服务是必须安装的:在此项目中IIS安装的部署就不展示.由于W2K08负载均衡的并发量有所限制,我

Nginx负载均衡配置实例详解

负载均衡是我们大流量网站要做的一个东西,下面我来给大家介绍在Nginx服务器上进行负载均衡配置方法.   测试环境 测试域名  :www.threegroup.space A服务器IP :123.56.255.173 (主) B服务器IP :101.200.159.138 C服务器IP :123.56.255.53   部署思路A服务器做为主服务器,域名直接解析到A服务器(123.56.255.173)上,由A服务器负载均衡到B服务器(101.200.159.138)与C服务器(123.56.2

负载均衡服务器Session共享的解决方案

在ASP.NET的程序中要使用Session对象时,必须确保页面的@page指令中EnableSessionState属性是True或者 Readonly,并且在web.config文件中正确的设置了SessionState属性. ASP.NET中Session的状态保持是由web.config文件中的标记下的标记的mode属性来决定的.该属性有四种可能的值:Off.Inproc.StateServer和SQlServer. 设为Off会禁用Session. Inproc是缺省的设置,这种模式和

一起谈.NET技术,负载均衡服务器Session共享的解决方案

在ASP.NET的程序中要使用Session对象时,必须确保页面的@page指令中EnableSessionState属性是True或者 Readonly,并且在web.config文件中正确的设置了SessionState属性. ASP.NET中Session的状态保持是由web.config文件中的标记下的标记的mode属性来决定的.该属性有四种可能的值:Off.Inproc.StateServer和SQlServer. 设为Off会禁用Session. Inproc是缺省的设置,这种模式和