天天看点

Dubbo 基本介绍与手写模拟 Dubbo

什么是 RPC

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

简单来说,RPC 就是远程方法调用,远程方法调用和本地方法调用是相对的两个概念,本地方法调用指的是进程内部的方法调用,而远程方法调用指的是两个进程内的方法相互调用。实现远程方法调用,基本的就是通过网络,通过传输数据来进行调用。

RPC 可以基于 HTTP 或者TCP 来传输数据:

  • 1.RPC over Http:基于Http协议来传输数据。
  • 2.PRC over Tcp:基于Tcp协议(socket)来传输数据。

对于所传输的数据,可以交由RPC的双方来协商定义,但基本都会包括:

  • 1.调用的是哪个类或接口。
  • 2.调用的是哪个方法,方法名和方法参数类型(考虑方法重载)。
  • 3.调用方法的入参。

所以,我们其实可以看到 RPC 的自定义性是很高的,各个公司内部都可以实现自己的一套 RPC 框架,而 Dubbo 就是阿里所开源出来的一套 RPC 框架。

RPC 工作原理

RPC的设计由Client,Client stub,Network ,Server stub,Server构成。其中Client就是用来调用服务的,Cient stub是用来把调用的方法和参数序列化的(因为要在网络中传输,必须要把对象转变成字节),Network用来传输这些信息到Server stub, Server stub用来把这些信息反序列化的,Server就是服务的提供者,最终调用的就是Server提供的方法。

Dubbo 基本介绍与手写模拟 Dubbo
  • 1.Client像调用本地服务似的调用远程服务。
  • 2.Client stub接收到调用后,将方法、参数序列化。
  • 3.客户端通过网络(socket,http等)将消息发送到服务端。
  • 4.Server stub 收到消息后进行解码(将消息对象反序列化)。
  • 5.Server stub 根据解码结果调用本地的服务。
  • 6.本地服务执行(对于服务端来说是本地执行)并将结果返回给Server stub。
  • 7.Server stub将返回结果打包成消息(将结果消息对象序列化)。
  • 8.服务端通过sockets将消息发送到客户端。
  • 9.Client stub接收到结果消息,并进行解码(将结果消息反序列化)。
  • 10.客户端得到最终结果。
Dubbo 基本介绍与手写模拟 Dubbo

目前,官网上是这么介绍的:Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。在几个月前,官网的介绍是:Apache Dubbo 是一款高性能、轻量级的开源 Java RPC框架。

为什么会将 RPC 改为服务?

Dubbo 一开始的定位就是 RPC 框架,专注于两个服务之间的调用。但随着微服务的盛行,除开服务调用之外,Dubbo 也在逐步的涉猎服务治理、服务监控、服务网关等等,所以现在的 Dubbo 目标已经不止是 RPC 框架了,而是想成为和Spring Cloud 类似的一个服务框架。

上面所说的 Dubbo 指的是 Dubbo 框架,另外 Dubbo 还有 Dubbo 协议的含义,Dubbo 框架提供了许许多多的协议实现,例如:dubbo(默认,基于 Netty),rmi, webservice,http,redis 等。

Dubbo 基本介绍与手写模拟 Dubbo

Provider需要完成以下内容:

  • 1.提供服务的接口。
  • 2.提供服务实现类。
  • 3.将服务注册到注册中心(根据接口可以获取到服务实现类和服务地址)。
  • 4.暴露服务:HTTP协议(基于Tomcat),Dubbo协议(基于Netty)。

Consumer调用服务的时需要完成以下内容:

  • 1.去注册中心获取服务信息。
  • 2.调用方法时需要提供以下信息,我们把这四个必要的东西构建成一个对象(Invocation对象):
    • 1.接口名
    • 2.方法名
    • 3.方法参数类型列表
    • 4.方法值参数值列表

github 地址:

https://github.com/cr7258/dubbo-lab/tree/master/dubbo-simulate

HelloService 服务

添加一个 HelloService 服务的接口,Consumer 在依赖中只需要引用 HelloService 接口:

package com.chengzw.provider.api;
/**
 * 服务的接口
 * @author 程治玮
 * @since 2021/3/30 10:10 下午
 */
public interface HelloService {
    public String sayHello(String name);
}      

Provider 需要实现该接口:

package com.chengzw.provider.impl;
import com.chengzw.provider.api.HelloService;
/**
 * 服务实现类
 * @author 程治玮
 * @since 2021/3/30 10:11 下午
 */
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "hello " + name;
    }
}      

RPC 远程调用模块

HTTP 和 Dubbo 两个远程调用协议都实现了 Protocol接口:

package com.chengzw.framework;
/**
 * HTTP和Dubbo协议都实现该接口
 * 策略模式
 * @author 程治玮
 * @since 2021/3/31 11:17 下午
 */
public interface Protocol {
    void start(URL url);
    String send(URL url,Invocation invocation);
}      

Provider 和 Consumer 可以通过配置文件来指定使用哪个协议来完成远程调用(主要就是 Invocation 对象的序列号反序列化和方法的处理),而不需要将调用哪个协议写死在代码中。

package com.chengzw.framework;
import com.chengzw.framework.protocol.dubbo.DubboProtocol;
import com.chengzw.framework.protocol.http.HttpProtocol;
import org.springframework.beans.factory.annotation.Value;
/**
 * 读取配置文件决定服务端和客户端使用http还是dubbo协议
 * 工厂模式,解决协议切换的问题
 * @author 程治玮
 * @since 2021/3/31 11:23 下午
 */
public class ProtocolFactory {
    @Value("${protocol}")
    private static String name;
    public static Protocol getProtocol() {
        if (name == null || name.equals("")) name = "http";
        switch (name) {
            case "http":
                return new HttpProtocol();
            case "dubbo":
                return new DubboProtocol();
            default:
                break;
        }
        return new HttpProtocol();
    }
}      

服务注册

使用 Zookeeper 来做服务的注册中心,Provider 将服务(接口名)和 地址端口(URL)注册到注册中心中,Consumer 从注册中心获取服务的信息:

package com.chengzw.framework.register;
import com.chengzw.framework.URL;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import java.util.HashMap;
import java.util.Map;
/**
 * Zookeeper注册中心写入读取服务端信息
 * @author 程治玮
 * @since 2021/3/31 11:39 下午
 */
public class ZookeeperRegister {
    static CuratorFramework client;
    static Map<String, String> UrlCache = new HashMap<>();
    static {
        client = CuratorFrameworkFactory
                .newClient("localhost:2181", new RetryNTimes(3, 1000));
        client.start();
    }
    private static Map<String, String> REGISTER = new HashMap<>();
    //Provider注册服务
    public static void regist(String interfaceName, String implClass, String url) {
        try {
            Stat stat = client.checkExists().forPath(String.format("/dubbo/service/%s", interfaceName));
            if(stat != null){
                client.delete().forPath(String.format("/dubbo/service/%s", interfaceName));
            }
            String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format("/dubbo/service/%s", interfaceName),(implClass + "::" + url).getBytes());
            System.out.println("Provier服务注册: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //获取Provider URL
    public static URL getURL(String interfaceName) {
        URL url = null;
        String urlString = null;
        //先查询缓存
        if (UrlCache.containsKey(interfaceName)) {
            urlString = UrlCache.get(interfaceName);
        } else {
            try {
                byte[] bytes = client.getData().forPath(String.format("/dubbo/service/%s", interfaceName));
                urlString = new String(bytes);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        String host = urlString.split("::")[1].split(":")[0];
        String port = urlString.split("::")[1].split(":")[1];
        return new URL(host,Integer.parseInt(port));
    }
    //获取Provider实现类
    public static Class getImplClass(String interfaceName) throws Exception {
        byte[] bytes = client.getData().forPath(String.format("/dubbo/service/%s", interfaceName));
        String urlString = new String(bytes);
        return Class.forName(urlString.split("::")[0]);
    }
}      

参考链接