在这篇文章我分享了如何使用分层与模块化的方法来设计一个分布式服务集群。这个分布式服务集群是基于DynamicProxy、WCF和OSGi.NET插件框架实现的。我将从设计思路、目标和实现三方面来描述。

1 设计思路

首先,我来说明一下设计思路。我们先来看看目前OSGi.NET插件框架的服务。在这里,服务不是远程服务,它是轻量级的服务,由接口和实现类组成,如下图所示。服务契约插件定义了服务接口,服务实现插件向服务总线注册服务,服务调用插件利用服务契约(接口)从服务总线获取实现的服务并调用,服务实现插件和服务调用插件都依赖于服务契约,但二者并未有依赖。服务是插件间松耦合的调用方式。

我们希望在不更改现有的通讯机制的情况下,将以前定义的在同一个框架下的服务能够直接不更改代码情况下,变成远程服务。此时,基于远程服务的通讯方式变成如下图所示的方式。

这时候,在不更改服务定义、服务注册的代码下,在OSGi.NET框架中安装一个远程服务宿主插件,它直接将服务总线的服务暴露成远程服务;OSGi.NET插件框架安装一个远程服务客户端插件,就可以使用服务契约来获取并调用远程服务。

接下来,我们希望能更进一步在不需要更改服务定义和注册代码情况下,来实现透明的集群支持。

在这里,我们引入了负载均衡插件。首先,一个OSGi.NET插件框架安装了远程服务负载均衡插件,它管理所有远程服务的负载状况,并为集群提供了统一的访问和负载均衡支持;接着,所有安装了远程服务宿主插件的OSGi.NET框架,会安装一个负载均衡客户端插件,它用于将远程服务注册到负载均衡器;服务调用端安装了远程服务客户端插件,它通过负载均衡器来调用远程服务。

这个思路可以简单描述如下:

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的调用转换成对本地服务方法的调用并返回。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Threading.Tasks;
using fastJSON;
using Fasterflect;
using UIShell.OSGi.Utility; namespace UIShell.RemoteServiceHostPlugin
{
public class RemoteServiceInvoker : IRemoteServiceInvoker
{
public static ReaderWriterLock Locker = new ReaderWriterLock();
public static Dictionary<string, System.Tuple<MethodInfo, Type[], MethodInvoker>> InvokerCache = new Dictionary<string, System.Tuple<MethodInfo, Type[], MethodInvoker>> (); public string InvokeService(RemoteServiceInvocation invocation)
{
AssertUtility.NotNull(invocation);
AssertUtility.ArgumentHasText(invocation.ContractName, "service contract name");
AssertUtility.ArgumentHasText(invocation.MethodName, "service method name"); var service = Activator.Context.GetFirstOrDefaultService(invocation.ContractName);
string msg = string.Empty;
if(service == null)
{
msg = string.Format ("Remote Service '{0}' not found.", invocation.ContractName);
FileLogUtility.Warn (msg);
throw new Exception(msg);
} System.Tuple<MethodInfo, Type[], MethodInvoker> invokerTuple;
using (var locker = ReaderWriterLockHelper.CreateReaderLock (Locker))
{
InvokerCache.TryGetValue (invocation.Key, out invokerTuple);
} if (invokerTuple == null)
{
Type serviceType = service.GetType (); var serviceMethodInfo = serviceType.GetMethod (invocation.MethodName);
if (serviceMethodInfo == null)
{
msg = string.Format ("The method '{1}' of the remote service '{0}' not found.", invocation.ContractName, invocation.MethodName);
FileLogUtility.Warn (msg);
throw new Exception (msg);
} if (invocation.JsonSerializedParameters == null) {
invocation.JsonSerializedParameters = new List<string> ();
} var parameterInfos = serviceMethodInfo.GetParameters ();
if (invocation.JsonSerializedParameters.Count != parameterInfos.Length)
{
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);
FileLogUtility.Warn (msg);
throw new Exception (msg);
} var parameterTypes = new Type[parameterInfos.Length];
for (int i = ; i < parameterInfos.Length; i++)
{
parameterTypes [i] = parameterInfos [i].ParameterType;
} try
{
var methodInvoker = serviceType.DelegateForCallMethod (invocation.MethodName, parameterTypes); invokerTuple = new System.Tuple<MethodInfo, Type[], MethodInvoker> (serviceMethodInfo, parameterTypes, methodInvoker); using (var locker = ReaderWriterLockHelper.CreateWriterLock (Locker))
{
if (!InvokerCache.ContainsKey (invocation.Key))
{
InvokerCache [invocation.Key] = invokerTuple;
}
}
}
catch(Exception ex)
{
msg = string.Format ("Failed to create delegate method for the method '{0}' of service '{1}'.", invocation.MethodName, invocation.ContractName);
FileLogUtility.Warn (msg);
FileLogUtility.Warn (ex);
throw new Exception (msg, ex);
}
} var paramters = new object[invokerTuple.Item2.Length]; for(int i = ; i < invokerTuple.Item2.Length; i++)
{
try
{
paramters[i] = JSON.ToObject(invocation.JsonSerializedParameters[i], invokerTuple.Item2[i]);
}
catch(Exception ex)
{
msg = string.Format ("Failed to unserialize the '{0}'th parameter for the method '{1}' of service '{2}'.", i + , invocation.MethodName, invocation.ContractName);
FileLogUtility.Warn (msg);
FileLogUtility.Warn (ex);
throw new Exception (msg, ex);
}
} try
{
return JSON.ToJSON(invokerTuple.Item3(service, paramters));
}
catch(Exception ex)
{
msg = string.Format("Failed to invoke the method '{0}' of service '{1}'.", invocation.MethodName, invocation.ContractName);
FileLogUtility.Warn (msg);
FileLogUtility.Warn (ex);
throw new Exception (msg, ex);
}
}
}
}

