[开源]KJFramework.ServiceModel 基于SOA模式的轻量级服务调用框架

框架介绍

 

      KJFramework框架中内置了一套轻量级RPC服务调用框架,使用此框架,将使得用户不再关心底层协议转换和网络传输的繁琐过程,解放程序员的脑力劳动。
此框架的使用也非常简单,作为服务器端的使用人员,只需要完成一个契约的实现,就可以通过配置不同的底层通信信道来达到不同通信模式需求。

在客户端,我们可以有2种方法来完成对于服务器端契约的使用:

1. 直接引用服务器端契约文件(dll文件)
2. 使用内部专门编写的VS插件(VS Add-in)来完成对于远程服务端契约的动态生成



当然,我们推荐使用第二种方式,因为使用VS插件可以帮助用户自动生成针对于每一个契约操作的异步版本。
目前此内置框架被初步定义在命名空间:KJFramework.ServiceModel内,拥有独立Assembly.
目前此框架中,仍保留了内部扩展能力,日后扩展后,将会通过配置文件来增强配置能力。

 

适用场景

 

      作为一个轻量级的SOA解决方案,对于此框架,我们建议的场景是内部的SERVER间服务调用。首先,对于外部公有式的解决方案,微软已经提出了WCF。

 当然,还有一个老古董级别的WebService.  所以,对于部门间的服务调用,WCF和WebService可以作为整合不同技术平台的一个公有接口。 

      我在SERVER SIDE的开发已经有很多年了,据我了解和熟知,几乎大多数的SERVER SIDE开发,内部的服务通讯都会有属于自己的一套框架,内部的服务调用

,如果采用SOA方式的话,也不会选用WCF,因为它确实太重了,而REMOTING服务存在内部的BUG,这个BUG是只有固定场景才会出现的,更何况这些解决方案的可驾驭型不强。 一个私有的SOA解决方案,对于内部服务的调用是具有积极效应的。于是,我自己设计

并完成了这套轻量级的SOA解决方案, 目前这套框架支持2种通讯方式,TCP, IPC(命名管道),日后会支持更多的通讯方式。

 

内部设计&使用体验

 

      这套框架,我遵从了类似于WCF技术的使用体验,因为这种设计体验会使得使用者以更少的成本去用它。首先,我们来举个例子,看一下服务器端对于服务契约的声明:

 

//以下为服务端契约接口和实现
[ServiceContract(ServiceConcurrentType = ServiceConcurrentTypes.Concurrent, Description="这是一个契约的描述", Name = "IHellowWorld 服务契约", Version = "0.0.0.1")]
public interface IHelloWorld
{
    [Operation(IsOneway = true)]
    void Increment();
    [Operation]
    int[] Hello(int[] text);
    [Operation]
    String Hello(int x, int y, string z);
    [Operation]
    string TestMultiPackage();
    [Operation]
    string TestIntellectObject(InObject inObject);
    [Operation]
    InObject3 TestReturnValue(string content);
}


//服务器端实现,具体方法的实现就不写了 :)
public class HelloWorld : IHelloWorld
{
    /*Some code here.*/
}

 

     从中,我们可以看到,每一个服务器端契约的操作,我们都为它打上了一个 OperationAttribute, 这个标签是至关重要的,它直接的标示了一个契约中的某些可用操作。

当然,一个OperationAttribute内部拥有很多属性,我们来介绍一下:

 

  • IsOneway(该属性直接标示了当前的操作是单向操作,也就是说,客户端不会接收到服务器端的请求回馈Response,这样的话,直接能够节省服务器端的通信开销)
  • IsAsync(该属性直接标示了当前的操作是异步的,由于我们的框架服务器端是不分异步操作或者同步操作的,所以此属性仅限于客户端使用)
  • MethodToken(契约操作编号,此属性更多的代表了远端服务契约中某个操作的唯一编号,请不用担心,一般此属性都会由我们内置的插件自动生成)

     

      好吧,我们再来看一下客户端拿到的服务器端契约接口会是个什么样子呢?

 

