【编程不良人】Dubbo学习笔记---Dubbo定义、架构演变、Dubbo入门案例、示例说明、参数配置等

1. 什么是Dubbo?

视频链接:【编程不良人】dubbo 从入门到精通_哔哩哔哩_bilibili

总结:

Dubbo: 是⼀个基于SOA思想的RPC(远程过程调用)框架

  • 高性能RPC框架

  • 服务治理和资源调度的核心框架

SOA思想:面向服务的架构

给每⼀个模块暴露对应的ip和端⼝,当做⼀个服务进⾏运⾏

重点在于服务的管理(负载均衡、容灾模式、服务的横向扩展)

dubbo与spring无缝整合

2. 架构的演变

     随看互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构勢在必行,亟需一个治理系统确保架构有条不系的演进。
 * 单一应用架构
     当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
 ​
 * 垂直应用架构
     当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键
 ​
 * 分布式服务架构
     当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键
 ​
 * 流动计算架构
     当服务越来越多,容量的评估,小服务资源的浪费等同題逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

附:各种框架流程演示,摘自:从前慢-Dubbo_unique_perfect的博客-优快云博客

  • 单一应用架构

在这里插入图片描述

  • 垂直应用架构

在这里插入图片描述

  • 分布式服务架构

在这里插入图片描述

注册中心:

在这里插入图片描述

  • 流动计算架构

在这里插入图片描述

3. Dubbo架构图

基于注册中心的远程调用:

调用关系说明:

  1. 服务容器负责启动,加载,运行服务提供者。

  2. 服务提供者在启动时,向注册中心注册自己提供的服务。

  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

4. Dubbo入门案例

视频链接:2.dubbo的快速使用_哔哩哔哩_bilibili

SDK 手册-Java-快速入门-Spring Boot 开发服务:Spring Boot | Apache Dubbo

Dubbo 2.x版本快速开始案例:快速开始 | Apache Dubbo

4.1 开发dubbo的服务提供方

新建基于maven的spring项目:dubbo_001_p,作为服务的提供方:

4.1.1 pom.xml导入依赖

 <!--spring-core-->
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>4.3.4.RELEASE</version>
 </dependency>
 ​
 <!--spring相关依赖-->
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>4.3.4.RELEASE</version>
 </dependency>
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-beans</artifactId>
   <version>4.3.4.RELEASE</version>
 </dependency>
 ​
 <!--dubbo-->
 <dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>dubbo</artifactId>
   <version>2.5.3</version>
 </dependency>
 ​
 <!--zookeeper-->
 <dependency>
   <groupId>org.apache.zookeeper</groupId>
   <artifactId>zookeeper</artifactId>
   <version>3.4.12</version>
 </dependency>
 ​
 <!--
     由于我们使用zookeeper作为注册中心,所以需要操作zookeeper
     dubbo 2.6以前的版本需要引入zkclient操作zookeeper
     dubbo 2.6及以后的版本引入curator操作zookeeper
     下面两个zk客户端根据dubbo版本2选1即可,此处由于dubbo是2.5.3版本选择zkclient
 -->
 <!--zkclient-->
 <dependency>
   <groupId>com.101tec</groupId>
   <artifactId>zkclient</artifactId>
   <version>0.10</version>
 </dependency>
 ​
 <!--curator-framework-->
 <!--    <dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-framework</artifactId>
   <version>2.12.0</version>
 </dependency>-->

4.1.2 开发服务提供者接口

 package com.study.service;
 ​
 /**
  * @ClassName UserService
  * @Description 服务提供者接口
  * @Author Jiangnan Cui
  * @Date 2022/8/7 22:01
  * @Version 1.0
  */
 public interface UserService {
     public String findName(String name);
 ​
     public void addUser(String username);
 }

4.1.3 开发服务提供者接口实现类

 package com.study.service;
 ​
 /**
  * @ClassName UserServiceImpl
  * @Description 服务提供者接口实现类
  * @Author Jiangnan Cui
  * @Date 2022/8/7 22:03
  * @Version 1.0
  */
 public class UserServiceImpl implements UserService{
     @Override
     public String findName(String name) {
         System.out.println("姓名:" + name);
         return "hello:" + name;
     }
 ​
     @Override
     public void addUser(String username) {
         System.out.println("添加用户,用户名为:" + username);
     }
 }