3.1.2 远程服务客户端插件的实现

接下来,我们看看远程服务客户端插件的实现。它定义了一个IRemoteServiceProxyService服务,暴露了两个接口分别用于对远程服务和负载均衡远程服务的调用。

该服务的远程服务获取实现如下所示。

它仅仅时通过DynamicProxy创建远程服务代理类,此时,对代理类方法的调用会转换成远程服务调用。下面看看拦截机的实现。

 class RemoteServiceProxyInterceptor : IInterceptor, IDisposable
{
private RemoteServiceContext _remoteServiceContext;
private RemoteServiceClient _remoteServiceClient;
public RemoteServiceProxyInterceptor(RemoteServiceContext context)
{
_remoteServiceContext = context;
_remoteServiceClient = new RemoteServiceClient(_remoteServiceContext.IPAddress, _remoteServiceContext.Port);
_remoteServiceClient.Start();
}
public void Intercept(IInvocation invocation)
{
try
{
var jsonParameters = new List<string>();
foreach (var param in invocation.Arguments)
{
jsonParameters.Add(JSON.ToJSON(param));
} var resultJson = _remoteServiceClient.Invoke(invocation.Method.DeclaringType.FullName, invocation.Method.Name, jsonParameters); if (!invocation.Method.ReturnType.FullName.Equals("System.Void"))
{
invocation.ReturnValue = JSON.ToObject(resultJson, invocation.Method.ReturnType);
}
else
{
invocation.ReturnValue = null;
}
}
catch(Exception ex)
{
FileLogUtility.Error (string.Format("Failed to invoke the remote service 'Remote Service: {0}, Method: {1}.'",
invocation.Method.DeclaringType.FullName, invocation.Method.Name));
throw;
}
} public void Dispose()
{
if (_remoteServiceClient != null)
{
_remoteServiceClient.Stop();
_remoteServiceClient = null;
}
}
}

拦截机的代码很简单,对远程服务代理类方法的调用将直接转换成对远程服务宿主插件的WCF服务的调用。下面看看负载均衡远程服务的实现。

3.2 负载均衡远程服务的实现

有了以上的技术,关于负载均衡远程服务的实现,就简单多了。负载均衡远程服务的目的就是将所有远程服务统一在负载均衡服务器进行注册,并实现负载的动态管理。因此,需要在远程服务基础上,创建一个负载均衡服务器和负载均衡客户端。负载均衡服务器用于管理所有远程服务及提供远程服务的机器,管理所有远程服务的负载情况,并实现负载均衡及故障转移;负载均衡客户端的目的时将远程服务注册到负载均衡器,并且当远程服务关闭时从负载均衡器卸载。下面,看看负载均衡器的实现。

3.2.1 负载均衡器实现

负载均衡服务器暴露了如下WCF服务。这个服务用于提供远程服务注册和卸载以及负载均衡请求。

这个服务的实现如下所示。

它使用远程服务注册表来实现远程服务的管理和负载均衡的实现。

3.2.2 负载均衡客户端的实现

负载均衡客户端的目的时实现远程服务的注册与卸载,通过该插件将远程服务暴露到负载均衡服务器。这样服务调用者就可以通过负载均衡器来调用远程服务。

3.2.3 负载均衡远程服务调用

负载均衡远程服务的调用方式的实现和远程服务类似。它由远程服务代理服务的GetFirstOrDefaultLoadBalancerService接口来实现。

该接口的实现如下所示,主要时创建代理和方法拦截机。

