Dubbo探究

目录:

1、什么是Dubbo

2、什么是RPC

3、手写模拟Dubbo

4、Dubbo的基本使用

5、Dubbo的高级功能

6、Dubbo与SpringCloud Feign对比

7、Dubbo3新特性

8、Dubbo与SpringCloud Feign互通

什么是Dubbo

Dubbo是一个高性能、轻量级、致力于分布式、云原生、透明化的远程服务调用方案。

以前的官网介绍:Apache Dubbo是一款高性能、轻量级的开源Java RPC框架。

现在的官网是这么介绍的:Apache Dubbo是一款云原生微服务开发框架,构建具备内置RPC、流量管控、安全、可观测能力的应用,支持Kubernetes和VM部署环境。

 为什么会将RPC改为服务?

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

既然是服务框架,那我们一起看看Dubbo的架构是怎样的,这里放一张官网提供的核心概念架构图:

 

以上是 Dubbo 的工作原理图,从抽象架构上分为两层:服务治理抽象控制面 和 Dubbo 数据面 。

  • 服务治理控制面。服务治理控制面不是特指如注册中心类的单个具体组件,而是对 Dubbo 治理体系的抽象表达。控制面包含协调服务发现的注册中心、流量管控策略、Dubbo Admin 控制台等,如果采用了 Service Mesh 架构则还包含 Istio 等服务网格控制面。
  • Dubbo 数据面。数据面代表集群部署的所有 Dubbo 进程,进程之间通过 RPC 协议实现数据交换,Dubbo 定义了微服务应用开发与调用规范并负责完成数据传输的编解码工作。
    • 服务消费者 (Dubbo Consumer),发起业务调用或 RPC 通信的 Dubbo 进程
    • 服务提供者 (Dubbo Provider),接收业务调用或 RPC 通信的 Dubbo 进程

Dubbo 数据面

从数据面视角,Dubbo 帮助解决了微服务实践中的以下问题:

  • Dubbo 作为 服务开发框架 约束了微服务定义、开发与调用的规范,定义了服务治理流程及适配模式
  • Dubbo 作为 RPC 通信协议实现 解决服务间数据传输的编解码问题

 

服务开发框架

微服务的目标是构建足够小的、自包含的、独立演进的、可以随时部署运行的分布式应用程序,几乎每个语言都有类似的应用开发框架来帮助开发者快速构建此类微服务应用,比如 Java 微服务体系的 Spring Boot,它帮 Java 微服务开发者以最少的配置、最轻量的方式快速开发、打包、部署与运行应用。

微服务的分布式特性,使得应用间的依赖、网络交互、数据传输变得更频繁,因此不同的应用需要定义、暴露或调用 RPC 服务,那么这些 RPC 服务如何定义、如何与应用开发框架结合、服务调用行为如何控制?这就是 Dubbo 服务开发框架的含义,Dubbo 在微服务应用开发框架之上抽象了一套 RPC 服务定义、暴露、调用与治理的编程范式,比如 Dubbo Java 作为服务开发框架,当运行在 Spring 体系时就是构建在 Spring Boot 应用开发框架之上的微服务开发框架,并在此之上抽象了一套 RPC 服务定义、暴露、调用与治理的编程范式。

 

 

Dubbo 作为服务开发框架包含的具体内容如下:

  • RPC 服务定义、开发范式。比如 Dubbo 支持通过 IDL 定义服务,也支持编程语言特有的服务开发定义方式,如通过 Java Interface 定义服务。
  • RPC 服务发布与调用 API。Dubbo 支持同步、异步、Reactive Streaming 等服务调用编程模式,还支持请求上下文 API、设置超时时间等。
  • 服务治理策略、流程与适配方式等。作为服务框架数据面,Dubbo 定义了服务地址发现、负载均衡策略、基于规则的流量路由、Metrics 指标采集等服务治理抽象,并适配到特定的产品实现。

 再放一张更老的图:

什么是RPC

既然Dubbo是由RPC框架进化而来,所以要理解Dubbo首先要理解RPC的概念。

