最近跟高老師討論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平衡服務器,這只是一個簡單的方案,肯定有很多很多問題沒有考慮到,希望大家指出討論。
不過我想雖然實現了請求的分發,但是面對真正的高並發環境,平衡服務器會不會成為另外一個瓶頸。