4.1.4 编写服务提供方dubbo配置文件spring-dubbo.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
     
     <!--提供方应用信息,用于计算依赖关系,即:通过dubbo发布服务-->
     <dubbo:application name="dubbo_001_p"/>
 ​
     <!--将服务提供方注册到注册中心:使用multicast广播注册中心暴露服务地址-->
     <dubbo:registry address="multicast://224.5.6.7:1234"/>
 ​
     <!--指定服务的协议和使用端口号:用dubbo协议在20881端口暴露服务-->
     <dubbo:protocol name="dubbo" port="20881"/>
 ​
     <!--声明需要暴露的服务接口-->
     <dubbo:service interface="com.study.service.UserService" ref="userService"/>
 ​
     <!--指定服务提供者,和本地bean一样实现服务-->
     <bean id="userService" class="com.study.service.UserServiceImpl"/>
 </beans>

4.1.5 启动服务提供者

 package com.study.provider;
 ​
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 ​
 import java.io.IOException;
 ​
 /**
  * @ClassName TestProvider
  * @Description TODO
  * @Author Jiangnan Cui
  * @Date 2022/8/7 22:23
  * @Version 1.0
  */
 public class TestProvider {
     public static void main(String[] args) throws IOException {
         //加载
         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-dubbo.xml");
         //启动,此条语句写不写均可以
         context.start();
         System.out.println("服务提供者,开始提供服务......");//如果不阻塞,服务启动后就停止了
 ​
         System.in.read();//服务阻塞,按任意键退出
     }
 }

启动服务提供方服务:

服务提供方项目最终目录结构:

4.2 开发dubbo的服务消费方

新建基于maven的spring项目:dubbo_001_c,作为服务的消费方:

4.2.1 pom.xml导入依赖

 <!--spring-core-->
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>4.3.4.RELEASE</version>
 </dependency>
 ​
 <!--spring相关依赖-->
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>4.3.4.RELEASE</version>
 </dependency>
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-beans</artifactId>
   <version>4.3.4.RELEASE</version>
 </dependency>
 ​
 <!--dubbo-->
 <dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>dubbo</artifactId>
   <version>2.5.3</version>
 </dependency>
 ​
 <!--zookeeper-->
 <dependency>
   <groupId>org.apache.zookeeper</groupId>
   <artifactId>zookeeper</artifactId>
   <version>3.4.12</version>
 </dependency>
 ​
 <!--
     由于我们使用zookeeper作为注册中心,所以需要操作zookeeper
     dubbo 2.6以前的版本需要引入zkclient操作zookeeper
     dubbo 2.6及以后的版本引入curator操作zookeeper
     下面两个zk客户端根据dubbo版本2选1即可,此处由于dubbo是2.5.3版本选择zkclient
 -->
 <!--zkclient-->
 <dependency>
   <groupId>com.101tec</groupId>
   <artifactId>zkclient</artifactId>
   <version>0.10</version>
 </dependency>
 ​
 <!--curator-framework-->
 <!--    <dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-framework</artifactId>
   <version>2.12.0</version>
 </dependency>-->

4.2.2 将服务提供者的接口复制到服务消费方项目中

 package com.study.service;
 ​
 /**
  * @ClassName UserService
  * @Description 服务提供者接口
  * @Author Jiangnan Cui
  * @Date 2022/8/7 22:01
  * @Version 1.0
  */
 public interface UserService {
     public String findName(String name);
 ​
     public void addUser(String username);
 }

注意:包结构要与服务提供方严格一致

4.2.3 编写服务消费方dubbo配置文件spring-dubbo.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
     <!--服务消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样:通过dubbo消费服务-->
     <dubbo:application name="dubbo_001_c"/>
 ​
     <!--将服务消费方注册到注册中心:使用multicast广播注册中心暴露发现服务地址-->
     <dubbo:registry address="multicast://224.5.6.7:1234"/>
 ​
     <!--生成远程服务代理,调用服务-->
     <dubbo:reference id="userService" interface="com.study.service.UserService"/>
 </beans>