维基百科是这么定义RPC的:

分布式计算,远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。

 

如果涉及的软件采用面向对象编程

,那么远程过程调用亦可称作远程调用远程方法调用,例:Java RMI

所以,对于Java程序员而言,RPC就是远程方法调用。

远程方法调用本地方法调用是相对的两个概念,本地方法调用指的是进程内部的方法调用,而远程方法调用指的是两个进程内的方法相互调用。

所以就有了:

  1. RPC over Http:基于Http协议来传输数据
  2. RPC over Tcp:基于Tcp协议来传输数据

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

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

手写模拟Dubbo

知道了RPC和Dubbo的概念和核心框架,是否可以自己写一个RPC框架模拟Dubbo的工作流程呢?这里提前准备了一个写好的demo

simplerpc

 

 Provicer暴露服务到ZK上

此时ZK节点存放服务信息。包括接口全限定名,ip和端口

 Consumer打印消费日志,成功进行了远程过程调用

 

 整个流程可以简化成:

 

 Dubbo的基本使用

 Dubbo服务分为Consumer,Provider,也就是服务的消费者和服务提供者。通常情况下,服务不是单一角色。一个服务即是Provider又是Consumer的情况比较多。下面给出一个接入Dubbo的例子: dubbolearn

本地启动nacos 单例 

 

 

Provider未启动,nacos服务列表是空的

 启动ProviderApplication,再看nacos服务列表,多出来一个Provider服务

 服务信息,包含:服务版本、序列化协议、传输协议、端口等等,并且也支持多协议同时暴露。

这时启动Consumer,在nacos服务列表点击订阅者,跳转到订阅者详情界面:

 同时也能远程调用Provider的方法。

 

 再回到Dubbo的定义上,Dubbo本质上还是基于一个RPC协议拓展出来的,可以方便使用者进行远程过程调用,像调用本地方法一样调用远程的方法。对比SpringCloud的Feign调用,相当于颗粒度减小,要对其进行降级限流则更加细致精确。

Dubbo的高级功能

  1. 负载均衡、集群容错、服务降级
  2. 本地存根、本地伪装、参数回调
  3. 异步调用、泛化调用、泛化实现
  4. 管理台、动态配置、服务路由

既然作为一款云原生微服务开发框架,肯定具备一系列强大的服务治理工具。

 

负载均衡:

官网给出的是支持如下7中负载均衡策略,可以通过配置的方式修改生效。

算法特性备注
Weighted Random LoadBalance 加权随机 默认算法,默认权重相同
RoundRobin LoadBalance 加权轮询 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同,
LeastActive LoadBalance 最少活跃优先 + 加权随机 背后是能者多劳的思想
Shortest-Response LoadBalance 最短响应优先 + 加权随机 更加关注响应速度
ConsistentHash LoadBalance 一致性哈希 确定的入参,确定的提供者,适用于有状态请求
P2C LoadBalance Power of Two Choice 随机选择两个节点后,继续选择“连接数”较小的那个节点。
Adaptive LoadBalance 自适应负载均衡 在 P2C 算法基础上,选择二者中 load 最小的那个节点

如果在消费端和服务端都配置了负载均衡策略,以消费端为准。

 

这其中比较难理解的就是最少活跃调用数是如何进行统计的?

 

道理,最少活跃数应该是在服务提供者端进行统计的,服务提供者统计有多少个请求正在执行中

但在Dubbo中,就是不讲道理,它是在消费端进行统计的,为什么能在消费端进行统计?

 

逻辑是这样的:

  1. 消费者会缓存所调用服务的所有提供者,比如记为p1、p2、p3三个服务提供者,每个提供者内都有一个属性记为active,默认位0
  2. 消费者在调用次服务时,如果负载均衡策略是leastactive
  3. 消费者端会判断缓存的所有服务提供者的active,选择最小的,如果都相同,则随机
  4. 选出某一个服务提供者后,假设位p2,Dubbo就会对p2.active+1
  5. 然后真正发出请求调用该服务
  6. 消费端收到响应结果后,对p2.active-1
  7. 这样就完成了对某个服务提供者当前活跃调用数进行了统计,并且并不影响服务调用的性能

