Dubbo的远程过程调用和自定义RPC框架

Dubbo的介绍

Apache Dubbo是一款高性能的Java RPC框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。

Dubbo提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

官网:http://dubbo.apache.org/

什么是RPC?

RPC全称为remote procedure call,即远程过程调用。比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上的应用提供的方法,由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。

需要注意的是RPC并不是一个具体的技术,而是指整个网络远程调用过程。

而不需要了解底层网络技术的协议,在面向对象的编程语言中,远程过程调用即是 远程方法调用

Dubbo的远程过程调用和自定义RPC框架_第1张图片

RPC调用过程

Dubbo的远程过程调用和自定义RPC框架_第2张图片

java中RPC框架比较多,常见的有RMI、Hessian、gRPC、bRPC、Dubbo等,其实对于RPC框架而言,核心模块就是通讯和序列化接下来我们就分别看一下常见的RPC框架

RMI

1)RMI(remote method invocation)是java原生支持的远程调用,RMI采用JRMP(Java RemoteMessageing Protocol)作为通信协议,可以认为是纯java版本的分布式远程调用解决方案。

2)RMI的核心概念

Dubbo的远程过程调用和自定义RPC框架_第3张图片

3)RMI步骤

  1. 创建远程接口, 并且继承java.rmi.Remote接口
  2. 实现远程接口,并且继承:UnicastRemoteObject
  3. 创建服务器程序: createRegistry()方法注册远程对象
  4. 创建客户端程序 (获取注册信息,调用接口方法)

服务器:

1.创建服务接口

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface UserService extends Remote {
    String sayHello(String name) throws RemoteException;
}

2.提供接口的实现类

import Jackie.service.UserService;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class UserServiceImpl extends UnicastRemoteObject implements UserService
{
    public UserServiceImpl() throws RemoteException {
    }

    @Override
    public String sayHello(String name) throws RemoteException{
        return "成功调用了服务端的服务"+name;
    }
}

3.将本地服务暴露出去,供外部调用