4.2.4 调用服务方服务

 package com.study.customer;
 ​
 import com.study.service.UserService;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 ​
 /**
  * @ClassName TestCustomer
  * @Description TODO
  * @Author Jiangnan Cui
  * @Date 2022/8/7 22:48
  * @Version 1.0
  */
 public class TestCustomer {
     public static void main(String[] args) {
         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-dubbo.xml");
         context.start();
         UserService userService = (UserService) context.getBean("userService");//获取远程调用服务
         userService.addUser("小崔");//执行远程方法
         String name = userService.findName("小红");
         System.out.println(name);//显示调用结果
     }
 }

启动服务调用方服务:

(1)TestProvider

(2)TestCustomer

服务消费方最终项目目录结构:

 

4.3 基于zookeeper(zk)的注册中心

       默认dubbo使用的是其默认组件multicast组件作为dubbo的注册中心,在dubbo中可以选择很多的组件作为注册中心,通 常在发布服务提供者时强烈建议使用zookeeper作为服务的注册中心,具体配置如下:

 <!--提供方的应用信息-->
 <dubbo:application name="dubbo_001_p"/>
 <!--注册中心-->
 <dubbo:registry address="zookeeper://192.168.28.136:2181" />
 <!--暴露dubbo的协议-->
 <dubbo:protocol name="dubbo" port="20880"/>
 ......

使用以上配置dubbo的服务就是基于zk的注册中心了。

5. dubbo细节补充

视频链接:3.dubbo的细节_哔哩哔哩_bilibili

依赖

依赖 | Apache Dubbo

必须依赖

JDK 1.6+ 1

缺省依赖

通过 mvn dependency:tree > dep.log 命令分析,Dubbo 缺省依赖以下三方库:

 [INFO] +- com.alibaba:dubbo:jar:2.5.9-SNAPSHOT:compile
 [INFO] |  +- org.springframework:spring-context:jar:4.3.10.RELEASE:compile
 [INFO] |  +- org.javassist:javassist:jar:3.21.0-GA:compile
 [INFO] |  \- org.jboss.netty:netty:jar:3.2.5.Final:compile

这里所有依赖都是按照 Dubbo 缺省配置选的,这些缺省值是基于稳定性和性能考虑的。

  • javassist.jar 2: 如果 <dubbo:provider proxy="jdk" /><dubbo:consumer proxy="jdk" />,以及 <dubbo:application compiler="jdk" />,则不需要。

  • spring-context.jar 3: 如果用 ServiceConfigReferenceConfig 的 API 调用,则不需要。

  • netty.jar 4: 如果 <dubbo:protocol server="mina"/><dubbo:protocol server="grizzly"/>,则换成 mina.jar 或 grizzly.jar。如果 <protocol name="rmi"/>,则不需要。

可选依赖

以下依赖,在主动配置使用相应实现策略时用到,需自行加入依赖。

  • netty-all 4.0.35.Final

  • mina: 1.1.7

  • grizzly: 2.1.4

  • httpclient: 4.5.3

  • hessian_lite: 3.2.1-fixed

  • fastjson: 1.2.31

  • zookeeper: 3.4.9

  • jedis: 2.9.0

  • xmemcached: 1.3.6

  • hessian: 4.0.38

  • jetty: 6.1.26

  • hibernate-validator: 5.4.1.Final

  • zkclient: 0.2

  • curator: 2.12.0

  • cxf: 3.0.14

  • thrift: 0.8.0

  • servlet: 3.0 5

  • validation-api: 1.1.0.GA 5

  • jcache: 1.0.0 5

  • javax.el: 3.0.1-b08 5

  • kryo: 4.0.1

  • kryo-serializers: 0.42

  • fst: 2.48-jdk-6

  • resteasy: 3.0.19.Final

  • tomcat-embed-core: 8.0.11

  • slf4j: 1.7.25

  • log4j: 1.2.16


  1. 理论上 Dubbo 可以只依赖 JDK,不依赖于任何三方库运行,只需配置使用 JDK 相关实现策略 ↩︎

  2. 字节码生成 ↩︎

  3. 配置解析 ↩︎

  4. 网络传输 ↩︎

  5. JEE ↩︎ ↩︎ ↩︎ ↩︎