服务超时

在服务提供者和服务消费者上都可以配置服务超时时间,这两者是不一样的。

 

消费者调用一个服务,分为三步:

  1. 消费者发送请求(网络传输)
  2. 服务端执行服务
  3. 服务端返回响应(网络传输)

 

如果在服务端和消费端只在其中一方配置了timeout,那么没有歧义,表示消费端调用服务的超时时间,消费端如果超过时间还没有收到响应结果,则消费端会抛超时异常,服务端不会抛异常,服务端在执行服务后,会检查执行该服务的时间,如果超过timeout,则会打印一个超时日志。服务会正常的执行完。

 

 

如果在服务端和消费端各配了一个timeout,那就比较复杂了,假设

  1. 服务执行为5s
  2. 消费端timeout=3s
  3. 服务端timeout=6s

 

那么消费端调用服务时,消费端会收到超时异常(因为消费端超时了),服务端一切正常(服务端没有超时)。

集群容错

集群容错官方解释

集群容错表示:服务消费者在调用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选出其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。

服务降级

服务降级官方解释

服务降级表示:服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。

 

集群容错和服务降级的区别在于:

  1. 集群容错是整个集群范围内的容错
  2. 服务降级是单个服务提供者的自身容错

本地存根与本地伪装

本地存根官方解释

本地存根,名字很抽象,但实际上不难理解,本地存根就是一段逻辑,这段逻辑是在服务消费端执行的,这段逻辑一般都是由服务提供者提供,服务提供者可以利用这种机制在服务消费者远程调用服务提供者之前或之后再做一些其他事情,比如结果缓存,请求参数验证等等。这很像我们经常使用的AOP的环绕增强,背后的原理也不难理解,肯定和动态代理有关。

参数回调

参数回调官方解释

Dubbo支持参数回调,可以允许服务端调用客户端接口,Dubbo是基于长连接生成反向代理。参数回调的意义在于:可以用回调函数通知客户端执行结果,或发送通知,在方法执行时间比较长时,有些类似异步调用,比如在审批工作流中回调客户端审批结果。那假设在服务端执行完主动调用客户端逻辑,不回调行不行?如果不依赖回调,则需要服务端依赖客户端的jar包,存在循环依赖的问题。而参数回调还有一个好处,就是可以由客户端决定服务端调用的回调函数,由服务端主导,变为客户端主导。

 

异步调用

异步调用官方解释

在微服务场景下,服务之间的交互往往对性能要求很高,如果使用同步调用会显著增加服务的响应时间,并对系统造成一定的压力。而Dubbo通过异步调用这种方式,可以在保证业务逻辑完整的同时,大幅提升性能和并发能力。

Dubbo的Provider和Consumer都支持异步调用,一共有四种组合

 没有使用Dubbo框架的时候,我们也经常有这样的优化项:把远程接口调用改成异步执行,增加处理效率,减少调用方等待时间。而Dubbo中只需要一些配置就能轻松实现异步调用。“异步”作为性能优化的利器之一,对于系统优化是一种常见思路;Dubbo天然的异步模式,不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。

Dubbo3在使用异步调用的时候,Provider使用CompleteableFuture签名的接口,

public interface AsyncService {
    CompletableFuture<String> sayHello(String name);
}

接口实现:

复制代码
public class AsyncServiceImpl implements AsyncService {
    @Override
    public CompletableFuture<String> sayHello(String name) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println(name);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "async response from provider.";
        });
    }
}
复制代码

Consumer端在进行接口调用的时候:

复制代码
// 此调用会立即返回null
asyncService.sayHello("world");
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
CompletableFuture<String> helloFuture = RpcContext.getServiceContext().getCompletableFuture();
// 为Future添加回调
helloFuture.whenComplete((retValue, exception) -> {
    if (exception == null) {
        System.out.println(retValue);
    } else {
        exception.printStackTrace();
    }
});
复制代码

 

 泛化调用

