我不懂微服务:RPC远程调用

Posted 杨友山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我不懂微服务:RPC远程调用相关的知识,希望对你有一定的参考价值。

RPC(Remote Procedure Call)远程过程调用协议,简单来说是一个节点请求另一个节点提供的服务。RPC是伴随着分布式的出现的,因为分布式客户端和服务端部署在不同的机器上,所以需要远程调用。

一、基本模型
RPC基本组件有如下几个:
1、客户端
服务的调用方
2、客户端存根
存放服务端信息,包括地址信息,对象结构等等,用于对服务端的信息进行序列化和反序列化。
3、服务端存根
存放服务端信息,用户对客户端发送的信息序列化和反序列化,以及调用服务端本地的方法。
4、服务端
服务的提供者

通讯过程如下

二、具体实现
以上是rpc基本通讯模型。
下面我以C#语言为例,通过代码来实现远程调用,代码实现过程中有如下关键元素:
1、远程对象
运行在服务端的对象,不能在客户端本地直接使用。一般是通过代理调用这个远程对象。最终体现出来的是,调用时和使用本地对象一样。在C#中远程对象时继承自MarshalByRefObject类。
2、信道
Chanel,用于客户端与服务端通信的通道。.net中提供了TCP、Http、IPC通讯,本文中我们稍后会使用TCP和Http通讯。
3、消息
是通讯的内容,以及附属信息。比如包含远程对象信息,被调用的方法和参数等。
4、代理
客户端不能直接访问远程对象的成员和方法,只能通过代理来访问。使用代理,让客户端访问远程对象的过程看起来和访问本地对象一样。
5、激活器
服务端通过激活器,激活需要让客户端访问的对象,或者提供一个代理;
客户端通过激活器获取服务端的对象,或者获取一个被服务端激活的代理。

三、代码分析
接下来,边看代码变理解。
程序的目标:完成一个客户端向服务端发起的请求,服务端返回结果。客户端和服务端是两个完全独立的控制台程序,表示这两个端可以分开部署在不同的机器上。

3.1、定义远程对象
远程对象,一般定义在服务端。客户端需要通过代理调用远程对象的方法,经过信道通讯,客户端的请求到了服务端,服务端再到具体的处理逻辑中,处理完结果后再将结果一远程对象的结构返回给客户端。
定义一个独立类库:TestRpcEntity工程,其中有一个类叫Person类。代码如下:

   public class Person: MarshalByRefObject
    
        public Person()
        
            Console.WriteLine("远程对象构造函数被调用");
        

        public virtual string Greet(string name)
        
            return null;
        
    

为了演示结果更清晰,此处我定义了虚方法。服务端继承后重写这个虚方法。

3.2、服务端
服务端是服务的提供者,需要先注册通道,包括指定开放的端口。然后激活通道,包括在注册的通道中激活远程对象,以便让客户端可以访问端口,并取得代理。
一个控制台应用程序:TestRpcServer。需要注意,这里通过第三方引用dll方式,将TestRpcEntity引用进来。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;
using System.Text;
using System.Threading.Tasks;
using TestRpcEntity;

namespace TestRpcServer

    class Program
    
        static void Main(string[] args)
        
            //服务端注册通道(带指定端口)
            TcpChannel tcpChannel1 = new TcpChannel(8090);
            HttpChannel httpChannel1 = new HttpChannel(8091);

            //服务端注册通道
            ChannelServices.RegisterChannel(tcpChannel1, false);
            ChannelServices.RegisterChannel(httpChannel1, false);

            //服务端激活
            //注册激活对象方式一:有状态模式
            RemotingConfiguration.RegisterWellKnownServiceType(typeof(PersonServer), "MyFirstRpcServeObj", WellKnownObjectMode.Singleton);

            //注册激活对象方式二:无状态模式
            //RemotingConfiguration.RegisterWellKnownServiceType(typeof(PersonServer), "MyFirstRpcServeObj", WellKnownObjectMode.SingleCall);

            //客户端激活
            //RemotingConfiguration.ApplicationName = "MyFirstRpcServeObj";
            //RemotingConfiguration.RegisterActivatedServiceType(typeof(PersonServer));


            System.Console.WriteLine("按任意键退出");
            System.Console.ReadLine();
        
    

通道
这里用到了TcpChannel和HttpChannel。

激活方式
服务端激活
知道已注册的已知对象的 URI 的任何客户端都可以通过注册 ChannelServices所首选的通道,并通过调用 new 或 Activator.GetObject 方法来激活对象,从而获取该对象的代理。 若要使用 new激活已知对象,必须先使用 RegisterWellKnownClientType 方法在客户端上注册众所周知的对象类型。 调用 RegisterWellKnownClientType 方法将为远程处理基础结构提供远程对象的位置,这允许 new 关键字创建该对象。 另一方面,如果使用 Activator.GetObject 方法激活已知对象,则必须将该对象的 URL 作为参数提供,因此不需要在客户端上进行事先注册。
当调用到达服务器时,.NET Framework 从消息中提取 URI,检查远程处理表以找到与 URI 匹配的对象的引用,然后根据需要实例化对象(如有必要),将方法调用转发给对象。 如果将对象注册为 SingleCall,则在方法调用完成后将其销毁。 将为每个调用的方法创建对象的新实例。 Activator.GetObject 和 new 之间唯一的区别在于前者允许你指定一个 URL 作为参数,后者从配置中获取 URL。
注册过程不会实例化远程对象本身。 仅当客户端尝试调用对象上的方法或从客户端激活对象时,才会发生这种情况。