成熟度

成熟度 | Apache Dubbo

配置

Dubbo 配置 | Apache Dubbo

以不同的方式来配置你的 Dubbo 应用

XML 配置

以 XML 配置的方式来配置你的 Dubbo 应用

动态配置中心

Dubbo 2.7 中的动态配置中心

属性配置

以属性配置的方式来配置你的 Dubbo 应用

自动加载环境变量

在 Dubbo 中自动加载环境变量

API 配置

以API 配置的方式来配置你的 Dubbo 应用

注解配置

以注解配置的方式来配置你的 Dubbo 应用

配置加载流程

Dubbo 中的配置加载流程介绍

以下摘自:dubbo 从入门到精通---编程不良人学习_wei198621的博客-优快云博客

dubbo= RPC + 服务治理+ 资源调度

RPC(Remote Procedure Call)远程过程调用:简单的理解是一个节点请求另一个节点提供的服务。

SOA(Service Oriented Architecture)“面向服务的架构”:它是一种设计方法,其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务通常以独立的形式存在于操作系统进程中,各个服务之间通过网络调用。

微服务架构:其实和 SOA 架构类似,微服务是在 SOA上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用,这些小应用之间通过服务完成交互和集成。

微服务架构 = 80%的SOA服务架构思想 + 100%的组件化架构思想 + 80%的领域建模思想

不同粒度配置的覆盖关系

以 timeout 为例,下图显示了配置的查找顺序,其它 retries, loadbalance, actives 等类似:

  • 方法级优先,接口级次之,全局配置再次之。

  • 如果级别一样,则消费方优先,提供方次之。

其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。

属性配置

       Dubbo 可以自动加载 classpath 根目录下的 dubbo.properties,但是你同样可以使用 JVM 参数来指定路径:-Ddubbo.properties.file=xxx.properties

映射规则

可以将 xml 的 tag 名和属性名组合起来,用 ‘.’ 分隔。每行一个属性。

  • dubbo.application.name=foo 相当于 <dubbo:application name="foo" />

  • dubbo.registry.address=10.20.153.10:9090 相当于 <dubbo:registry address="10.20.153.10:9090" />

如果在 xml 配置中有超过一个的 tag,那么你可以使用 ‘id’ 进行区分。如果你不指定 id,它将作用于所有 tag。

  • dubbo.protocol.rmi.port=1099 相当于 <dubbo:protocol id="rmi" name="rmi" port="1099" />

  • dubbo.registry.china.address=10.20.153.10:9090 相当于 <dubbo:registry id="china" address="10.20.153.10:9090" />

如下,是一个典型的 dubbo.properties 配置样例。

 dubbo.application.name=foo
 dubbo.application.owner=bar
 dubbo.registry.address=10.20.153.10:9090

重写与优先级

优先级从高到低:

  • JVM -D 参数:当你部署或者启动应用时,它可以轻易地重写配置,比如,改变 dubbo 协议端口;

  • XML:XML 中的当前配置会重写 dubbo.properties 中的;

  • Properties:默认配置,仅仅作用于以上两者没有配置时。

  1. 如果在 classpath 下有超过一个 dubbo.properties 文件,比如,两个 jar 包都各自包含了 dubbo.properties,dubbo 将随机选择一个加载,并且打印错误日志。

  2. 如果 id 没有在 protocol 中配置,将使用 name 作为默认属性。

 

 

6. Dubbo示例

视频链接:4.dubbo中启动时检查和集群容错_哔哩哔哩_bilibili

6.1 启动时检查

启动时检查 | Apache Dubbo

       当消费者调用多个服务,只希望其中测试其中一个服务的时候,不需要关心其他服务是否启动,可以配置其他服务为check=false。

6.2 集群容错

集群容错 | Apache Dubbo

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。

各节点关系:

  • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息

  • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更

  • ClusterDirectory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个

  • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等

  • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

集群容错模式如下:

可以自行扩展集群容错策略,参见:集群扩展

Failover Cluster

       失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。

