Dubbo的高级特性:服务管控篇

简介: 大家好,我是王有志。今天我们继续了解并学习如何使用Dubbo在服务管控方面提供的常用高级特性。

王有志,一个分享硬核Java技术的互金摸鱼侠

加入Java人的提桶跑路群:共同富裕的Java人

上一篇,我们已经介绍了 DUbbo 在服务治理方面提供的特性,今天我们一起来看看 Dubbo 在其它方面提供的特性。同服务治理篇一样,本文的目的在于学会使用 Dubbo 在服务管控方面提供的特性,依旧不涉及任何实现原理。

工程结构

嗯~~

是这样的,因为电脑过于拉胯,而且 IDEA 着实有些吃内存了,所以我将测试工程按照子项目合并到一起了,目前我使用的工程结构是这样的:

https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlwNDQ7kPYOxAY/img/6f26e516-ce78-49d8-ba20-cb4e23826805.png

子模块名由两部分组成:配置方式+功能,如: XMLProvider ,表示以 XML 配置方式为主的服务提供方。

Tips:IDEA 快要追上“内存雄狮” CLion 了。

本地存根(Stub)

使用 Dubbo 时,服务使用方只集成了接口,所有的实现全都在服务提供方,但部分场景中,我们希望服务使用方完成一些逻辑的处理,以此来减少 RPC 交互带来的性能消耗,例如:将参数校验放在服务使用方去做,减少一次与服务调用方的网络交互。

https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlwNDQ7kPYOxAY/img/8dd6c9c7-451c-4681-b225-8f823cc4092c.jpeg

这种场景中,我们可以使用 Dubbo 提供的本地存根特性。我们有如下的服务提供方的工程结构:

https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlwNDQ7kPYOxAY/img/0a7169a4-cef6-40f8-afe5-94206631503a.png

xml-provider-api 模块中定义了对外提供服务的接口 XMLProviderService ,代码如下:

public interface XMLProviderService {
  String say(String message);
}

以及接口存根 XMLProviderServiceStub,代码如下:

public class XMLProviderServiceStub implements XMLProviderService {

  private final XMLProviderService xmlProviderService;

  public XMLProviderServiceStub(XMLProviderService xmlProviderService) {
    this.xmlProviderService = xmlProviderService;
  }

  @Override
  public String say(String message) {
    if (StringUtils.isBlank(message)) {
      return "message不能为空!";
    }

    try {
      return this.xmlProviderService.say(message);
    } catch (Exception e) {
      return "远程调用失败:" + e.getMessage();
    }
  }
}

接着我们在服务使用方的工程中配置接口存根:

<dubbo:reference id="xmlProviderService" interface="com.wyz.api.XMLProviderService" stub="com.wyz.api.stub.XMLProviderServiceStub"/>

Tips:使用本地存根,要求存根的实现类必须有传入 Proxy 实例(服务使用方生成的 Proxy 实例)的构造函数。

本地伪装(Mock)

本地伪装即我们在《Dubbo的高级特性:服务治理篇》中提到的服务降级,我们今天再稍微做一个补充。本地伪装是本地存根的一个子集,本地存根可以处理 RPC 调用环节中各种各样的错误和异常,而本地伪装则专注于处理 RpcException (如网络失败,响应超时等)这种需要容错处理的异常。

我们为 XMLProviderService 添加一个本地伪装服务 XMLProviderServiceMock,工程结构如下:

https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlwNDQ7kPYOxAY/img/37396cfe-7f16-4a7b-aac4-6e9a91e59edd.png

XMLProviderServiceMock 的代码如下:

public class XMLProviderServiceMock implements XMLProviderService {

  @Override
  public String say(String message) {
    return "服务出错了!";
  }
}

配置文件可以按如下方式配置:

<dubbo:reference id="xmlProviderService" interface="com.wyz.api.XMLProviderService" mock="true"/>

这种配置中,要求 Mock 的实现必须按照“接口名+Mock 后缀”的方式进行命名;如果不想使用这种命名方式,可以使用全限名:

<dubbo:reference id="xmlProviderService" interface="com.wyz.api.XMLProviderService" mock="com.wyz.api.mock.XMLProviderServiceMock"/>

Tips:再“重复”一遍 Mock 的原因是,上一篇中出了一点错误,本应在<dubbo:reference>标签中做的配置,我写到了<dubbo:service>标签中,产生错误的原因还是没有动手在项目中歇一歇,哎,真应了那句“纸上得来终觉浅,绝知此事要躬行”。

