最近跟高老师讨论nginx跟tomcat集群做负载均衡方案。感觉很有意思。想到自己项目中服务用的WCF技术,于是就想WCF如何做负载均衡,Google了一会,发现wcf4.0的路由服务好像可以实现。不过在研究路由服务期间,我有了个自己的方案,哈哈。
我要在客户端跟WCF服务中间部署一台WCF平衡服务器,用来分发请求,模拟nginx的工作。
WCF平衡服务器我同样用WCF来实现,所有服务接口全部通过平衡服务区暴露给客户端。对于客户端来说,只要跟正常调用服务一样,添加平衡器的远程服务引用。
实现:
1.平衡服务类库
namespace WcfSimpleBalance { /// <summary> /// 负载均衡基类 /// </summary> /// <typeparam name="T"></typeparam> public class WcfBalance<T> { private ChannelFactory<T> _channelFactory; public T BalanceProxy { get; set; } public WcfBalance(string serviceName) { string endpoint = EndpointBalance.GetBalanceEndpoint(serviceName);//获取随机endpoint this._channelFactory = new ChannelFactory<T>(endpoint); this.BalanceProxy = this._channelFactory.CreateChannel(); } } }
其中泛型T为协定,这样就能动态构建wcf的通道了。
namespace WcfSimpleBalance { internal class EndpointBalance { /// <summary> /// 平衡节点配置 /// </summary> private static List<WcfBalanceSection> _wcfBalanceCfg; static EndpointBalance() { _wcfBalanceCfg = ConfigurationManager.GetSection("wcfBalance") as List<WcfBalanceSection>; } /// <summary> /// 随机一个Endpoint /// </summary> /// <param name="serviceName"></param> /// <returns></returns> public static string GetBalanceEndpoint(string serviceName) { var serviceCfg = _wcfBalanceCfg.Single(s=>s.ServiceName==serviceName); var ran = new Random(); int i = ran.Next(0, serviceCfg.Endpoints.Count); string endpoint = serviceCfg.Endpoints[i]; Console.WriteLine(endpoint); return endpoint; } } }
这个类提供一个静态方法可以根据服务名称从配置文件中配置的endpoint,并且从中随机一个。随机数的算法可能分布不是特别均匀,不知有什么好的办法。
namespace WcfSimpleBalance {/// <summary> /// 配置模型 /// </summary> internal class WcfBalanceSection { public string ServiceName { get; set; } public List<string> Endpoints { get; set; } }
/// <summary> /// 自定义配置处理 /// </summary> public class WcfBalanceSectionHandler : IConfigurationSectionHandler { public object Create(object parent, object configContext, XmlNode section) { var config = new List<WcfBalanceSection>(); foreach (XmlNode node in section.ChildNodes) { if (node.Name != "balanceService") throw new ConfigurationErrorsException("不可识别的配置项", node); var item = new WcfBalanceSection(); foreach (XmlAttribute attr in node.Attributes) { switch (attr.Name) { case "ServiceName": item.ServiceName = attr.Value; break; case "Endpoints": item.Endpoints = attr.Value.Split(',').ToList(); break; default: throw new ConfigurationErrorsException("不可识别的配置属性", attr); } } config.Add(item); } return config; } } }
这2个是用来处理配置文件的。
2.普通的WCF服务
协定:
namespace WcfServiceContracts { [ServiceContract(Name = "CalculatorService")] public interface IAdd { [OperationContract] int Add(int x, int y); } }
一个简单的加法。
wcf服务实现:
namespace WcfService { public class AddServices:IAdd { public int Add(int x, int y) { return x + y; } } }
3.WCF平衡器实现
同样新建一个wcf服务类库,引用同样的协定,引用上面的平衡类库
namespace WcfServiceBalance { public class AddServices : WcfBalance<IAdd>, IAdd { public AddServices() : base("AddServices") { } public int Add(int x, int y) { return BalanceProxy.Add(x, y); } } }
继承WcfBalance跟协定接口。构造函数调用基类的构造函数,传入服务名称。Add实现直接调用基类的方法。
模拟:
1.wcf服务器寄宿
WCF服务可以寄宿在多个方案下面,IIS,win服务,控制台。这里为了方便直接寄宿在控制台下。
新建2个控制台程序,一个寄宿普通的wcf服务。一个寄宿wcf平衡服务。代码不表,给出服务地址。
3个普通的服务。(把寄宿普通服务的控制台程序的bin目录复制3份,改3个端口就成了3个服务)
平衡服务
http://localhost:8088/WcfBalance
配置文件
在平衡服务器的配置文件中定义所有后台服务器的endpoint,然后在自定义wcfBalance节点中配置,服务名对应的endpoint列表用逗号分隔。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="wcfBalance" type="WcfSimpleBalance.WcfBalanceSectionHandler, WcfSimpleBalance" /> </configSections> <wcfBalance> <balanceService ServiceName="AddServices" Endpoints="AddService1,AddService2,AddService3" /> </wcfBalance> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_CalculatorService" /> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost:8081/Wcf" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_CalculatorService" contract="WcfServiceContracts.IAdd" name="AddService1" /> <endpoint address="http://localhost:8082/Wcf" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_CalculatorService" contract="WcfServiceContracts.IAdd" name="AddService2" /> <endpoint address="http://localhost:8083/Wcf" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_CalculatorService" contract="WcfServiceContracts.IAdd" name="AddService3" /> </client> </system.serviceModel> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> </configuration>
2.客户端调用
添加平衡服务器引用,然后用代码调用。
启动30个线程去跑服务。
namespace WcfServiceClient { class Program { static void Main(string[] args) { for (int i = 0; i < 30; i++) { var thread = new Thread(new ThreadStart(CallAdd)); thread.Start(); } Console.Read(); } private static void CallAdd() { using (var proxy = new CalculatorServiceClient()) { proxy.Add(1,2); } } } }
运行结果:
请求被分布到3个服务上面,不过貌似不太均匀,这个跟算法有关系。
通过以上我们实现了一个简单的wcf平衡服务器,这只是一个简单的方案,肯定有很多很多问题没有考虑到,希望大家指出讨论。
不过我想虽然实现了请求的分发,但是面对真正的高并发环境,平衡服务器会不会成为另外一个瓶颈。