泛化调用官方解释

在Dubbo中,如果某个服务想要支持泛化调用,就可以将该服务的generic属性设置为true,那对于服务消费者来说,就可以不用依赖该服务的接口,换句话说,没有Provider提供的接口jar包,只要有接口全限定名定义,方法定义,参数列表,参数类型定义,只使用map组装参数,依然可以进行RPC调用。直接利用GenericService接口来进行服务调用。

比如如果要基于Dubbo做上层封装或者要做一个测试平台,此时就可以用到泛化调用。

demo中也给出了泛化调用的例子。dubbolearn

核心代码如下:

复制代码
    @GetMapping("/genget/{name}")
    public ResponseEntity<String> genericSay(@PathVariable("name") String name){
        HashMap map = new HashMap();
        map.put("userName","一只小Coder");
        map.put("passWord","root");
        Object result = DubboGenericInvoke.invoke("job",//表示需要调用哪个实例的服务,通过Dubbo的tag属性来区分
                "com.joey.service.BarService",//需要调用的服务的全限定类名
                "sayHello", //方法
                new String[]{"com.joey.model.User"},//参数类型,不仅支持java的基本数据类型,同时支持自定义类型
                new Object[]{map});//如果是基本数据类型,组装成Map集合就好了
        return ResponseEntity.ok(result.toString());

    }
复制代码

 

复制代码
    private static Map<String, ReferenceConfig<GenericService>> referenceConfigMap = new ConcurrentHashMap<>();
    public static Object invoke(String tag,String interfaceClass, String methodName, String[] paramTypes, Object[] params) {
        ReferenceConfig<GenericService> referenceConfig;
        String key = interfaceClass+"---"+tag;
        try {
            referenceConfig = referenceConfigMap.get(key);
            if (referenceConfig == null) {
                referenceConfig = new ReferenceConfig<>();
                RegistryConfig registry = new RegistryConfig("nacos://127.0.0.1:8848/username=nacos&password=nacos");
                referenceConfig.setRegistry(registry);
                referenceConfig.setRetries(4);//重试次数
                ConsumerConfig consumerConfig = new ConsumerConfig();
                referenceConfig.setConsumer(consumerConfig);
                referenceConfig.setGeneric("true");
                referenceConfig.setInterface(interfaceClass);
                referenceConfig.setTimeout(3000);//ms,如果服务方超时时间超过了这里的时间,将进行重试,达到重试次数时,报超时异常。
                if(tag.length()!=0){
                    referenceConfig.setTag(tag);
                }
                referenceConfig.setCheck(true);
                referenceConfigMap.put(key, referenceConfig);
            }
            GenericService genericService = referenceConfigMap.get(key).get();
            Object result = genericService.$invoke(methodName, paramTypes, params);
            if (result == null) {
                System.out.println("远程服务结果返回为空,请注意查看远程服务的参数:"+ params);
            }
            return result;
        } catch (GenericException e) {
            System.out.println("发起远程调用失败,错误信息:{}"+e.getMessage());
            referenceConfigMap.remove(key);
            return null;

        } catch (Exception e) {
            System.out.println("远程服务获取结果失败,错误信息:{}"+e.getMessage());
            referenceConfigMap.remove(key);
            return null;
        }
    }
复制代码

 管理台

管理台官方教程

作为微服务框架,Dubbo具备服务的实时监控、服务治理等功能。dubbo-control-plane支持可视化的展示、监控集群状态,还支持实时下发流量管控规则

 

Dubbo与SpringCloud Feign对比

  1. 通信效率:

Dubbo采用短连接的方式,通信效率较高。而Feign基于Http传输协议,通信效率相对较低。在实际应用中,如果服务调用频繁且对延迟要求较高,Dubbo可能更适合。

  1. 负载均衡算法:

Dubbo支持4种负载均衡算法(随机、轮询、活跃度、Hash一致性),并引入权重的概念,可以根据实际需求进行灵活配置。而Feign只支持轮询、随机、ResponseTime加权等策略,负载均衡算法是Client级别的。因此,在负载均衡方面,Dubbo具有更高的灵活性和可扩展性。

  1. 容错机制:

Dubbo支持多种容错策略,如failover、failfast、broadcast、forking等,并引入了retry次数、timeout等配置参数。而Feign则利用熔断机制来实现容错。在实际应用中,根据具体需求选择合适的容错策略,可以提高系统的稳定性和可用性。

  1. 集成与扩展:

Dubbo作为一个独立的框架,与其他系统的集成可能需要额外的开发工作。而Feign与Spring Cloud等微服务框架集成良好,可以方便地与其他系统协同工作。此外,Feign还提供了丰富的扩展点,方便开发者根据实际需求进行定制开发。

综上所述,Dubbo和Feign在性能上各有优势。如果你的项目已经选择了Spring Cloud或微服务架构,并且主要依赖于HTTP/HTTPS协议的RESTful API调用,那么使用Feign可能更加合适。然而,如果你的项目对通信效率、负载均衡算法和容错机制有较高要求,或者需要与其他系统进行集成,那么Dubbo可能是一个更好的选择。

 

 Dubbo3新特性

主要分为以下几个方面:

注册模型的改变
新一代RPC协议,Triple协议
Triple协议的流式调用
Dubbo3的跨语言调用

注册模型的改变

在服务注册领域,市面上有两种模型,一种是应用级注册,一种是接口级注册,在Spring Cloud中,一个应用是一个微服务,而在Dubbo2.7中,一个接口是一个微服务。
 
所以,Spring Cloud在进行服务注册时,是把应用名以及应用所在服务器的IP地址和应用所绑定的端口注册到注册中心,相当于key是应用名,value是ip+port,而在Dubbo2.7中,是把接口名以及对应应用的IP地址和所绑定的端口注册到注册中心,相当于key是接口名,value是ip+port。
 
所以在Dubbo2.7中,一个应用如果提供了10个Dubbo服务,那么注册中心中就会存储10对keyvalue,而Spring Cloud就只会存一对keyvalue,所以以Spring Cloud为首的应用级注册是更加适合的。
 
所以Dubbo3.0中将注册模型也改为了应用级注册,提升效率节省资源的同时,通过统一注册模型,也为各个微服务框架的互通打下了基础
新一代RPC协议,Triple协议
服务消费者是通过发送一个Invocation对象来表示要调用的是哪个接口中的哪个方法,demo中是直接把Invocation对象进行JDK序列化得到字节流后然后发送出去的,那如果现在不用JDK序列化呢,比如还有很多序列化的方式,比如JSON、Hessian等等。
 
此时服务消费者最好能在发送的请求中,能标记所使用的序列化方式,这个标记是不能放在Invocation对象中的,因为这个标记指的就是Invocation对象的序列化方法,服务端接收到字节流后,首先得能拿到序列化标记,看采用的是什么序列化方法,再解析反序列化。
 
如果通过HTTP协议(特指HTTP1.x,HTTP2后面分析),那实现起来就比较方便,把序列化标记放在请求头,Invocation对象序列化之后的结果放在请求体,服务端收到HTTP请求后,就先解析请求头得到序列化标记,再取请求体进行反序列化。
但是Dubbo觉得用HTTP1.x协议性能太低了,原因在于:
  1. HTTP1.x协议中,多余无用的字符太多了,比如回车符、换行符,这每一个字符都会占用一个字节,这些字节占用了网络带宽,降低了网络IO的效率
  2. HTTP1.x协议中,一条Socket连接,一次只能发送一个HTTP请求,因为如果连续发送两个HTTP请求,然后收到了一个响应,那怎么知道这个响应对应的是哪个请求呢,这样导致Socket连接的利用低,并发、吞吐量低。
所以有了dubbo协议,它就是为了解决上面两个问题,协议,描述的就是一份数据长什么样子,HTTP协议也是一样,描述的就是一个HTTP请求长什么样子,以什么开始,到哪里结束。
 