这个方法调用拦截机会将方法调用转化为:(1)从负载均衡服务器获取均衡的远程服务主机;(2)直接调用该远程服务主机的服务,如果调用失败则尝试进行重新负载均衡。其实现如下所示。

 class RemoteServiceLoadBalancerProxyInterceptor : IInterceptor, IDisposable
{
private string LoadBalancerHost
{
get
{
return ConfigurationSettings.AppSettings["LoadBalancerHost"];
}
} private string LoadBalancerPort
{
get
{
return ConfigurationSettings.AppSettings["LoadBalancerPort"];
}
} private LoadBalancerContext _remoteServiceLoadBalancerContext;
private RemoteServiceClient _remoteServiceClient;
private RemoteServiceLoadBalancerAccessClient _remoteServiceLoadBalancerAccessClient;
private bool _initialized; public RemoteServiceLoadBalancerProxyInterceptor(LoadBalancerContext context)
{
_remoteServiceLoadBalancerContext = context; if (string.IsNullOrEmpty(LoadBalancerHost))
{
throw new Exception("You need to specified the load balancer host (HostName or IP Address) by app setting 'LoadBalancerHost'.");
} int loadBalancerPortInt;
if (!int.TryParse(LoadBalancerPort, out loadBalancerPortInt))
{
throw new Exception("You need to specified the load balancer port by app setting 'LoadBalancerPort'.");
} try
{
_remoteServiceLoadBalancerAccessClient = new RemoteServiceLoadBalancerAccessClient (LoadBalancerHost, loadBalancerPortInt);
_remoteServiceLoadBalancerAccessClient.Start ();
}
catch(Exception ex)
{
FileLogUtility.Error (string.Format("Faild to connect to load balancer '{0}'.", _remoteServiceLoadBalancerContext));
FileLogUtility.Error (ex);
throw;
}
} private bool Initialize(string serviceContractName)
{
if(_remoteServiceClient != null)
{
_remoteServiceClient.Stop();
} RemoteServiceHost remoteHost = null;
try
{
remoteHost = _remoteServiceLoadBalancerAccessClient.Balance(serviceContractName);
FileLogUtility.Inform(string.Format("Get the remote service host '{0}' by load balancer '{1}'.", remoteHost, _remoteServiceLoadBalancerContext));
}
catch(Exception ex)
{
FileLogUtility.Error (string.Format("Faild to get a remote service host by load balancer '{0}'.", _remoteServiceLoadBalancerContext));
FileLogUtility.Error (ex);
return false;
}
if (remoteHost != null)
{
_remoteServiceClient = new RemoteServiceClient (remoteHost.IPAddress, remoteHost.Port);
try
{
_remoteServiceClient.Start ();
return true;
}
catch(Exception ex)
{
FileLogUtility.Error (string.Format("Failed to connect to the remote service host '{0}' by using load balancer '{1}'.", remoteHost, _remoteServiceLoadBalancerContext));
}
} return false;
} public void Intercept(IInvocation invocation)
{
var serviceContractName = invocation.Method.DeclaringType.FullName;
if (!_initialized)
{
_initialized = Initialize (serviceContractName);
if (!_initialized)
{
invocation.ReturnValue = null;
return;
}
} var jsonParameters = new List<string>();
foreach (var param in invocation.Arguments)
{
jsonParameters.Add(JSON.ToJSON(param));
} int tryTimes = ; for (int i = ; i < tryTimes; i ++ )
{
try
{
var resultJson = _remoteServiceClient.Invoke(serviceContractName, invocation.Method.Name, jsonParameters); if (!invocation.Method.ReturnType.FullName.Equals("System.Void"))
{
invocation.ReturnValue = JSON.ToObject(resultJson, invocation.Method.ReturnType);
}
else
{
invocation.ReturnValue = null;
}
return;
}
catch(Exception ex)
{
FileLogUtility.Error (string.Format("Failed to invoke the remote service 'Remote Service: {0}, Method: {1}.'",
serviceContractName, invocation.Method.Name));
FileLogUtility.Error (ex);
if (i == tryTimes)
{
throw;
}
if (!((_initialized = Initialize (serviceContractName)) == true)) // 重新Balance
{
throw;
}
}
}
} public void Dispose()
{
if (_remoteServiceClient != null)
{
_remoteServiceClient.Stop();
_remoteServiceClient = null;
}
}
}

4 小结

在这篇文章,我详细介绍了支持集群的远程服务的实现。你可以发现,整体实现完全按照模块化组装的方式。你也可以尝试来考虑以模块化组装的方法实现一个远程服务集群。多谢支持~~。