参数回调

Dubbo 支持参数回调功能,使服务提供方可以“反向”调用服务使用方,该功能是基于长链接生成的反向代理实现的,效果类似于异步调用。我们举个支付的例子:

XMLProvider 工程的 xml-provider-api 模块中添加 PaymentService 接口,同时添加 PaymentNotifyService 用于通知PaymentService 的结果:

public interface PaymentService {
  void payment(String cardNo, PaymentNotifyService paymentNotifyService);
}

public interface PaymentNotifyService {
  void paymentNotify(String message);
}

XMLProvider 工程的 xml-provider-service 模块中实现 PaymentService 接口:

public class PaymentServiceImpl implements PaymentService {
  @Override
  public void payment(String cardNo, PaymentNotifyService paymentNotifyService) {
    System.out.println("向卡号[" + cardNo + "]付钱!");
    // 业务逻辑
    paymentNotifyService.paymentNotify("付款成功");
  }
}

执行PaymentService#payment方法,并调用PaymentNotifyService#paymentNotify方法通知服务调用方执行结果。

XMLConsumer 工程中实现 PaymentNotifyService 接口:

public class PaymentNotifyServiceImpl implements PaymentNotifyService {
  @Override
  public void paymentNotify(String message) {
    System.out.println("支付结果:" + message);
  }
}

来看一下此时的工程结构:

https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlwNDQ7kPYOxAY/img/a45fd029-2f1b-4f8b-83c1-c54997100c6d.png

接下来是 XML 的配置,参数回调中,我们需要关注的是服务提供方 XMLProvider 工程的 xml-provider-service 模块的配置:

<bean id="paymentServiceImpl" class="com.wyz.service.impl.PaymentServiceImpl"/>
<dubbo:service interface="com.wyz.api.PaymentService" ref="paymentServiceImpl" callbacks="10">
  <dubbo:method name="payment">
    <dubbo:argument index="1" callback="true"/>
  </dubbo:method>
</dubbo:service>

配置通过第 4 行的<dubbo:argument index="1" callback="true"/>来确定 PaymentService#payment 方法中第 2 个(index 从 0 开始)参数是回调参数 ;callbacks限制了同一个长链接下回调的次数,而不是总共回调的次数。

Tips:在实际的支付业务场景中,更倾向于异步处理,比如服务提供方在接收到时,启动新线程处理支付业务并调用通知接口,主线程返回成功接收支付请求。

异步调用

异步调用允许服务提供方立即返回响应,同时后台继续执行请求处理,当服务使用方请求响应结果时,服务提供方将结果返回。

https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlwNDQ7kPYOxAY/img/8dd1ba48-cfc6-4e05-ba5b-2a7a5dcb5170.jpeg

DUbbo 支持两种异步调用方式:

  • 使用 CompletableFuture 接口
  • 使用 RpcContext

DUbbo 2.7 之后,DUbbo 以CompletableFuture 接口为异步编程的基础。

使用 CompletableFuture 实现异步调用

我们先来看如何使用 CompletableFuture 实现异步调用,声明 CompletableFutureAsyncService 接口:

public interface CompletableFutureAsyncService {
  CompletableFuture<String> async(String message);
}

接着是接口实现:

public class CompletableFutureAsyncServiceImpl implements CompletableFutureAsyncService {
  @Override
  public CompletableFuture<String> async(String message) {
    return CompletableFuture.supplyAsync(() -> {
      System.out.println(Thread.currentThread().getName() + " say : " + message);

      try {
        TimeUnit.SECONDS.sleep(10);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
      return "异步调用成功!";
    });
  }
}

XML 配置与普通的 Dubbo RPC 接口配置相同,xml-provider-service 模块的配置:

<bean id="completableFutureAsyncServiceImpl" class="com.wyz.service.impl.CompletableFutureAsyncServiceImpl" />
<dubbo:service interface="com.wyz.api.CompletableFutureAsyncService" ref="completableFutureAsyncServiceImpl" />

XMLConsumer 模块的配置:

<dubbo:reference id="completableFutureAsyncService" interface="com.wyz.api.CompletableFutureAsyncService"/>

使用方式也非常简单:

CompletableFuture<String> completableFuture = completableFutureAsyncService.async("Hello");
System.out.println(completableFuture.get());

Tips