重试次数配置如下:

 <dubbo:service retries="2" />

 <dubbo:reference retries="2" />

 <dubbo:reference>
     <dubbo:method name="findFoo" retries="2" />
 </dubbo:reference>

提示

该配置为缺省配置

Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

Broadcast Cluster

       广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

       现在广播调用中,可以通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。

Broadcast Cluster 配置 broadcast.fail.percent。

broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。

 @reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})

提示

2.1.0 开始支持

集群模式配置

按照以下示例在服务提供方和消费方配置集群模式

 <dubbo:service cluster="failsafe" />

 <dubbo:reference cluster="failsafe" />

服务的集群容错:

  • 搭建服务方:3个或多个服务,端口号分别为20881、20882、20883

  • 启动多个服务提供者

6.3 负载均衡

负载均衡 | Apache Dubbo

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。

可以自行扩展负载均衡策略,参见:负载均衡扩展

负载均衡策略如下:

Random LoadBalance

  • 随机,按权重设置随机概率。

  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

  • 轮询,按公约后的权重设置轮询比率。

  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。

  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

  • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing

  • 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />

  • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />

配置:

服务端服务级别

 <dubbo:service interface="..." loadbalance="roundrobin" />

客户端服务级别

 <dubbo:reference interface="..." loadbalance="roundrobin" />

服务端方法级别

 <dubbo:service interface="...">
     <dubbo:method name="..." loadbalance="roundrobin"/>
 </dubbo:service>

客户端方法级别

 <dubbo:reference interface="...">
     <dubbo:method name="..." loadbalance="roundrobin"/>
 </dubbo:reference>

dubbo负载均衡策略:

一致性hash算法策略:

 

6.4 线程模型

视频链接:5.dubbo中的线程模型和直连提供者多协议与多注册中心_哔哩哔哩_bilibili

        如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。

       但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。

       如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。

因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:

 <dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />

Dispatcher:

  • all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。

  • direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。

  • message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

  • execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

  • connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

ThreadPool:

  • fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)

  • cached 缓存线程池,空闲一分钟自动删除,需要时重建。

  • limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。

  • eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)

6.5 直连提供者

直连提供者 | Apache Dubbo

        在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连。点对点直连方式将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。

通过 XML 配置

         如果是线上需求需要点对点,可在 <dubbo:reference> 中配置 url 指向提供者,将绕过注册中心,多个地址用分号隔开,配置如下:

 <dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />

提示

1.0.6 及以上版本支持

通过 -D 参数指定

在 JVM 启动参数中加入-D参数映射服务地址,如:

 java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890

提示

key 为服务名,value 为服务提供者 url,此配置优先级最高,1.0.15 及以上版本支持

通过文件映射

如果服务比较多,也可以用文件映射,用 -Ddubbo.resolve.file 指定映射文件路径,此配置优先级高于 <dubbo:reference> 中的配置 3,如:

 java -Ddubbo.resolve.file=xxx.properties

然后在映射文件 xxx.properties 中加入配置,其中 key 为服务名,value 为服务提供者 URL:

 com.alibaba.xxx.XxxService=dubbo://localhost:20890

提示

1.0.15 及以上版本支持,2.0 以上版本自动加载 ${user.home}/dubbo-resolve.properties文件,不需要配置

注意

为了避免复杂化线上环境,不要在线上使用这个功能,只应在测试阶段使用。

6.6 多协议

多协议 | Apache Dubbo

Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。

不同服务不同协议

不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> 
     <dubbo:application name="world"  />
     <dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
     <!-- 多协议配置 -->
     <dubbo:protocol name="dubbo" port="20880" />
     <dubbo:protocol name="rmi" port="1099" />
     <!-- 使用dubbo协议暴露服务 -->
     <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" protocol="dubbo" />
     <!-- 使用rmi协议暴露服务 -->
     <dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" protocol="rmi" /> 
 </beans>

多协议暴露服务

需要与 http 客户端相互操作

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
     <dubbo:application name="world"  />
     <dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
     <!-- 多协议配置 -->
     <dubbo:protocol name="dubbo" port="20880" />
     <dubbo:protocol name="hessian" port="8080" />
     <!-- 使用多个协议暴露服务 -->
     <dubbo:service id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" protocol="dubbo,hessian" />
 </beans>