分享在Linux下使用OSGi.NET插件框架快速实现一个分布式服务集群的方法的更多相关文章

  1. Linux下查看内核、CPU、内存及各组件版本的命令和方法

    Linux下查看内核.CPU.内存及各组件版本的命令和方法 Linux查看内核版本: uname -a                        more /etc/*release       ...

  2. 高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群

    高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群 libnet软件包<-依赖-heartbeat(包含ldirectord插件(需要perl-MailTools的rpm包)) l ...

  3. .net core下简单构建高可用服务集群

    一说到集群服务相信对普通开发者来说肯定想到很复杂的事情,如zeekeeper ,反向代理服务网关等一系列的搭建和配置等等:总得来说需要有一定经验和规划的团队才能应用起来.在这文章里你能看到在.net ...

  4. rabbitmq集群搭建方法简介(测试机linux centos)【转】

    本文将介绍四台机器搭建rabbitmq集群: rabbitmq IP和主机名(每台机器已安装RabbitMQ 3.5.6, Erlang 18.1) 192.168.87.73 localhost73 ...

  5. 分享:linux下apache服务器的配置和管理

    linux下apache服务器的配置和管理. 一.两个重要目录: Apache有两个重要的目录:1.配置目录/etc/httpd/conf:2.文档目录/var/www: 二.两种配置模式: Apac ...

  6. elasticsearch插件安装之--linux下安装及head插件

    /** * 系统环境: vm12 下的centos 7.2 * 当前安装版本: elasticsearch-2.4.0.tar.gz */ 安装和学习可参照官方文档: 1, 安装 # 下载, 获取不成 ...

  7. linux下 tar解压 gz解压 bz2等各种解压文件使用方法

    http://alex09.iteye.com/blog/647128 大致总结了一下linux下各种格式的压缩包的压缩.解压方法. .tar 解包:tar xvf FileName.tar 打包:t ...

  8. 转Linux 下用alias 设置命令别名快速切换常用命令

    https://blog.csdn.net/u012830148/article/details/80618616 在linux下开发,经常需要切换目录,如果目录很长则切换起来非常的麻烦,针对一些常用 ...

  9. Linux下使用Vi是方向键变乱码 退格键不能使用的解决方法

    在Linux下编辑一些文件.这就涉及到了vi这个编辑器了.在Linux下,初始使用vi的时候有点问题.就是在编辑模式下使用方向键的时候,并不会使光标移动,而是在命令行中出现[A [B [C [D之类的 ...

随机推荐

  1. Android Studio配置 AndroidAnnotations——Hi_博客 Android App 开发笔记

    以前用Eclicps 用习惯了现在 想学学 用Android Studio 两天的钻研终于 在我电脑上装了一个Android Studio 并完成了AndroidAnnotations 的配置. An ...

  2. FREERTOS 手册阅读笔记

    郑重声明,版权所有! 转载需说明. FREERTOS堆栈大小的单位是word,不是byte. 根据处理器架构优化系统的任务优先级不能超过32,If the architecture optimized ...

  3. MVC Core 网站开发(Ninesky) 2.1、栏目的前台显示

    上次创建了栏目模型,这次主要做栏目的前台显示.涉及到数据存储层.业务逻辑层和Web层.用到了迁移,更新数据库和注入的一些内容. 一.添加数据存储层 1.添加Ninesky.DataLibrary(与上 ...

  4. 转:serialVersionUID作用

    汗,以前学了还忘了... Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的.在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本 ...

  5. zookeeper源码分析之六session机制

    zookeeper中session意味着一个物理连接,客户端连接服务器成功之后,会发送一个连接型请求,此时就会有session 产生. session由sessionTracker产生的,sessio ...

  6. C#中将DataTable导出为HTML的方法

    今天我要向大家分享一种将DataTable导出为到HTML格式的方法.有时我们需要HTML格式的输出数据, 以下代码就可以帮助我们达到目的,. 首先,我们要绑定DataTable和 DataGridV ...

  7. windows环境redis主从安装部署

    准备工作 下载windows环境redis,我下载的是2.4.5,解压,拷贝一主(master)两从(slaveof).主机端口使用6379,两从的端口分别为6380和6381, 我本地索性用6379 ...

  8. SharePoint 2016 入门视频教程

    之前一直有朋友让自己录一些SharePoint的入门视频,之前没有太多时间,一个巧合的机会收到CSDN学院的邮件,可以在CSDN上发布视频教程,自己就录了一些.说起录视频也是蛮辛苦的,每天下班吃完饭要 ...

  9. RMS去除在线认证

    在微软 OS 平台创建打开 RMS 文档如何避免时延 相信我们在企业内部的环境中已经部署了微软最新的OS平台,Windows 7和Windows 2008 R2,在这些OS平台上使用IRM功能时,您有 ...

  10. DB2重启数据库实例

    DB2重启数据库实例时,有时停止实例会失败,此时需要先确认没有应用链接数据库,然后再关闭数据库实例,并重新启动. 1.查看是否有活动的链接 命令:db2 list applications for d ...