import Jackie.service.UserService;
import Jackie.service.impl.UserServiceImpl;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class ServerMain {
    public static void main(String[] args) {
        try {
            //1.启动RMI注册服务,指定端口号
            LocateRegistry.createRegistry(5184);
            //2.创建要被访问的远程对象的实例
            UserService userService = new UserServiceImpl();
            3.暴露服务,把远程对象实例注册到RMI注册服务器上
            Naming.bind("rmi://localhost:5184/UserService", userService);
            System.out.println("服务器启动中。。。。");
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

客户端:

创建服务接口

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface UserService extends Remote {
    String sayHello(String name) throws RemoteException;
}

客户端远程调用服务,客户端需要依赖服务接口

import Jackie.service.UserService;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class ClientMain {
    public static void main(String[] args) {
        UserService userService = null;
        try {
            userService = (UserService) Naming.lookup("rmi://localhost:5184/UserService");
            //userService代理对象
            System.out.println(userService);
            System.out.println(userService.sayHello("客户端"));
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

Hessian

Hessian使用C/S方式,基于HTTP协议传输,使用Hessian二进制序列化。

server端:

1.添加hessian的maven依赖


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>DubboartifactId>
        <groupId>Jackie.demogroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>
    <packaging>warpackaging>

    <artifactId>Hessian_ServerartifactId>

    <dependencies>
        <dependency>
            <groupId>com.cauchogroupId>
            <artifactId>hessianartifactId>
            <version>4.0.7version>
        dependency>
    dependencies>

2.创建跟server端相同的接口UserService

public interface UserService {
    String sayHello(String name);
}

3.实现类

import Jackie.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public String sayHello(String name) {
        return "调用了hessian服务端的服务" + name;
    }
}

4.web.xml中配置HessianServlet


<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>HessianServletservlet-name>
        <servlet-class>com.caucho.hessian.server.HessianServletservlet-class>
        <init-param>
            <param-name>service-classparam-name>
            <param-value>Jackie.service.impl.UserServiceImplparam-value>
        init-param>
    servlet>
    <servlet-mapping>
        <servlet-name>HessianServletservlet-name>
        <url-pattern>/hessianServleturl-pattern>
    servlet-mapping>
web-app>

5.添加tomcat7插件启动服务


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>DubboartifactId>
        <groupId>Jackie.demogroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>
    <packaging>warpackaging>

    <artifactId>Hessian_ServerartifactId>

    <dependencies>
        <dependency>
            <groupId>com.cauchogroupId>
            <artifactId>hessianartifactId>
            <version>4.0.7version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.mavengroupId>
                <artifactId>tomcat7-maven-pluginartifactId>
                <version>2.2version>
                <configuration>
                    <port>5184port>
                    <path>/path>
                    <uriEncoding>UTF-8uriEncoding>
                configuration>
            plugin>
        plugins>
    build>
project>

客户端:

1.添加hessian的maven依赖


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>DubboartifactId>
        <groupId>Jackie.demogroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>Hessian_ClientartifactId>

    <dependencies>
        <dependency>
            <groupId>com.cauchogroupId>
            <artifactId>hessianartifactId>
            <version>4.0.7version>
        dependency>
    dependencies>
project>

2.创建跟server端相同的接口UserService

public interface UserService {
    String sayHello(String name);
}

3.创建测试类测试

import Jackie.service.UserService;
import com.caucho.hessian.client.HessianProxyFactory;

import java.net.MalformedURLException;

public class ClientTest {
    public static void main(String[] args) throws MalformedURLException {
        String url = "http://localhost:5184/hessianServlet";
        HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();
        UserService userService = (UserService) hessianProxyFactory.create(UserService.class, url);
        System.out.println(userService.sayHello("hessian客户端"));
    }
}

Thrift:FaceBook开源RPC框架,典型的CS架构,支持跨语言,客户端和服务端可以使用不同的
语言开发,thrift通过IDL(Interface Description Language)来关联客户端和服务端。
gRPC google
dubbo

2、手写RPC框架

基本实现思路:Dubbo的远程过程调用和自定义RPC框架_第4张图片

provider服务提供

consumer服务消费

registry注册

protocol协议

服务提供者:

1、定义服务接口

接口HelloService

public interface HelloService {
    String sayHello(String message);
}

2、实现类HelloServiceImpl

import Jackie.service.HelloService;

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return name+ "调用了myRPC的服务";
    }
}

3、服务注册:注册中心

此处注册中心我们将服务注册在map集合中,结构:Map>

外边map的key存储服务接口的全类名;

URL封装了调用服务的ip和port,里边value指定指定具体实现类;
注册中心类提供注册服务并暴露服务和发现服务功能:

public class URL {

    private String hostname;
    private Integer port;

    public URL() {
    }

    public URL(String hostname, Integer port) {
        this.hostname = hostname;
        this.port = port;
    }

    /**
     * 获取
     * @return hostname
     */
    public String getHostname() {
        return hostname;
    }

    /**
     * 设置
     * @param hostname
     */
    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    /**
     * 获取
     * @return port
     */
    public Integer getPort() {
        return port;
    }

    /**
     * 设置
     * @param port
     */
    public void setPort(Integer port) {
        this.port = port;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj==null){
            return false;
        }
        if(!(obj instanceof  URL)){
            return false;
        }
        URL url = (URL) obj;
        if(hostname.equals(((URL) obj).getHostname())  && port.intValue() == url.port.intValue()){
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return hostname.hashCode();
    }
}
import Jackie.pojo.URL;

import java.util.HashMap;
import java.util.Map;

public class NativeRegistry {

    //注册中心 map
    private static Map<String, Map<URL, Class>> registCenter = new HashMap<>();

    /**
     * 注册服务
     *
     * @param url
     * @param interfaceName
     * @param implClass
     */
    public static void regist(String interfaceName, URL url, Class implClass) {
        Map<URL, Class> map = new HashMap<>();
        map.put(url, implClass);
        registCenter.put(interfaceName, map);
    }

    /**
     * 从注册中心获取服务
     *
     * @param url
     * @param interfaceName
     * @return
     */
    public static Class get(String interfaceName, URL url) {
        return registCenter.get(interfaceName).get(url);
    }
}

注册服务

import Jackie.Tomcat.HttpServer;
import Jackie.pojo.URL;
import Jackie.registry.NativeRegistry;
import Jackie.service.HelloService;
import Jackie.service.impl.HelloServiceImpl;


public class ServiceProvider {
    public static void main(String[] args) {

        //创建URL
        URL url = new URL("localhost", 8080);

        //注册中心中注册服务
        NativeRegistry.regist(HelloService.class.getName(), url, HelloServiceImpl.class);

        //启动tomcat,并暴露服务
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostname(),url.getPort());
    }
}

4、暴露服务

服务之间调用的通信协议采用http协议,所以在服务provider中启动tomcat暴露服务

添加内嵌tomcat的依赖

 
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>DubboartifactId>
        <groupId>Jackie.demogroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>providerartifactId>

    
    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embedgroupId>
            <artifactId>tomcat-embed-coreartifactId>
            <version>9.0.12version>
        dependency>

        <dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
            <version>2.6version>
        dependency>
    dependencies>
project>

创建HttpServer

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;

public class HttpServer {

    /**
     * tomcat服务启动
     * 参考tomcat配置
     * 
     * 	
     * 		
     * 		
     * 			
     *     		 	
     *      		
     *      
     *   
     * 
     */

    /**
     * 启动服务
     * @param hostname
     * @param port
     */
    public void start(String hostname,int port){
        // 实例一个tomcat
        Tomcat tomcat = new Tomcat();

        // 构建server
        Server server = tomcat.getServer();

        // 获取service
        Service service = server.findService("Tomcat");

        // 构建Connector
        Connector connector = new Connector();
        connector.setPort(port);
        connector.setURIEncoding("UTF-8");

        // 构建Engine
        Engine engine = new StandardEngine();
        engine.setDefaultHost(hostname);

        // 构建Host
        Host host = new StandardHost();
        host.setName(hostname);

        // 构建Context
        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());// 生命周期监听器

        // 然后按照server.xml,一层层把子节点添加到父节点
        host.addChild(context);
        engine.addChild(host);
        service.setContainer(engine);
        service.addConnector(connector);
        // service在getServer时就被添加到server节点了

        // tomcat是一个servlet,设置路径与映射
        tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet());
        context.addServletMappingDecoded("/*","dispatcher");

        try {
            tomcat.start();// 启动tomcat
            tomcat.getServer().await();// 接受请求
        }catch (LifecycleException e){
            e.printStackTrace();
        }
    }
}

