在這篇文章我分享瞭如何使用分層與模塊化的方法來設計一個分佈式服務集羣。這個分佈式服務集羣是基於DynamicProxy、WCF和OSGi.NET插件框架實現的。我將從設計思路、目標和實現三方面來描述。json
1 設計思路服務器
首先,我來講明一下設計思路。咱們先來看看目前OSGi.NET插件框架的服務。在這裏,服務不是遠程服務,它是輕量級的服務,由接口和實現類組成,以下圖所示。服務契約插件定義了服務接口,服務實現插件向服務總線註冊服務,服務調用插件利用服務契約(接口)從服務總線獲取實現的服務並調用,服務實現插件和服務調用插件都依賴於服務契約,但兩者並未有依賴。服務是插件間鬆耦合的調用方式。app
咱們但願在不更改現有的通信機制的狀況下,將之前定義的在同一個框架下的服務可以直接不更改代碼狀況下,變成遠程服務。此時,基於遠程服務的通信方式變成以下圖所示的方式。負載均衡
這時候,在不更改服務定義、服務註冊的代碼下,在OSGi.NET框架中安裝一個遠程服務宿主插件,它直接將服務總線的服務暴露成遠程服務;OSGi.NET插件框架安裝一個遠程服務客戶端插件,就可使用服務契約來獲取並調用遠程服務。框架
接下來,咱們但願能更進一步在不須要更改服務定義和註冊代碼狀況下,來實現透明的集羣支持。分佈式
在這裏,咱們引入了負載均衡插件。首先,一個OSGi.NET插件框架安裝了遠程服務負載均衡插件,它管理全部遠程服務的負載情況,併爲集羣提供了統一的訪問和負載均衡支持;接着,全部安裝了遠程服務宿主插件的OSGi.NET框架,會安裝一個負載均衡客戶端插件,它用於將遠程服務註冊到負載均衡器;服務調用端安裝了遠程服務客戶端插件,它經過負載均衡器來調用遠程服務。模塊化
這個思路能夠簡單描述以下:spa
A)本地服務 = OSGi.NET插件框架 + 服務契約插件 + 服務實現插件 + 服務調用插件;服務實現和服務調用在同一個OSGi.NET插件框架內,在同一個進程。插件
B)遠程服務實現 = OSGi.NET插件框架 + 服務契約插件 + 服務實現插件 + 遠程服務宿主插件,遠程服務調用 = OSGi.NET插件框架 + 服務契約插件 + 遠程服務客戶端插件;服務實現和服務調用能夠在不一樣的OSGi.NET插件框架,在不一樣進程內。設計
C)負載均衡器 = OSGi.NET插件框架 + 遠程服務負載均衡插件,負載均衡遠程服務實現 = OSGi.NET插件框架 + 服務契約插件 + 服務實現插件 + 遠程服務宿主插件 + 負載均衡客戶端插件,遠程服務調用 = OSGi.NET插件框架 + 服務契約插件 + 遠程服務客戶端插件; 負載均衡器、遠程服務、服務調用都可以在不一樣OSGi.NET插件框架和不一樣進程,遠程服務能夠在多臺機器中,註冊到負載均衡器。
2 設計目標
遠程服務和負載均衡的實現基於模塊化思路,以下圖所示。
(1)不更改本地服務的定義、註冊和使用方法;
(2)在本地服務的基礎上,安裝遠程服務宿主插件,服務就暴露成遠程服務;
(3)在遠程服務的基礎上,安裝負載均衡客戶端插件和負載均衡器,遠程服務就支持集羣及負載均衡。
以一個簡單的服務ISayHelloService爲例,下面將描述如何經過以上方式來實現遠程服務和服務集羣。
2.1 遠程服務示例
2.1.1 遠程服務註冊及實現
以下圖所示,SayHelloServiceContract插件定義了一個ISayHelloService服務接口,SayHelloService定義了SayHelloServiceImpl服務實現,在OSGi.NET插件框架安裝了UIShell.RemoteServiceHostPlugin插件,這樣咱們就將本地服務暴露成遠程服務了。
下圖是SayHelloServiceImpl服務的實現和服務註冊。
下圖則是服務註冊。
你能夠發現,爲了支持遠程服務,咱們僅僅是安裝了一個遠程服務宿主插件,而沒有更改服務的實現和註冊方法。
2.1.2 遠程服務調用
遠程服務調用以下所示,在OSGi.NET插件框架安裝了遠程服務客戶端插件。服務調用插件使用服務契約,來調用遠程服務。
調用遠程服務的步驟爲:
1 使用遠程服務客戶端插件的IRemoteServiceProxyService來獲取遠程服務;
2 直接調用遠程服務的方法。
所以,你能夠發現,遠程服務的定義和使用都很是的簡單。接下來咱們再看看負載均衡遠程服務的使用。
2.2 負載均衡遠程服務示例
2.2.1 負載均衡器
負載均衡器至關於遠程服務註冊表,它用於註冊暴露遠程服務的全部機器以及每個機器每個服務的負載均衡情況,並提供負載均衡支持。下圖是負載均衡器的實現。
負載均衡器向外暴露一個固定的IP地址和端口號,用於註冊遠程服務,並提供負載均衡。
2.2.2 負載均衡遠程服務
支持負載均衡的遠程服務須要安裝一個負載均衡客戶端插件,以下所示。負載均衡客戶端插件用於將遠程服務註冊到負載均衡器,從而,負載均衡器能夠來管理遠程服務的負載狀況,當發生故障時能夠實現負載轉移和實現負載均衡。
這裏,遠程負載均衡器客戶端插件會鏈接到負載均衡服務器,向其註冊本機器的遠程服務。
2.2.3 負載均衡遠程服務調用
調用負載均衡遠程服務與直接調用遠程服務方法相似,以下圖所示。它使用GetFirstOrDefaultLoadBalancerService接口來訪問負載均衡器,獲取通過負載均衡的遠程服務。
你能夠發現,調用負載均衡遠程服務的方法也很是簡單。下面,我來介紹一下如何實現。
3 設計實現
首先,咱們先來看看遠程服務的實現。
3.1 遠程服務的實現
遠程服務的實現能夠歸結爲如下幾點:(1)遠程服務宿主插件用於暴露一個WCF服務,這個WCF服務至關於本地服務的Bridge,即客戶端對遠程服務的調用先中專到這個WCF服務,再由WCF服務來調用本地服務,而後返回給客戶端;(2)客戶端使用DynamicProxy爲服務契約生成一個代理,對這個代理的方法調用將會被攔截,而後調用遠程服務宿主插件的WCF服務,將調用結果再返回。
3.1.1 遠程服務宿主插件實現
該插件首先定義了一個IRemoteServiceInvoker的WCF服務接口,這個接口及參數的定義以下。
它的做用就是經過調用這個WCF遠程服務來調用OSGi.NET框架本地服務,達到將本地服務暴露成遠程服務的目的。
這個WCF的實現代碼以下所示,其目的就是對WCF的調用轉換成對本地服務方法的調用並返回。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Reflection; 7 using System.Threading.Tasks; 8 using fastJSON; 9 using Fasterflect; 10 using UIShell.OSGi.Utility; 11 12 namespace UIShell.RemoteServiceHostPlugin 13 { 14 public class RemoteServiceInvoker : IRemoteServiceInvoker 15 { 16 public static ReaderWriterLock Locker = new ReaderWriterLock(); 17 public static Dictionary<string, System.Tuple<MethodInfo, Type[], MethodInvoker>> InvokerCache = new Dictionary<string, System.Tuple<MethodInfo, Type[], MethodInvoker>> (); 18 19 public string InvokeService(RemoteServiceInvocation invocation) 20 { 21 AssertUtility.NotNull(invocation); 22 AssertUtility.ArgumentHasText(invocation.ContractName, "service contract name"); 23 AssertUtility.ArgumentHasText(invocation.MethodName, "service method name"); 24 25 var service = Activator.Context.GetFirstOrDefaultService(invocation.ContractName); 26 string msg = string.Empty; 27 if(service == null) 28 { 29 msg = string.Format ("Remote Service '{0}' not found.", invocation.ContractName); 30 FileLogUtility.Warn (msg); 31 throw new Exception(msg); 32 } 33 34 System.Tuple<MethodInfo, Type[], MethodInvoker> invokerTuple; 35 using (var locker = ReaderWriterLockHelper.CreateReaderLock (Locker)) 36 { 37 InvokerCache.TryGetValue (invocation.Key, out invokerTuple); 38 } 39 40 if (invokerTuple == null) 41 { 42 Type serviceType = service.GetType (); 43 44 var serviceMethodInfo = serviceType.GetMethod (invocation.MethodName); 45 if (serviceMethodInfo == null) 46 { 47 msg = string.Format ("The method '{1}' of the remote service '{0}' not found.", invocation.ContractName, invocation.MethodName); 48 FileLogUtility.Warn (msg); 49 throw new Exception (msg); 50 } 51 52 if (invocation.JsonSerializedParameters == null) { 53 invocation.JsonSerializedParameters = new List<string> (); 54 } 55 56 var parameterInfos = serviceMethodInfo.GetParameters (); 57 if (invocation.JsonSerializedParameters.Count != parameterInfos.Length) 58 { 59 msg = string.Format ("The parameters count is not match with the method '{0}' of service '{1}'. The expected count is {2}, the actual count is {3}.", invocation.MethodName, invocation.ContractName, parameterInfos.Length, invocation.JsonSerializedParameters.Count); 60 FileLogUtility.Warn (msg); 61 throw new Exception (msg); 62 } 63 64 var parameterTypes = new Type[parameterInfos.Length]; 65 for (int i = 0; i < parameterInfos.Length; i++) 66 { 67 parameterTypes [i] = parameterInfos [i].ParameterType; 68 } 69 70 try 71 { 72 var methodInvoker = serviceType.DelegateForCallMethod (invocation.MethodName, parameterTypes); 73 74 invokerTuple = new System.Tuple<MethodInfo, Type[], MethodInvoker> (serviceMethodInfo, parameterTypes, methodInvoker); 75 76 using (var locker = ReaderWriterLockHelper.CreateWriterLock (Locker)) 77 { 78 if (!InvokerCache.ContainsKey (invocation.Key)) 79 { 80 InvokerCache [invocation.Key] = invokerTuple; 81 } 82 } 83 } 84 catch(Exception ex) 85 { 86 msg = string.Format ("Failed to create delegate method for the method '{0}' of service '{1}'.", invocation.MethodName, invocation.ContractName); 87 FileLogUtility.Warn (msg); 88 FileLogUtility.Warn (ex); 89 throw new Exception (msg, ex); 90 } 91 } 92 93 var paramters = new object[invokerTuple.Item2.Length]; 94 95 for(int i = 0; i < invokerTuple.Item2.Length; i++) 96 { 97 try 98 { 99 paramters[i] = JSON.ToObject(invocation.JsonSerializedParameters[i], invokerTuple.Item2[i]); 100 } 101 catch(Exception ex) 102 { 103 msg = string.Format ("Failed to unserialize the '{0}'th parameter for the method '{1}' of service '{2}'.", i + 1, invocation.MethodName, invocation.ContractName); 104 FileLogUtility.Warn (msg); 105 FileLogUtility.Warn (ex); 106 throw new Exception (msg, ex); 107 } 108 } 109 110 try 111 { 112 return JSON.ToJSON(invokerTuple.Item3(service, paramters)); 113 } 114 catch(Exception ex) 115 { 116 msg = string.Format("Failed to invoke the method '{0}' of service '{1}'.", invocation.MethodName, invocation.ContractName); 117 FileLogUtility.Warn (msg); 118 FileLogUtility.Warn (ex); 119 throw new Exception (msg, ex); 120 } 121 } 122 } 123 }
3.1.2 遠程服務客戶端插件的實現
接下來,咱們看看遠程服務客戶端插件的實現。它定義了一個IRemoteServiceProxyService服務,暴露了兩個接口分別用於對遠程服務和負載均衡遠程服務的調用。
該服務的遠程服務獲取實現以下所示。
它僅僅時經過DynamicProxy建立遠程服務代理類,此時,對代理類方法的調用會轉換成遠程服務調用。下面看看攔截機的實現。
1 class RemoteServiceProxyInterceptor : IInterceptor, IDisposable 2 { 3 private RemoteServiceContext _remoteServiceContext; 4 private RemoteServiceClient _remoteServiceClient; 5 public RemoteServiceProxyInterceptor(RemoteServiceContext context) 6 { 7 _remoteServiceContext = context; 8 _remoteServiceClient = new RemoteServiceClient(_remoteServiceContext.IPAddress, _remoteServiceContext.Port); 9 _remoteServiceClient.Start(); 10 } 11 public void Intercept(IInvocation invocation) 12 { 13 try 14 { 15 var jsonParameters = new List<string>(); 16 foreach (var param in invocation.Arguments) 17 { 18 jsonParameters.Add(JSON.ToJSON(param)); 19 } 20 21 var resultJson = _remoteServiceClient.Invoke(invocation.Method.DeclaringType.FullName, invocation.Method.Name, jsonParameters); 22 23 if (!invocation.Method.ReturnType.FullName.Equals("System.Void")) 24 { 25 invocation.ReturnValue = JSON.ToObject(resultJson, invocation.Method.ReturnType); 26 } 27 else 28 { 29 invocation.ReturnValue = null; 30 } 31 } 32 catch(Exception ex) 33 { 34 FileLogUtility.Error (string.Format("Failed to invoke the remote service 'Remote Service: {0}, Method: {1}.'", 35 invocation.Method.DeclaringType.FullName, invocation.Method.Name)); 36 throw; 37 } 38 } 39 40 public void Dispose() 41 { 42 if (_remoteServiceClient != null) 43 { 44 _remoteServiceClient.Stop(); 45 _remoteServiceClient = null; 46 } 47 } 48 }
攔截機的代碼很簡單,對遠程服務代理類方法的調用將直接轉換成對遠程服務宿主插件的WCF服務的調用。下面看看負載均衡遠程服務的實現。
3.2 負載均衡遠程服務的實現
有了以上的技術,關於負載均衡遠程服務的實現,就簡單多了。負載均衡遠程服務的目的就是將全部遠程服務統一在負載均衡服務器進行註冊,並實現負載的動態管理。所以,須要在遠程服務基礎上,建立一個負載均衡服務器和負載均衡客戶端。負載均衡服務器用於管理全部遠程服務及提供遠程服務的機器,管理全部遠程服務的負載狀況,並實現負載均衡及故障轉移;負載均衡客戶端的目的時將遠程服務註冊到負載均衡器,而且當遠程服務關閉時從負載均衡器卸載。下面,看看負載均衡器的實現。
3.2.1 負載均衡器實現
負載均衡服務器暴露了以下WCF服務。這個服務用於提供遠程服務註冊和卸載以及負載均衡請求。
這個服務的實現以下所示。
它使用遠程服務註冊表來實現遠程服務的管理和負載均衡的實現。
3.2.2 負載均衡客戶端的實現
負載均衡客戶端的目的時實現遠程服務的註冊與卸載,經過該插件將遠程服務暴露到負載均衡服務器。這樣服務調用者就能夠經過負載均衡器來調用遠程服務。
3.2.3 負載均衡遠程服務調用
負載均衡遠程服務的調用方式的實現和遠程服務相似。它由遠程服務代理服務的GetFirstOrDefaultLoadBalancerService接口來實現。
該接口的實現以下所示,主要時建立代理和方法攔截機。
這個方法調用攔截機會將方法調用轉化爲:(1)從負載均衡服務器獲取均衡的遠程服務主機;(2)直接調用該遠程服務主機的服務,若是調用失敗則嘗試進行從新負載均衡。其實現以下所示。
1 class RemoteServiceLoadBalancerProxyInterceptor : IInterceptor, IDisposable 2 { 3 private string LoadBalancerHost 4 { 5 get 6 { 7 return ConfigurationSettings.AppSettings["LoadBalancerHost"]; 8 } 9 } 10 11 private string LoadBalancerPort 12 { 13 get 14 { 15 return ConfigurationSettings.AppSettings["LoadBalancerPort"]; 16 } 17 } 18 19 private LoadBalancerContext _remoteServiceLoadBalancerContext; 20 private RemoteServiceClient _remoteServiceClient; 21 private RemoteServiceLoadBalancerAccessClient _remoteServiceLoadBalancerAccessClient; 22 private bool _initialized; 23 24 public RemoteServiceLoadBalancerProxyInterceptor(LoadBalancerContext context) 25 { 26 _remoteServiceLoadBalancerContext = context; 27 28 if (string.IsNullOrEmpty(LoadBalancerHost)) 29 { 30 throw new Exception("You need to specified the load balancer host (HostName or IP Address) by app setting 'LoadBalancerHost'."); 31 } 32 33 int loadBalancerPortInt; 34 if (!int.TryParse(LoadBalancerPort, out loadBalancerPortInt)) 35 { 36 throw new Exception("You need to specified the load balancer port by app setting 'LoadBalancerPort'."); 37 } 38 39 try 40 { 41 _remoteServiceLoadBalancerAccessClient = new RemoteServiceLoadBalancerAccessClient (LoadBalancerHost, loadBalancerPortInt); 42 _remoteServiceLoadBalancerAccessClient.Start (); 43 } 44 catch(Exception ex) 45 { 46 FileLogUtility.Error (string.Format("Faild to connect to load balancer '{0}'.", _remoteServiceLoadBalancerContext)); 47 FileLogUtility.Error (ex); 48 throw; 49 } 50 } 51 52 private bool Initialize(string serviceContractName) 53 { 54 if(_remoteServiceClient != null) 55 { 56 _remoteServiceClient.Stop(); 57 } 58 59 RemoteServiceHost remoteHost = null; 60 try 61 { 62 remoteHost = _remoteServiceLoadBalancerAccessClient.Balance(serviceContractName); 63 FileLogUtility.Inform(string.Format("Get the remote service host '{0}' by load balancer '{1}'.", remoteHost, _remoteServiceLoadBalancerContext)); 64 } 65 catch(Exception ex) 66 { 67 FileLogUtility.Error (string.Format("Faild to get a remote service host by load balancer '{0}'.", _remoteServiceLoadBalancerContext)); 68 FileLogUtility.Error (ex); 69 return false; 70 } 71 if (remoteHost != null) 72 { 73 _remoteServiceClient = new RemoteServiceClient (remoteHost.IPAddress, remoteHost.Port); 74 try 75 { 76 _remoteServiceClient.Start (); 77 return true; 78 } 79 catch(Exception ex) 80 { 81 FileLogUtility.Error (string.Format("Failed to connect to the remote service host '{0}' by using load balancer '{1}'.", remoteHost, _remoteServiceLoadBalancerContext)); 82 } 83 } 84 85 return false; 86 } 87 88 public void Intercept(IInvocation invocation) 89 { 90 var serviceContractName = invocation.Method.DeclaringType.FullName; 91 if (!_initialized) 92 { 93 _initialized = Initialize (serviceContractName); 94 if (!_initialized) 95 { 96 invocation.ReturnValue = null; 97 return; 98 } 99 } 100 101 var jsonParameters = new List<string>(); 102 foreach (var param in invocation.Arguments) 103 { 104 jsonParameters.Add(JSON.ToJSON(param)); 105 } 106 107 int tryTimes = 1; 108 109 for (int i = 0; i < tryTimes; i ++ ) 110 { 111 try 112 { 113 var resultJson = _remoteServiceClient.Invoke(serviceContractName, invocation.Method.Name, jsonParameters); 114 115 if (!invocation.Method.ReturnType.FullName.Equals("System.Void")) 116 { 117 invocation.ReturnValue = JSON.ToObject(resultJson, invocation.Method.ReturnType); 118 } 119 else 120 { 121 invocation.ReturnValue = null; 122 } 123 return; 124 } 125 catch(Exception ex) 126 { 127 FileLogUtility.Error (string.Format("Failed to invoke the remote service 'Remote Service: {0}, Method: {1}.'", 128 serviceContractName, invocation.Method.Name)); 129 FileLogUtility.Error (ex); 130 if (i == tryTimes) 131 { 132 throw; 133 } 134 if (!((_initialized = Initialize (serviceContractName)) == true)) // 從新Balance 135 { 136 throw; 137 } 138 } 139 } 140 } 141 142 public void Dispose() 143 { 144 if (_remoteServiceClient != null) 145 { 146 _remoteServiceClient.Stop(); 147 _remoteServiceClient = null; 148 } 149 } 150 }
4 小結
在這篇文章,我詳細介紹了支持集羣的遠程服務的實現。你能夠發現,總體實現徹底按照模塊化組裝的方式。你也能夠嘗試來考慮以模塊化組裝的方法實現一個遠程服務集羣。多謝支持~~。