分享在Linux下使用OSGi.NET插件框架快速實現一個分佈式服務集羣的方法

在這篇文章我分享瞭如何使用分層與模塊化的方法來設計一個分佈式服務集羣。這個分佈式服務集羣是基於DynamicProxy、WCF和OSGi.NET插件框架實現的。我將從設計思路、目標和實現三方面來描述。json

1 設計思路服務器

首先,我來講明一下設計思路。咱們先來看看目前OSGi.NET插件框架的服務。在這裏,服務不是遠程服務,它是輕量級的服務,由接口和實現類組成,以下圖所示。服務契約插件定義了服務接口,服務實現插件向服務總線註冊服務,服務調用插件利用服務契約(接口)從服務總線獲取實現的服務並調用,服務實現插件和服務調用插件都依賴於服務契約,但兩者並未有依賴。服務是插件間鬆耦合的調用方式。app

clip_image001

咱們但願在不更改現有的通信機制的狀況下,將之前定義的在同一個框架下的服務可以直接不更改代碼狀況下,變成遠程服務。此時,基於遠程服務的通信方式變成以下圖所示的方式。負載均衡

clip_image002

這時候,在不更改服務定義、服務註冊的代碼下,在OSGi.NET框架中安裝一個遠程服務宿主插件,它直接將服務總線的服務暴露成遠程服務;OSGi.NET插件框架安裝一個遠程服務客戶端插件,就可使用服務契約來獲取並調用遠程服務。框架

接下來,咱們但願能更進一步在不須要更改服務定義和註冊代碼狀況下,來實現透明的集羣支持。分佈式

clip_image004

在這裏,咱們引入了負載均衡插件。首先,一個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 設計目標

遠程服務和負載均衡的實現基於模塊化思路,以下圖所示。

clip_image006

(1)不更改本地服務的定義、註冊和使用方法;

(2)在本地服務的基礎上,安裝遠程服務宿主插件,服務就暴露成遠程服務;

(3)在遠程服務的基礎上,安裝負載均衡客戶端插件和負載均衡器,遠程服務就支持集羣及負載均衡。

以一個簡單的服務ISayHelloService爲例,下面將描述如何經過以上方式來實現遠程服務和服務集羣。

2.1 遠程服務示例

2.1.1 遠程服務註冊及實現

以下圖所示,SayHelloServiceContract插件定義了一個ISayHelloService服務接口,SayHelloService定義了SayHelloServiceImpl服務實現,在OSGi.NET插件框架安裝了UIShell.RemoteServiceHostPlugin插件,這樣咱們就將本地服務暴露成遠程服務了。

clip_image008

下圖是SayHelloServiceImpl服務的實現和服務註冊。

clip_image010

下圖則是服務註冊。

clip_image012

你能夠發現,爲了支持遠程服務,咱們僅僅是安裝了一個遠程服務宿主插件,而沒有更改服務的實現和註冊方法。

2.1.2 遠程服務調用

遠程服務調用以下所示,在OSGi.NET插件框架安裝了遠程服務客戶端插件。服務調用插件使用服務契約,來調用遠程服務。

clip_image014

調用遠程服務的步驟爲:

1 使用遠程服務客戶端插件的IRemoteServiceProxyService來獲取遠程服務;

2 直接調用遠程服務的方法。

所以,你能夠發現,遠程服務的定義和使用都很是的簡單。接下來咱們再看看負載均衡遠程服務的使用。

2.2 負載均衡遠程服務示例

2.2.1 負載均衡器

負載均衡器至關於遠程服務註冊表,它用於註冊暴露遠程服務的全部機器以及每個機器每個服務的負載均衡情況,並提供負載均衡支持。下圖是負載均衡器的實現。

clip_image016

負載均衡器向外暴露一個固定的IP地址和端口號,用於註冊遠程服務,並提供負載均衡。

2.2.2 負載均衡遠程服務

支持負載均衡的遠程服務須要安裝一個負載均衡客戶端插件,以下所示。負載均衡客戶端插件用於將遠程服務註冊到負載均衡器,從而,負載均衡器能夠來管理遠程服務的負載狀況,當發生故障時能夠實現負載轉移和實現負載均衡。

clip_image018

這裏,遠程負載均衡器客戶端插件會鏈接到負載均衡服務器,向其註冊本機器的遠程服務。

2.2.3 負載均衡遠程服務調用

調用負載均衡遠程服務與直接調用遠程服務方法相似,以下圖所示。它使用GetFirstOrDefaultLoadBalancerService接口來訪問負載均衡器,獲取通過負載均衡的遠程服務。

clip_image020

你能夠發現,調用負載均衡遠程服務的方法也很是簡單。下面,我來介紹一下如何實現。

3 設計實現

首先,咱們先來看看遠程服務的實現。

3.1 遠程服務的實現

遠程服務的實現能夠歸結爲如下幾點:(1)遠程服務宿主插件用於暴露一個WCF服務,這個WCF服務至關於本地服務的Bridge,即客戶端對遠程服務的調用先中專到這個WCF服務,再由WCF服務來調用本地服務,而後返回給客戶端;(2)客戶端使用DynamicProxy爲服務契約生成一個代理,對這個代理的方法調用將會被攔截,而後調用遠程服務宿主插件的WCF服務,將調用結果再返回。

3.1.1 遠程服務宿主插件實現

該插件首先定義了一個IRemoteServiceInvoker的WCF服務接口,這個接口及參數的定義以下。

clip_image022

它的做用就是經過調用這個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服務,暴露了兩個接口分別用於對遠程服務和負載均衡遠程服務的調用。

clip_image024

該服務的遠程服務獲取實現以下所示。

clip_image026

它僅僅時經過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服務。這個服務用於提供遠程服務註冊和卸載以及負載均衡請求。

clip_image028

這個服務的實現以下所示。

clip_image030

它使用遠程服務註冊表來實現遠程服務的管理和負載均衡的實現。

3.2.2 負載均衡客戶端的實現

負載均衡客戶端的目的時實現遠程服務的註冊與卸載,經過該插件將遠程服務暴露到負載均衡服務器。這樣服務調用者就能夠經過負載均衡器來調用遠程服務。

clip_image032

3.2.3 負載均衡遠程服務調用

負載均衡遠程服務的調用方式的實現和遠程服務相似。它由遠程服務代理服務的GetFirstOrDefaultLoadBalancerService接口來實現。

clip_image034

該接口的實現以下所示,主要時建立代理和方法攔截機。

clip_image036

這個方法調用攔截機會將方法調用轉化爲:(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 小結

在這篇文章,我詳細介紹了支持集羣的遠程服務的實現。你能夠發現,總體實現徹底按照模塊化組裝的方式。你也能夠嘗試來考慮以模塊化組裝的方法實現一個遠程服務集羣。多謝支持~~。

相關文章
相關標籤/搜索