协议参考手册

Dubbo 协议参考手册:协议参考手册 | Apache Dubbo

推荐使用 Dubbo 协议。各协议的性能情况,请参见:性能测试报告


dubbo 协议

dubbo:// 协议参考手册

rest 协议

rest:// 协议参考手册

http 协议

http:// 协议参考手册

hessian 协议

hessian:// 协议参考手册

redis 协议

redis:// 协议参考手册

thrift 协议

thrift:// 协议参考手册

gRPC 协议

grpc:// 协议参考手册

memcached 协议

memcached:// 协议参考手册

rmi 协议

rmi:// 协议参考手册

webservice 协议

webservice:// 协议参考手册

6.7 多注册中心

多注册中心 | Apache Dubbo

       Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的 1

多注册中心注册

比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
     <dubbo:application name="world"  />
     <!-- 多注册中心配置 -->
     <dubbo:registry id="hangzhouRegistry" address="10.20.141.150:9090" />
     <dubbo:registry id="qingdaoRegistry" address="10.20.141.151:9010" default="false" />
     <!-- 向多个注册中心注册 -->
     <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="hangzhouRegistry,qingdaoRegistry" />
 </beans>

不同服务使用不同注册中心

比如:CRM 有些服务是专门为国际站设计的,有些服务是专门为中文站设计的。

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
     <dubbo:application name="world"  />
     <!-- 多注册中心配置 -->
     <dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
     <dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
     <!-- 向中文站注册中心注册 -->
     <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="chinaRegistry" />
     <!-- 向国际站注册中心注册 -->
     <dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" registry="intlRegistry" />
 </beans>

多注册中心引用

        比如:CRM 需同时调用中文站和国际站的 PC2 服务,PC2 在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
     <dubbo:application name="world"  />
     <!-- 多注册中心配置 -->
     <dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
     <dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
     <!-- 引用中文站服务 -->
     <dubbo:reference id="chinaHelloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" registry="chinaRegistry" />
     <!-- 引用国际站站服务 -->
     <dubbo:reference id="intlHelloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" registry="intlRegistry" />
 </beans>

如果只是测试环境临时需要连接两个不同注册中心,使用竖号分隔多个不同注册中心地址:

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
     <dubbo:application name="world"  />
     <!-- 多注册中心配置,竖号分隔表示同时连接多个不同注册中心,同一注册中心的多个集群地址用逗号分隔 -->
     <dubbo:registry address="10.20.141.150:9090|10.20.154.177:9010" />
     <!-- 引用服务 -->
     <dubbo:reference id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" />
 </beans>

  1. 可以自行扩展注册中心,参见:注册中心扩展 ↩︎

注册中心参考手册

注册中心参考手册 | Apache Dubbo

推荐使用 Zookeeper 注册中心

Nacos 注册中心

Nacos 注册中心参考手册

Zookeeper 注册中心

Zookeeper 注册中心参考手册

Multicast 注册中心

Multicast 注册中心参考手册

Redis 注册中心

Redis 注册中心参考手册

Simple 注册中心

Simple 注册中心参考手册

6.8 服务分组

服务分组 | Apache Dubbo

使用服务分组区分服务接口的不同实现:当一个接口有多种实现时,可以用 group 区分。

服务

 <dubbo:service group="feedback" interface="com.xxx.IndexService" />
 <dubbo:service group="member" interface="com.xxx.IndexService" />

引用

 <dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
 <dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

任意组:

 <dubbo:reference id="barService" interface="com.foo.BarService" group="*" />

6.9 泛化调用

使用泛化调用 | Apache Dubbo

实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。

泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现(多用于公司自己的泛化调用需求)

通过 Spring 使用泛化调用

在 Spring 配置申明 generic="true"

 <dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />

在 Java 代码获取 barService 并开始泛化调用:

 GenericService barService = (GenericService) applicationContext.getBean("barService");
 Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" });