比如HTTP协议就描述了,从第一个字节开始,遇到第一个空格符时,那就是表示前面每个字节对应的字符组合成的字符串就表示请求方法(字符编码为ascii,一个字符对应一个字节),紧接着继续解析字节,将会按HTTP协议格式解析出请求行、请求头,解析完请求头之后,取出content-length对应的value,该value表示请求体的字节数,所以直接再获取content-length个字节,就表示获取到了请求体(Invocation对象序列化之后的字节),从而一个HTTP请求就获取出来,下一个字节就是另外一个HTTP请求了。
而dubbo协议也有自己的格式,比如:

 

dubbo协议相比于http1.x协议,性能会更好,因为请求中没有多余的无用的字节,都是必要的字节,并且每个Dubbo请求和响应中都有一个请求ID,这样可以基于一个Socket连接同时发送多个Dubbo请求,不用担心请求和响应对不上,所以dubbo协议成为了Dubbo框架中的默认协议。
 
但是dubbo协议一旦涉及到跨RPC框架,比如一个Dubbo服务要调用gPRC服务,就比较麻烦了,因为发一个dubbo协议的请求给一个gPRC服务,gPRC服务只会按照gRPC的格式来解析字节流,最终肯定会解析不成功的。
 
dubbo协议虽好,但是不够通用,所以这就出现了Triple协议,Triple协议是基于HTTP2,没有性能问题,另外HTTP协议非常通用,全世界都认它,兼容起来也比较简单,而且还有很多额外的功能,比如流式调用。
大概对比一下triple、dubbo、rest这三个协议
  • triple协议基于的是HTTP2,rest协议目前基于的是HTTP1,都可以做到跨语言。
  • triple协议兼容了gPRC(Triple服务可以直接调用gRPC服务,反过来也可以),rest协议不行
  • triple协议支持流式调用,rest协议不行
  • rest协议更方便浏览器、客户端直接调用,triple协议不行(原理上支持,当得对triple协议的底层实现比较熟悉才行,得知道具体的请求头、请求体是怎么生成的)
  • dubbo协议是Dubbo3.0之前的默认协议,triple协议是Dubbo3.0之后的默认协议,优先用Triple协议
  • dubbo协议不是基于的HTTP,不够通用,triple协议底层基于HTTP所以更通用(比如跨语言、跨异构系统实现起来比较方便)
  • dubbo协议不支持流式调用

Triple协议的流式调用

Stream 是 Dubbo3 新提供的一种调用类型,在以下场景时建议使用流的方式:

  • 接口需要发送大量数据,这些数据无法被放在一个 RPC 的请求或响应中,需要分批发送,但应用层如果按照传统的多次 RPC 方式无法解决顺序和性能的问题,如果需要保证有序,则只能串行发送
  • 流式场景,数据需要按照发送顺序处理, 数据本身是没有确定边界的
  • 推送类场景,多个消息在同一个调用的上下文中被发送和处理
当使用Triple协议进行RPC调用时,支持多种方式来调用服务,只不过在服务接口中要定义不同的方法,比如:
复制代码
public interface UserService {

    // UNARY
    String sayHello(String name);

    // SERVER_STREAM
    default void sayHelloServerStream(String name, StreamObserver<String> response) {
    }

    // CLIENT_STREAM / BI_STREAM
    default StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
        return response;
    }

}
复制代码
在provider和consumer下都要加上triple协议的依赖:
<dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-triple</artifactId>
            <version>3.0.7</version>
 </dependency>

 

UNARY

unary,就是正常的调用方法
Provider定义:
// UNARY
@Override
public String sayHello(String name) {
    return "Hello " + name;
}

Consumer定义:

String result = userService.sayHello("dubbo3");

SERVER_STREAM

Provicer定义:

// SERVER_STREAM
@Override
public void sayHelloServerStream(String name, StreamObserver<String> response) {
    response.onNext(name + " hello");
    response.onNext(name + " world");
    response.onCompleted();
}

Consumer定义:

复制代码
userService.sayHelloServerStream("dubbo", new StreamObserver<String>() {
    @Override
    public void onNext(String data) {
        // 服务端返回的数据
        System.out.println(data);
    }

    @Override
    public void onError(Throwable throwable) {}

    @Override
    public void onCompleted() {
        System.out.println("complete");
    }
});
复制代码

CLIENT_STREAM

Provider定义:

 

复制代码
// CLIENT_STREAM
@Override
public StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
    return new StreamObserver<String>() {
        @Override
        public void onNext(String data) {
            // 接收客户端发送过来的数据,然后返回数据给客户端
            response.onNext("result:" + data);
        }

        @Override
        public void onError(Throwable throwable) {}

        @Override
        public void onCompleted() {
            System.out.println("completed");
        }
    };
}
复制代码

Consumer定义:

复制代码
StreamObserver<String> streamObserver = userService.sayHelloStream(new StreamObserver<String>() {
     @Override
     public void onNext(String data) {
         System.out.println("接收到响应数据:"+ data);
     }

     @Override
     public void onError(Throwable throwable) {}

     @Override
     public void onCompleted() {
         System.out.println("接收到响应数据完毕");
     }
});

// 发送数据
streamObserver.onNext("request dubbo hello");
streamObserver.onNext("request dubbo world");
streamObserver.onCompleted();
复制代码

BI_STREAM

和CLIENT_STREAM一样
 
Dubbo3的跨语言调用
如果要实现Dubbo跨语言调用,且
消费者应用不是用Java语言写的呢,比如是通过python或者go语言实现的,那就至少需要满足两个条件才能调用Java实现的Dubbo服务:
  1. Dubbo一开始是用Java语言实现的,那现在就需要一个go语言实现的Dubbo框架,也就是现在的dubbo-go,然后在go项目中引入dubbo-go,从而可以在go项目中使用dubbo,比如使用go语言去暴露和使用Dubbo服务。
  2. 我们在使用Java语言开发一个Dubbo服务时,会把服务接口和相关类,单独抽象成为一个Maven项目,实际上就相当于一个单独的jar包,这个jar能被Java项目所使用,但不能被go项目所使用,所以go项目中该如何使用Java语言所定义的接口呢?直接用是不太可能的,只能通过间接的方式来解决这个问题,除开Java语言之外,那有没有其他技术也能定义接口呢?并且该技术也是Java和go都支持,这就是protobuf
我们可以通过protobuf来定义接口,然后通过protobuf的编译器将接口编译为特定语言的实现。
比如下面的例子:
复制代码
syntax = "proto3";

package api;

option go_package = "./;api";

option java_multiple_files = true;
option java_package = "com.joey";
option java_outer_classname = "UserServiceProto";

service UserService {
  rpc GetUser (UserRequest) returns (User) {}
}

// The response message containing the greetings
message UserRequest {
  string uid = 1;
}

// The response message containing the greetings
message User {
  string uid = 1;
  string username = 2;
}
复制代码
相当于定义了一个HelloService服务,并且定义了一个getUser方法,接收UserRequest类型的参数,返回User类型的对象。
将proto文件编译成java代码以后,target/classes包下就会多出一个类
复制代码
@DubboService
public class UserServiceImpl implements UserService {

    public User getUser(UserRequest userRequest) {
        User user = User.newBuilder().setUid(userRequest.getUid()).setUsername("dubbo").build();
        return user;
    }
    
}
复制代码
而对于想要调用UserService服务的消费者而言,其实也是一样的改造,只需要使用同一份userservice.proto进行编译就可以了,比如现在有一个go语言的消费者。

 

然后把userservice.proto复制到go-consumer/proto下,然后进行编译,编译成为go语言对应的服务代码,只不过go语言中没有maven这种东西可以帮助我们编译,我们只能用原生的protobuf的编译器进行编译。
 

Dubbo与SpringCloud Feign互通

最后再分享一篇文章:

零改造实现 Spring Cloud & Apache Dubbo 互通

posted @   Java民工陆小凤  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示