. Net Remoting把服务器端激活又分为SingleTon模式和SingleCall模式两种。
 SingleTon模式:为有状态模式。如果设置为SingleTon激活方式,则Remoting将为所有客户端建立同一个对象实例。当对象处于活动状态时, SingleTon实例会处理所有后来的客户端访问请求,而不管它们是同一个客户端,还是其他客户端。SingleTon实例将在方法调用中一直维持其状态。
  SingleCall模式:SingleCall是一种无状态模式。一旦设置为SingleCall模式,则当客户端调用远程对象的方法时, Remoting会为每一个客户端建立一个远程对象实例,至于对象实例的销毁则是由GC自动管理的。

客户端激活
若要在服务器上创建客户端激活的对象的实例,必须知道其 Type,并且必须通过使用 RegisterActivatedServiceType 方法在服务器端上注册它。 若要获取客户端激活对象的新实例的代理,客户端必须先向 ChannelServices 注册通道,然后通过调用 new 或 Activator.CreateInstance来激活该对象。
若要使用 new 关键字激活客户端激活的对象类型,必须先使用 RegisterActivatedClientType 方法在客户端上注册对象类型。 调用 RegisterActivatedClientType 方法将为远程处理基础结构提供远程应用程序的位置,new 尝试创建远程应用程序。 另一方面,如果使用 CreateInstance 方法来创建客户端激活对象的新实例,则必须将远程应用程序的 URL 作为参数提供,因此不需要在客户端上进行事先注册。 若要向 CreateInstance 方法提供要在其中创建对象的服务器的 URL,则必须将该 URL 封装在 UrlAttribute 类的实例中。

另外在服务端定义了一个PersonServer类,用于服务端逻辑处理。

namespace TestRpcServer

    public class PersonServer : Person
     
        public override string Greet(string name)
        
            Console.WriteLine("服务端的Greet方法被调用");
            return "你好 " + name;
        
    

3.3、客户端
客户端时服务的发起者,
也是一个控制台应用程序:TestRpcClient。需要注意,这里通过第三方引用dll方式,将TestRpcEntity引用进来了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;
using System.Text;
using System.Threading.Tasks;
using TestRpcEntity;

namespace TestRpcClient

    class Program
    
        static void Main(string[] args)
        
            //客户端注册通道
            TcpChannel tcpChannel = new TcpChannel();
            HttpChannel httpChannel = new HttpChannel();

            ChannelServices.RegisterChannel(tcpChannel,false);
            ChannelServices.RegisterChannel(httpChannel,false);

            //客户端激活
            //方式一:服务端激活
            Person clientRpcObj1 = (Person)Activator.GetObject
                (typeof(Person), "tcp://localhost:8090/MyFirstRpcServeObj");

            if (clientRpcObj1 == null)
            
                Console.WriteLine("连接到远程TCP服务器失败");
            
            else
             
                Console.WriteLine("调用Hello:0", clientRpcObj1.Greet("TCP通道"));
            

            Person clientRpcObj2 = (Person)Activator.GetObject
         (typeof(Person), "http://localhost:8091/MyFirstRpcServeObj");

            if (clientRpcObj2 == null)
            
                Console.WriteLine("连接到远程Http服务器失败");
            
            else
             
                Console.WriteLine("调用Hello:0", clientRpcObj2.Greet("Http通道"));
            

            //方式二:客户端激活
            //(二)客户端激活模式
            //方法一
            //RemotingConfiguration.RegisterActivatedClientType(typeof(Person), "tcp://localhost:8090/MyFirstRpcServeObj");
            //Person obj = new Person();
            //obj.Name = "TCP";
            //Console.Write("调用HelloMethod:0", obj.Hello());

            //RemotingConfiguration.RegisterActivatedClientType(typeof(Person), "http://localhost:8091/MyFirstRpcServeObj");
            //Person obj2 = new Person();
            //obj2.Name = "HTTP";
            //Console.Write("调用HelloMethod:0", obj2.Hello());

            //方法二
            //object[] atts =  new UrlAttribute("tcp://localhost:8090/MyFirstRpcServeObj") ;
            //object[] parm = new object[3]; //要传达的构造函数的参数个数
            //parm[0] = "d";
            //Person obj = (Person)Activator.CreateInstance(typeof(Person), parm, atts);

            Console.WriteLine("按任意键退出");
            Console.ReadLine();
        
    

通道
注册两种类型通道

激活
服务端有服务端和客户端两种激活方式,对应的在客户端中就有服务端激活和客户端激活的使用方式,具体自行看代码。它们的调用方法不同。它们的区别可以再继续看3.2中服务端激活和客户端激活的说明。

代理
客户端使用服务端激活方式为例,Activator.GetObject返回的是一个服务端的代理,用此代理访问远程对象。

四、程序演示
启动服务端,只有服务端启用了,激活了通道和远程对象,服务端才能获取到代理。


启动客户端
客户端中完成一系列动作:注册通道,注册通道,获取代理,调用方法。

服务端调用了两次,所以就有了两次打印结果。从结果看,说明客户端确实调用到了服务端的PersonServer类中的方法。

其他服务端的激活方式和对应的客户端调用方式,大家可以自行下载代码练习。

全部代码下载链接:https://download.csdn.net/download/yysyangyangyangshan/12760970

以上是关于我不懂微服务:RPC远程调用的主要内容,如果未能解决你的问题,请参考以下文章

我不懂微服务:http服务

我不懂微服务:http服务

7.Go语言高并发与微服务实战 --- 远程过程调用 RPC

深入讲解SpringCloud Alibaba 微服务调用,还不懂微服务的一定得看看!

我不懂微服务:TCP三次握手

我不懂微服务:TCP三次握手