  • Dubbo 中使用 CompletableFuture 与单独使用 CompletableFuture 并无什么差异~~
  • CompletableFutureAsyncServiceImpl 的实现中打印接口名称的目的是为了清晰的展示出异步调用的效果;
  • CompletableFuture#supplyAsync(Supplier<U> supplier)默认使用ForkJoinPool#commonPool()
  • 重载方法CompletableFuture#supplyAsync(Supplier<U> supplier, Executor executor)允许使用自定义线程池。

使用 AsyncContext 实现异步调用

除了使用 CompletableFuture 外,还可以通过 Dubbo 定义的 AsyncContext 实现异步调用。先来编写接口和接口实现:

public interface RpcContextAsyncService {
  String async(String message);
}

public class RpcContextAsyncServiceImpl implements RpcContextAsyncService {

  @Override
  public String async(String message) {
    final AsyncContext asyncContext = RpcContext.startAsync();
    new Thread(() -> {
      asyncContext.signalContextSwitch();
      asyncContext.write(Thread.currentThread().getName() + " say : " + message);
    }).start();
    // 异步调用中,这个返回值完全没有意义
    return null;
  }
}

服务提供方的配置与其它 Dubbo 接口的配置并无不同:

<bean id="rpcContextAsyncServiceImpl" class="com.wyz.service.impl.RpcContextAsyncServiceImpl"/>
<dubbo:service interface="com.wyz.api.RpcContextAsyncService" ref="rpcContextAsyncServiceImpl"/>

接着是服务使用方的配置,需要添加 async 参数:

<dubbo:reference id="rpcContextAsyncService" interface="com.wyz.api.RpcContextAsyncService" async="true"/>

最后是在服务使用方中调用 RPC 接口:

rpcContextAsyncService.async("Thanks");

Future<String> future = RpcContext.getServiceContext().getFuture();
System.out.println(future.get());

泛化调用

Dubbo 的泛化调用提供了一种不依赖服务提供方 API (SDK)的而调用服务的实现方式。主要场景在于网关平台的实现,通常网关的实现不应该依赖于其他服务的 API(SDK)。

Dubbo 官方提供了 3 种泛化调用的方式:

  • 通过API使用泛化调用
  • 通过 Spring 使用泛化调用(XML 形式)
  • Protobuf 对象泛化调用

这里我们介绍以 XML 的形式配置泛化调用的方式。

准备工作

首先我们再准备一个服务提供的工程 GenericProvider,工程结构如下:

https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlwNDQ7kPYOxAY/img/834b8cb4-43b8-40e8-a0e4-1f9a30eb6263.png

工程中定义了接口即实现类 GenericProviderService 和 GenericProviderServiceImpl,代码如下:

public interface GenericProviderService {
  String say(String message);
}

public class GenericProviderServiceImpl implements GenericProviderService {
  @Override
  public String say(String message) {
    return "GenericProvider say:" + message;
  }
}

generic-dubbo-provider.xml 中只需要正常配置 GenericProvider 提供的服务即可:

<bean id="genericProviderServiceImpl" class="com.wyz.service.impl.GenericProviderServiceImpl"/>
<dubbo:service interface="com.wyz.service.api.GenericProviderService" ref="genericProviderServiceImpl" generic="true"/>

application.yml 文件的配置我们就不多赘述了。

服务使用方的配置

回到 XMLConsumer 工程中,先配置 Dubbo 服务引用,xml-dubbo-consumer.xml 中添加如下内容:

<dubbo:reference id="genericProviderService" generic="true" interface="com.wyz.service.api.GenericProviderService"/>

参数 generic 声明这是一个泛化调用的服务。此时 IDEA 会将interface="com.wyz.service.api.GenericProviderService"标红,提示“Cannot resolve class 'GenericProviderService' ”,这个我们不需要关注,因为com.wyz.service.api包下确实不存在 GenericProviderService 接口。

接着我们来使用 GenericProviderService 接口:

ApplicationContext context = SpringContextUtils.getApplicationContext();
// genericProviderService是XML中定义的服务id
GenericService genericService = (GenericService) context.getBean("genericProviderService");

// $invoke的3个参数分别为:方法名,参数类型,参数
Object result = genericService.$invoke("say", new String[]{"java.lang.String"}, new Object[]{"wyz"});
System.out.println(result);

这样,我们就可以通过 ApplicationContext 获取到 GenericProviderService 接口提供的服务了。

Tips:SpringContextUtils 用于获取 ApplicationContext,代码如下:

@Component
public class SpringContextUtils implements ApplicationContextAware {
  private static ApplicationContext applicationContext = null;