通过 API 方式使用泛化调用

 import org.apache.dubbo.rpc.service.GenericService; 
 ... 
  
 // 引用远程服务 
 // 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
 ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>(); 
 // 弱类型接口名
 reference.setInterface("com.xxx.XxxService");  
 reference.setVersion("1.0.0");
 // 声明为泛化接口 
 reference.setGeneric(true);  
 ​
 // 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用  
 GenericService genericService = reference.get(); 
  
 // 基本类型以及Date,List,Map等不需要转换,直接调用 
 Object result = genericService.$invoke("sayHello", new String[] {"java.lang.String"}, new Object[] {"world"}); 
  
 // 用Map表示POJO参数,如果返回值为POJO也将自动转成Map 
 Map<String, Object> person = new HashMap<String, Object>(); 
 person.put("name", "xxx"); 
 person.put("password", "yyy"); 
 // 如果返回POJO将自动转成Map 
 Object result = genericService.$invoke("findPerson", new String[]
 {"com.xxx.Person"}, new Object[]{person}); 
  
 ...

有关泛化类型的进一步解释

假设存在 POJO 如:

 package com.xxx;
 ​
 public class PersonImpl implements Person {
     private String name;
     private String password;
 ​
     public String getName() {
         return name;
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public String getPassword() {
         return password;
     }
 ​
     public void setPassword(String password) {
         this.password = password;
     }
 }

则 POJO 数据:

 Person person = new PersonImpl(); 
 person.setName("xxx"); 
 person.setPassword("yyy");

可用下面 Map 表示:

 Map<String, Object> map = new HashMap<String, Object>(); 
 // 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。
 map.put("class", "com.xxx.PersonImpl"); 
 map.put("name", "xxx"); 
 map.put("password", "yyy");

6.10 参数校验

参数验证 | Apache Dubbo

在 Dubbo 中进行参数验证:参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。

Maven 依赖

 <dependency>
     <groupId>javax.validation</groupId>
     <artifactId>validation-api</artifactId>
     <version>1.0.0.GA</version>
 </dependency>
 <dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-validator</artifactId>
     <version>4.2.0.Final</version>
 </dependency>

示例:

参数标注示例

 import java.io.Serializable;
 import java.util.Date;
  
 import javax.validation.constraints.Future;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Past;
 import javax.validation.constraints.Pattern;
 import javax.validation.constraints.Size;
  
 public class ValidationParameter implements Serializable {
     private static final long serialVersionUID = 7158911668568000392L;
  
     @NotNull // 不允许为空
     @Size(min = 1, max = 20) // 长度或大小范围
     private String name;
  
     @NotNull(groups = ValidationService.Save.class) // 保存时不允许为空,更新时允许为空 ,表示不更新该字段
     @Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
     private String email;
  
     @Min(18) // 最小值
     @Max(100) // 最大值
     private int age;
  
     @Past // 必须为一个过去的时间
     private Date loginDate;
  
     @Future // 必须为一个未来的时间
     private Date expiryDate;
  
     public String getName() {
         return name;
     }
  
     public void setName(String name) {
         this.name = name;
     }
  
     public String getEmail() {
         return email;
     }
  
     public void setEmail(String email) {
         this.email = email;
     }
  
     public int getAge() {
         return age;
     }
  
     public void setAge(int age) {
         this.age = age;
     }
  
     public Date getLoginDate() {
         return loginDate;
     }
  
     public void setLoginDate(Date loginDate) {
         this.loginDate = loginDate;
     }
  
     public Date getExpiryDate() {
         return expiryDate;
     }
  
     public void setExpiryDate(Date expiryDate) {
         this.expiryDate = expiryDate;
     }
 }

分组验证示例

 public interface ValidationService { // 缺省可按服务接口区分验证场景,如:@NotNull(groups = ValidationService.class)   
     @interface Save{} // 与方法同名接口,首字母大写,用于区分验证场景,如:@NotNull(groups = ValidationService.Save.class),可选
     void save(ValidationParameter parameter);
     void update(ValidationParameter parameter);
 }

关联验证示例

 import javax.validation.GroupSequence;
  
 public interface ValidationService {   
     @GroupSequence(Update.class) // 同时验证Update组规则
     @interface Save{}
     void save(ValidationParameter parameter);
  
     @interface Update{} 
     void update(ValidationParameter parameter);
 }

参数验证示例

 import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
  
 public interface ValidationService {
     void save(@NotNull ValidationParameter parameter); // 验证参数不为空
     void delete(@Min(1) int id); // 直接对基本类型参数验证
 }

配置:

在客户端验证参数

 <dubbo:reference id="validationService" interface="org.apache.dubbo.examples.validation.api.ValidationService" validation="true" />

在服务器端验证参数

 <dubbo:service interface="org.apache.dubbo.examples.validation.api.ValidationService" ref="validationService" validation="true" />

验证异常信息

 import javax.validation.ConstraintViolationException;
 import javax.validation.ConstraintViolationException;
  
 import org.springframework.context.support.ClassPathXmlApplicationContext;
  
 import org.apache.dubbo.examples.validation.api.ValidationParameter;
 import org.apache.dubbo.examples.validation.api.ValidationService;
 import org.apache.dubbo.rpc.RpcException;
  
 public class ValidationConsumer {   
     public static void main(String[] args) throws Exception {
         String config = ValidationConsumer.class.getPackage().getName().replace('.', '/') + "/validation-consumer.xml";
         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
         context.start();
         ValidationService validationService = (ValidationService)context.getBean("validationService");
         // Error
         try {
             parameter = new ValidationParameter();
             validationService.save(parameter);
             System.out.println("Validation ERROR");
         } catch (RpcException e) { // 抛出的是RpcException
             ConstraintViolationException ve = (ConstraintViolationException) e.getCause(); // 里面嵌了一个ConstraintViolationException
             Set<ConstraintViolation<?>> violations = ve.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
             System.out.println(violations);
         }
     } 
 }

提示

2.1.0 版本开始支持, 如何使用可以参考 dubbo 项目中的示例代码

验证方式可扩展,扩展方式参见开发者手册中的验证扩展

6.11 服务化最佳实践

服务化最佳实践 | Apache Dubbo

分包, 粒度, 版本, 兼容性, 枚举, 序列化, 异常

分包

建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。

如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需在 Spring 加载过程中引用此配置即可。配置建议放在模块的包目录下,以免冲突,如:com/alibaba/china/xxx/dubbo-reference.xml

粒度

服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。

服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。

不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。

版本

每个接口都应定义版本号,为后续不兼容升级提供可能,如: <dubbo:service interface="com.xxx.XxxService" version="1.0" />

建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。

当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。

兼容性

服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。

各协议的兼容性不同,参见:服务协议

枚举值

如果是完备集,可以用 Enum,比如:ENABLE, DISABLE

如果是业务种类,以后明显会有类型增加,不建议用 Enum,可以用 String 代替。

如果是在返回值中用了 Enum,并新增了 Enum 值,建议先升级服务消费方,这样服务提供方不会返回新值。

如果是在传入参数中用了 Enum,并新增了 Enum 值,建议先升级服务提供方,这样服务消费方不会传入新值。

序列化

服务参数及返回值建议使用 POJO 对象,即通过 setter, getter 方法表示属性的对象。

服务参数及返回值不建议使用接口,因为数据模型抽象的意义不大,并且序列化需要接口实现类的元信息,并不能起到隐藏实现的意图。

服务参数及返回值都必须是传值调用,而不能是传引用调用,消费方和提供方的参数或返回值引用并不是同一个,只是值相同,Dubbo 不支持引用远程对象。

异常

建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。

如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。

查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch,并且不能进行有效处理。

服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。

调用

不要只是因为是 Dubbo 调用,而把调用 try...catch 起来。try...catch 应该加上合适的回滚边界上。

Provider 端需要对输入参数进行校验。如有性能上的考虑,服务实现者可以考虑在 API 包上加上服务 Stub 类来完成检验。

7. Dubbo在项目中的实战应用

视频链接:【编程不良人】dubbo 从入门到精通_哔哩哔哩_bilibili

dubbo项目的拆分分析:

dubbo项目拆分实战:

springboot + dubbo 整合:没有官方版本

老雷整合了 springboot +dubbo,在gitee 码云上面,地址:spring-boot-starter-dubbo: 让dubbo小白正常使用dubbo,只需一盏茶的功夫。让分布式大牛,使用dubbo也如沐春风。

参考链接:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值