DispatcherServlet

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class DispatcherServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //HttpServerHandler真正处理远程调用请求
        new HttpServerHandler().handle(req,resp);
    }
}

HttpServerHandler处理远程调用请求

import Jackie.pojo.Invocation;
import Jackie.pojo.URL;
import Jackie.registry.NativeRegistry;
import org.apache.commons.io.IOUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class HttpServerHandler {

    /**
     *  服务的处理
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void handle(HttpServletRequest req, HttpServletResponse resp){
        try {
            //服务请求的处理逻辑
            //1 通过请求流获取请求服务调用的参数
            InputStream inputStream = req.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);

            Invocation invocation = (Invocation) objectInputStream.readObject();

            //2 从注册中心获取服务的列表
            Class implCass = NativeRegistry.get(invocation.getInterfaceName(),new URL("localhost", 8080));

            //3 调用服务 反射
            Method method = implCass.getMethod(invocation.getMethodName(),invocation.getParamTypes());

            String result = (String) method.invoke(implCass.newInstance(), invocation.getParams());

            //4 结果返回
            IOUtils.write(result,resp.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

封装调用参数Invocation

import java.io.Serializable;

public class Invocation implements Serializable {

    /**
     * 接口名
     */
    private String interfaceName;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 参数值列表
     */
    private Object[] params;
    /**
     * 参数类型列表
     */
    private Class[] paramTypes;

    public Invocation() {
    }

    public Invocation(String interfaceName, String methodName, Object[] params, Class[] paramTypes) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.params = params;
        this.paramTypes = paramTypes;
    }

    /**
     * 获取
     * @return interfaceName
     */
    public String getInterfaceName() {
        return interfaceName;
    }

    /**
     * 设置
     * @param interfaceName
     */
    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    /**
     * 获取
     * @return methodName
     */
    public String getMethodName() {
        return methodName;
    }

    /**
     * 设置
     * @param methodName
     */
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    /**
     * 获取
     * @return params
     */
    public Object[] getParams() {
        return params;
    }

    /**
     * 设置
     * @param params
     */
    public void setParams(Object[] params) {
        this.params = params;
    }

    /**
     * 获取
     * @return paramTypes
     */
    public Class[] getParamTypes() {
        return paramTypes;
    }

    /**
     * 设置
     * @param paramTypes
     */
    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }
}