[ServiceContract(ServiceConcurrentType = ServiceConcurrentTypes.Singleton, Description="这是一个契约的描述", Name = "IHellowWorld 服务契约", Version = "0.0.0.1")]
public interface IHelloWorld
{
    [Operation(MethodToken = 10655301)]
    void Increment();
    [Operation(MethodToken = 100663310)]
    string TestMultiPackage();
    [Operation(MethodToken = 100663311)]
    string TestIntellectObject(InObject inObject);
    //可以看到,此方法是异步的,异步方法不会由服务器端声明,而是由客户端通过我们的插件生成的
    [Operation(IsAsync = true, MethodToken = 100663308)]
    void TestIntellectObjectAsync(InObject inObject, AsyncMethodCallback callback);
}

 

        来请注意,这里的AsyncMethodCallback是框架内部定义的,通常情况下,一个带参数的同步操作,在使用插件生成异步操作时,我们都会为其参数的末尾加入一个 AsyncMethodCallback。那么,我们接下来,看一下,服务器端的程序是如何发布一个属于自己的服务呢? 代码如下:

*
*服务端用来公开一个服务的代码
*从此代码可知,公开服务的通讯方式为TCP
*地址为本地的9999端口
*公开的服务全地址为: tcp://localhost:9999/Test
*/
ServiceHost serviceHost = new ServiceHost(typeof(HellowWorld), new TcpBinding("tcp://localhost:9999/Test"));
//如果设置此属性,则会让客户端从网页上直接预览此服务的契约
serviceHost.IsSupportExchange = true;
serviceHost.Opened += (sender, e) =>
{
    Console.WriteLine("Service has been opened.");
};
serviceHost.Open();

 

     再来看一下客户端调用服务器端指定契约操作的代码:

 

//IClientProxy<T>接口是一个动态的客户端代理接口
IClientProxy<IHelloWorld> proxy= new DefaultClientProxy<IHelloWorld>(new TcpBinding("tcp://localhost:9999/Test"));
InObject inObject = new InObject { Inf1 = 1, Info2 = DateTime.Now, Info = "Info", Info3 = new[] { "1", "2" }, Info4 = new[] { 3, 4 } };
//调用的使用使用proxy.Channel, 此对象的类型是服务器端公开的契约接口
//从这里看出,客户端调用的这个方法,是个异步版本,是VS插件自动生成的
proxy.Channel.TestIntellectObjectAsync(inObject, delegate(IAsyncCallResult callResult)
                {
                    if (callResult.IsSuccess) Interlocked.Increment(ref callbackcount);
                    else if (!callResult.IsSuccess && callResult.LastError != null) throw callResult.LastError;
                });

 

      可以看出什么吗? 哦!是的。 从客户端的调用代码来看,使用起来是多么的简单! 由于客户端使用的是一个服务器端契约的接口,所以对于方法的名称,

方法传入参数的数量,以及参数的强类型,编译器都会默认的进行支持和智能提示。 我们的框架,将会很智能的为服务器端契约接口中的每一个操作来编写动态的MSIL语言,

这样一来,我们的客户端就能直接使用服务器端的接口来进行调用了。

      如果,在调用期间,产生了任何*业务逻辑*上的错误,那么客户端是能够知晓的。 我们框架能够支持远程的异常回传,这样的话,当我们调用了一个契约操作,

但是却出错的时候,会有如下2种场景:

  • 对于同步操作的调用,会在该操作的调用处触发一个异常,该异常为远端所抛出的异常 
  • 对于异步操作的调用,会为callback函数内部IAsyncCallResult接口的LastError字段赋值,这样的话,我们需要每次在回调函数中检测IAsyncCallResult.IsSuccess字段

 

什么样子才算是好的使用体验?  

 

      为了让使用者,更好的使用我们的框架,我们特地为此框架设计了一个VS的插件,该插件的目的就是为了能够通过远端服务所发布的元数据,来自动的为客户端

生成远端服务的接口。 这个插件的界面看起来是这样的: 

      

 

      当然,此插件现在已经能够正常使用了,我会在日后慢慢的进行优化。 

 

