Dubbo 基本介绍与手写模拟 Dubbo

本文涉及的产品
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 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提供的方法。image.png

  • 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.客户端得到最终结果。

image.png目前,官网上是这么介绍的: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 等。

image.pngProvider需要完成以下内容:

  • 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]);
    }
}

参考链接


相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
目录
相关文章
|
6月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
6月前
|
缓存 负载均衡 Dubbo
Dubbo 第二节_ Dubbo的基本应用与高级应用
官⽹地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/ 如果在消费端和服务端都配置了负载均衡策略,以消费端为准。 这其中⽐较难理解的就是最少活跃调⽤数是如何进⾏统计的? 讲道理,最少活跃数应该是在服务提供者端进⾏统计的,服务提供者统计有多少个请求正在执⾏中。 但在Dubbo中,就是不讲道理,它是在消费端进⾏统计的,为什么能在消费端进⾏统计? 逻辑是这样的:官⽹地址:http://dubbo.apache.org/zh/docs
|
6月前
|
负载均衡 监控 Dubbo
秒懂Dubbo接口(原理篇)
【4月更文挑战第25天】秒懂Dubbo接口(原理篇)
224 3
秒懂Dubbo接口(原理篇)
|
6月前
|
负载均衡 Dubbo 算法
深入理解Dubbo-2.Dubbo功能之高级特性分析
深入理解Dubbo-2.Dubbo功能之高级特性分析
105 0
|
6月前
|
Dubbo Java 应用服务中间件
Dubbo框架介绍与手写模拟Dubbo
Dubbo框架介绍与手写模拟Dubbo
|
6月前
|
负载均衡 Dubbo Java
Dubbo 第一节_ Dubbo框架介绍与手写模拟Dubbo 第二节_ Dubbo的基本应用与高级应用
官⽹地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/generic-service/官⽹地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-mock/官⽹地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/async-call/
|
6月前
|
Dubbo Java 应用服务中间件
Dubbo 第四节: Spring与Dubbo整合原理与源码分析
DubboConfigConfigurationRegistrar的主要作⽤就是对propties⽂件进⾏解析并根据不同的配置项项⽣成对应类型的Bean对象。
155 0
|
缓存 Dubbo Java
Dubbo2.7的Dubbo SPI实现原理细节
Dubbo2.7的Dubbo SPI实现原理细节
49 0
|
存储 开发框架 负载均衡
Dubbo框架知识点
Dubbo框架知识点
101 0
|
Dubbo Java 应用服务中间件
Dubbo源码学习一
首先,我们知道dubbo在以前都是基于zookeeper作为配置中心的,同时是建立在spring基础之上的。因此,就需要思考一些问题: 首先dubbo是怎样和spring集成的,也即dubbo集成在spring上需要具备什么条件?接着dubbo作为一个服务治理的微服务框架,那它的生产者和消费者与注册中心怎样进行交互的。 dubbo是基于spring的基础之上进行开发的RPC框架。需要和spring整合,必然就需要按照Spring解析默认标签和自定义标签的方式进行。而在Spring中,我们知道在Spring中是在ParseBeanDefintions(Elemen
155 0
Dubbo源码学习一