  public static ApplicationContext getApplicationContext() {
    return SpringContextUtils.applicationContext;
  }

  @Override
  public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
    SpringContextUtils.applicationContext = applicationContext;
  }
}

结语

好了,到目前为止,我们已经一起认识并学习了 Dubbo 中常用特性的配置与使用,当然了,经历了多年的发展,Dubbo 的提供的特性远不止于此,如果想要了解更多内容,可以查看阿里巴巴提供的文档《Apache Dubbo微服务框架从入门到精通》。

下一篇,我们从服务注册部分正式开启对 Dubbo 实现原理的探索。


如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠王有志,我们下次再见!

目录
相关文章
|
2天前
|
SpringCloudAlibaba 负载均衡 Dubbo
【SpringCloud Alibaba系列】Dubbo高级特性篇
本章我们介绍Dubbo的常用高级特性,包括序列化、地址缓存、超时与重试机制、多版本、负载均衡。集群容错、服务降级等。
【SpringCloud Alibaba系列】Dubbo高级特性篇
|
2月前
|
监控 Dubbo Java
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
这篇文章详细介绍了如何将Spring Boot与Dubbo和Zookeeper整合,并通过Dubbo管理界面监控服务注册情况。
173 0
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
|
4月前
|
JSON Dubbo Java
【Dubbo协议指南】揭秘高性能服务通信,选择最佳协议的终极攻略!
【8月更文挑战第24天】在分布式服务架构中,Apache Dubbo作为一款高性能的Java RPC框架,支持多种通信协议,包括Dubbo协议、HTTP协议及Hessian协议等。Dubbo协议是默认选择,采用NIO异步通讯,适用于高要求的内部服务通信。HTTP协议通用性强,利于跨语言调用;Hessian协议则在数据传输效率上有优势。选择合适协议需综合考虑性能需求、序列化方式、网络环境及安全性等因素。通过合理配置,可实现服务性能最优化及系统可靠性提升。
67 3
|
4月前
|
存储 负载均衡 Dubbo
Dubbo阶段性总结及3.0新特性
该文章是对Dubbo技术的一次总结,包括对Dubbo框架的整体架构、服务提供者发布注册原理、SPI机制、服务消费者订阅原理、服务调用原理、线程池模型、负载均衡机制、服务容错机制等内容的回顾,并简要介绍了Dubbo 3.0的新特性。
Dubbo阶段性总结及3.0新特性
|
4月前
|
缓存 Dubbo Java
Dubbo服务消费者启动与订阅原理
该文章主要介绍了Dubbo服务消费者启动与订阅的原理,包括服务消费者的启动时机、启动过程以及订阅和感知最新提供者信息的方式。
Dubbo服务消费者启动与订阅原理
|
4月前
|
Dubbo 网络协议 Java
深入掌握Dubbo服务提供者发布与注册原理
该文章主要介绍了Dubbo服务提供者发布与注册的原理,包括服务发布的流程、多协议发布、构建Invoker、注册到注册中心等过程。
深入掌握Dubbo服务提供者发布与注册原理
|
4月前
|
C# 开发者 Windows
勇敢迈出第一步:手把手教你如何在WPF开源项目中贡献你的第一行代码,从选择项目到提交PR的全过程解析与实战技巧分享
【8月更文挑战第31天】本文指导您如何在Windows Presentation Foundation(WPF)相关的开源项目中贡献代码。无论您是初学者还是有经验的开发者,参与这类项目都能加深对WPF框架的理解并拓展职业履历。文章推荐了一些适合入门的项目如MvvmLight和MahApps.Metro,并详细介绍了从选择项目、设置开发环境到提交代码的全过程。通过具体示例,如添加按钮点击事件处理程序,帮助您迈出第一步。此外,还强调了提交Pull Request时保持专业沟通的重要性。参与开源不仅能提升技能,还能促进社区交流。
52 0
|
4月前
|
缓存 负载均衡 Dubbo
Dubbo服务集群容错原理(重要)
该文章主要介绍了Dubbo服务集群容错的原理,包括集群容错技术的概念、Dubbo中使用的集群容错技术种类及其原理。
|
7月前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
2月前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
74 2