发布元数据

 

      对于每一个RPC服务来说,我们框架内部都为其提供了开放元数据功能的支持。一个已开放的RPC服务,如果也开放了元数据,那么意味着,我们就可以使用

刚才提到的插件来生成这个远程服务的本地契约接口啦(当然,也包括传递的第三方类参数等等)。  让一个RPC服务开放其自身的元数据,是很简单的一件事情,我们仅仅需要设置一个属性即可。 就如下:

 

 ServiceHost.IsSupportExchange = true;

 

      如果一个RPC服务开放了元数据,那么我们就可以直接使用本地的浏览器进行远端RPC服务元数据的浏览啦。 效果如下:

     

 

        服务器端的运行截图如下:

 

 

      我们也可以查看每一个契约操作的具体细节:  

 

 

 

      页面的具体元素还没有做更多的细节处理,很多页面的Style都是照扒的,请放心,日后我会进行改进的 :)  

 

 

服务的调用形式  

 

  • 单例模式
  • 多线程模式(多服务实例)
  • 并发模式(多服务实例)

     目前只是完成了第一和第三个,第二个方式的调用正在开发中。

 

 

性能测试  

 

     我们已经为此框架做了初步的性能测试 这个测试的结果是: callback func 13K/s,也就是说,1万3千个请求 + 1万个3千个请求响应消息 = 1秒

当然,对于框架内部,我们也加入了很多的性能计数器,这样的话,可以以一种很直观的方式来查看当前服务的调用性能。 在 13K/s的性能压力上,

我们并没有看到内存有任何的波动,整体资源也使用正常,线程数量很稳定。 由于测试环境硬件的因素,所以这个结果,仅仅是取决于当时测试的环境而给出的,

这个值也会根据各位不同的测试环境而重新待定。

 

测试平台:
CPU: Intel(R)Xeon(R)CPU X5670 @2.93GHz @2.93GHz (2处理器)
System: Windows Server 2008 R2 Enterprise 

 

以下是测试代码,各位也能从源代码的ClientTest项目中得到该段代码:

 

public void Test1()
{
    LightTimer.NewTimer(1000, -1).Start(delegate
    {
        Console.Title = String.Format("Callbacks {0}", callbackcount);
    }, null);
    Console.WriteLine("开始执行......")
    IClientProxy<IHelloWorld> instance0 = new DefaultClientProxy<IHelloWorld>(new TcpBinding("tcp://localhost:9999/Test"));
    InObject inObject = new InObject { Inf1 = 1, Info2 = DateTime.Now, Info = "Info", Info3 = new[] { "1", "2" }, Info4 = new[] { 3, 4 } };
    CodeTimer.Initialize();
    CodeTimer.Time("Client Performane Test: 1000000", 1000000, delegate
    {
        instance0.Channel.TestIntellectObjectAsync(inObject, delegate(IAsyncCallResult callResult)
        {
            if (callResult.IsSuccess) Interlocked.Increment(ref callbackcount);
            else if (!callResult.IsSuccess && callResult.LastError != null) throw callResult.LastError;
        });
    });
    Console.ReadLine();
}

 

 

附:

    此框架目前已经能够完成基本需求了,但是距离商用级别还有待时日。我开源此项目也并不是为了能够将此项目商用,只是希望这个项目能够越来越好,

如果,看了上述的描述,您对此项目很感兴趣,不妨下载下来源代码研究下,又或者是来加入我们的这个项目,一起改进。
   

    在日后,我会为此框架提供越来越多的能力,当然,性能上面也会越来越好。很多的新功能,我也正在策划中,争取能以最快的速度完成。

很感谢各位耐心的看完这篇很长很长的文章。



项目地址: http://servicemodel.codeplex.com/


目前此项目并没有发布二进制的文件,因为我感觉此框架还没有达到初步商用的级别。 但是,仍然可以下载源代码供各位研究。

如果在尝试此框架的途中,有任何问题,您也可以选择与我联系,我将提供最优的技术支持。

QQ:250623008

Email: Kevin.Jee@live.cn







谢谢.       

posted @ 2011-11-18 09:46  Kevin.Jee  阅读(1254)  评论(3编辑  收藏  举报