启动服务

import Jackie.Tomcat.HttpServer;
import Jackie.pojo.URL;
import Jackie.registry.NativeRegistry;
import Jackie.service.HelloService;
import Jackie.service.impl.HelloServiceImpl;

public class ServiceProvider {
    public static void main(String[] args) {

        //创建URL
        URL url = new URL("localhost", 8080);

        //注册中心中注册服务
        NativeRegistry.regist(HelloService.class.getName(), url, HelloServiceImpl.class);

        //启动tomcat,并暴露服务
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostname(),url.getPort());
    }
}

4、consumer服务消费端

添加commons-io的依赖


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>DubboartifactId>
        <groupId>Jackie.demogroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>consumerartifactId>

    <dependencies>
        <dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
            <version>2.6version>
        dependency>
    dependencies>
project>

封装HttpClient对象,发起远程调用j

import Jackie.pojo.Invocation;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpClient {

    /**
     * 远程方法调用
     * @param hostname :远程主机名
     * @param port :远程端口号
     * @param invocation :封装远程调用的信息
     */
    public String post(String hostname, int port, Invocation invocation) {

        try {
            //进行连接
            URL url = new URL("http", hostname, port, "/client");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);// 必填项

            //发送调用的信息
            OutputStream outputStream = connection.getOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(invocation);
            objectOutputStream.flush();
            objectOutputStream.close();

            // 将输入流转为字符串(此处可是java对象) 获取远程调用的结果
            InputStream inputStream = connection.getInputStream();
            return IOUtils.toString(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

创建跟provider端相同的接口HelloService

public interface HelloService {
    String sayHello(String message);
}

创建跟provider端相同的pojo

import java.io.Serializable;

public class Invocation implements Serializable {

    /**
     * 接口名
     */
    private String interfaceName;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 参数值列表
     */
    private Object[] params;
    /**
     * 参数类型列表
     */
    private Class[] paramTypes;

    public Invocation() {
    }

    public Invocation(String interfaceName, String methodName, Object[] params, Class[] paramTypes) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.params = params;
        this.paramTypes = paramTypes;
    }

    /**
     * 获取
     * @return interfaceName
     */
    public String getInterfaceName() {
        return interfaceName;
    }

    /**
     * 设置
     * @param interfaceName
     */
    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    /**
     * 获取
     * @return methodName
     */
    public String getMethodName() {
        return methodName;
    }

    /**
     * 设置
     * @param methodName
     */
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    /**
     * 获取
     * @return params
     */
    public Object[] getParams() {
        return params;
    }

    /**
     * 设置
     * @param params
     */
    public void setParams(Object[] params) {
        this.params = params;
    }

    /**
     * 获取
     * @return paramTypes
     */
    public Class[] getParamTypes() {
        return paramTypes;
    }

    /**
     * 设置
     * @param paramTypes
     */
    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }
}

调用测试

import Jackie.pojo.Invocation;
import Jackie.service.HelloService;

public class ConsumerMain {
    public static void main(String[] args) {
        //封装一个invocation
        Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello",
                new Object[]{"myRPC的客户端"}, new Class[]{String.class});

        //远程调用服务
        String result = new HttpClient().post("localhost", 8080, invocation);

        System.out.println("远程调用执行的结果result="+result);
    }
}

你可能感兴趣的:(java,dubbo,rpc,java)