Dubbo

354 阅读6分钟

Dubbo配置

dubbo.application

对应 org.apache.dubbo.config.ApplicationConfig, 代表当前应用的信息

  1. name: 当前应用程序的名称,在dubbo-admin中我们也可以看到,这个代表这个应用名称。我们 在真正时是时也会根据这个参数来进行聚合应用请求。
  2. owner: 当前应用程序的负责人,可以通过这个负责人找到其相关的应用列表,用于快速定位到责 任人。
  3. qosEnable : 是否启动QoS 默认true
  4. qosPort : 启动QoS绑定的端口 默认22222
  5. qosAcceptForeignIp: 是否允许远程访问 默认是false

这里需要注意的是:配置项类似qosEnable,对应的xml写法和properties文件写法,不一样

xml配置案例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       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="service-consumer">
        <dubbo:parameter key="qos.enable" value="true"/>
        <dubbo:parameter key="qos.port" value="33333"/>
        <dubbo:parameter key="qos.accept.foreign.ip" value="true"/>
    </dubbo:application>

    <dubbo:registry address="zookeeper://81.xx.xx.80:2181?timeout=30000"/>
    
    <dubbo:reference id="helloService" interface="com.lagou.service.HelloService" />

</beans>

注解形式配置文件

dubbo.application.name=service-consumer33355555
dubbo.application.owner=444
dubbo.registry.address=zookeeper://81.xx.xx.80:2181?timeout=30000
dubbo.application.qosEnable=true
dubbo.application.qosPort=33333
dubbo.application.qosAcceptForeignIp=false

小坑:在第一次进行绑定端口的参数配置的时候,发现qosPort没有生效,代码追踪到 org.springframework.beans.AbstractPropertyAccessor中的setPropertyValues方法,发现在properties中,不能配置 dubbo.application.qos.port=33333 必须配置成 dubbo.application.qosPort=33333才行

那么,我们在xml中时,配置成了 <dubbo:parameter key="qos.port" value="33334"/>,究竟是怎么转换的呢?

答案应该在解析xml文件的时候,就针对这个配置文件进行自动拼接了,即 qos.port -> qosPort

JDK SPI

Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通 过SPI机制中约定好的信息进行查询相应的接口实现。

SPI遵循如下约定:

1、当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全 限定名”为命名的文件,内容为实现类的全限定名;

2、接口实现类所在的jar包放在主程序的classpath中;

3、主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下 的配置文件找到实现类的全限定名,把类加载到JVM;

4、SPI的实现类必须携带一个无参构造方法;

案例展示,一个简单的SPI例子

  1. 创建 api项目,定义一个接口 HelloService

  2. 创建impl项目,编写接口实现类 HumanHelloService

在资源目录下,创建一个META-INF.services包,并且创建一个文件,文件名就为:类名的全限定名, 在此文件中,填入两个实现类的完全限定名

  1. 创建main项目,引入两个两个项目

创建一个测试项目,使用ServiceLoader去加载系统中,所有实现了HelloService的类

final ServiceLoader<HelloService> helloServices=ServiceLoader.load(HelloService.class);

Dubbo SPI

下面使用dubbo进行 SPI的案例演示,下图为全项目结构

  1. 创建API项目,引入dubbo的坐标,在接口上面,添加dubbo的SPI注解
    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.5</version>
        </dependency>
    </dependencies>
  1. 创建impl项目,编写接口实现类 HumanHelloService,DogHelloService,创建一个META-INF.services包,并且创建一个文件,文件名就为:类名的全限定名, 在此文件中,填入两个实现类的完全限定名

注意:这里在完全限定名之前,需要带上一个key

3)创建main测试项目,引入两个工程

使用extensionLoader,通过接口,加载实现类

//获取扩展加载器 
ExtensionLoader<HelloService> extensionLoader=ExtensionLoader.getExtensionLoader(HelloService.class);

//遍历所有支持的扩展点 META-INF.dubbo
Set<String> extensions=extensionLoader.getSupportedExtensions();

for (String extension : extensions) {
  String result = extensionLoader.getExtension(extension).sayHello();
  System.out.println(result);
}
  1. 运行结果

Dubbo 为什么单独实现 SPI?

  1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加 载,会很浪费资源

  2. 如果有扩展点加载失败,则所有扩展点无法使用

  3. 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入

Dubbo 动态选择组件

  1. 创建接口,在sayHello中增加 Adaptive 注解,并且在参数中 提供URL参数.注意这里的URL参数的类为 org.apache.dubbo.common.URL

其中@SPI可以指定一个字符串参数,用于指明该SPI的默认实现。

  1. 实现类实现接口

  2. 编写DubboAdaptiveMain

注意点:

  • 因为在这里只是临时测试,所以为了保证URL规范,前面的信息均为测试值即可,关键的点在于 hello.service 参数,这个参数的值指定的就是具体的实现方式。关于为什么叫 hello.service 是因为这个接口的名称,其中后面的大写部分被dubbo自动转码为 . 分割。 我们的接口名称为HelloService,所以这里的URL地址参数就为 hello.service

  • 通过 getAdaptiveExtension 来提供一个统一的类来对所有的扩展点提供支持(底层对所有的扩展 点进行封装)。

  • 调用时通过参数中增加 URL 对象来实现动态的扩展点使用。

  • 如果URL没有提供该参数,则该方法会使用默认在 SPI 注解中声明的实现。

Dubbo负载均衡

如果想要实现负载均衡,则需要在消费者引用服务的注解上面,增加 loadbalance参数

注意:这里传入的是全部小写的字母

  • random 随机
  • roundrobin 轮询
  • leastactive 最少活跃调用数
  • consistenthash 一致性Hash

Dubbo 自定负载均衡器

  1. 创建一个工程loadbalance项目,新建一个类,继承dubbo的-LoadBalance接口

  2. 使用SPI的方式,新建一个文件名为:org.apache.dubbo.rpc.cluster.LoadBalance,文件内部填入新创建类的引用路径 onlyFirst=com.lagou.loadbalance.OnlyFirstLoadBalance

  3. 在Consumer项目中,直接引入即可

  4. 演示效果

Dubbo future异步调用

dubbo支持异步调用,使用异步调用的时候,使用xml配置的方法,在reference中,针对method,增加async参数。在main方法中,使用future模式进行获取数据 Future<Object> future= RpcContext.getContext().getFuture();

consumer.xml

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="service-consumer"/>

    <dubbo:registry address="zookeeper://xx.xx.xx.80:2181?timeout=30000"/>

    <dubbo:reference id="helloService" interface="com.lagou.service.HelloService">
        <dubbo:method name="sayHello" async="true"></dubbo:method>
    </dubbo:reference>
 
</beans>

XmlCosumerMain.java


import com.lagou.service.HelloService;
import org.apache.dubbo.rpc.RpcContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class XMLConsumerMain {
    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");

        HelloService service = context.getBean(HelloService.class);

        System.out.println("启动了消费端...");

        while (true) {
            System.in.read();
            String world = service.sayHello("world", 100);
            //利用future模式 来获取
            Future<Object> future= RpcContext.getContext().getFuture();
            System.out.println(world);
            System.out.println("future:"+future.get());
        }
    }
}

Dubbo控制台部署

1.从git 上下载项目 github.com/apache/dubb… 下载master分支

2.进入dubbo-admin项目,修改项目下的dubbo.properties文件

  • 注意dubbo.registry.address对应的值需要对应当前使用的Zookeeper的ip地址和端口号
  • dubbo.registry.address=zookeeper://zk所在机器ip:zk端口
  • dubbo.admin.root.password=root
  • dubbo.admin.guest.password=guest

3.切换到项目所在的路径,使用mvn 打包 mvn clean package -Dmaven.test.skip=true

4.进入dubbo-admin下面的target目录,java 命令运行 java -jar 对应的jar包

控制台启动完毕后,页面访问 http://localhost:7001/

正式进入主界面

zookeeper图像化客户端

  1. 下载地址:issues.apache.org/jira/secure…

  2. 进入到文件夹的build目录

  3. java -jar zookeeper-dev-ZooInspector.jar 启动客户端,填入zookeeper地址

  4. 看到了zookeeper的节点

服务降级

1. 在 dubbo 管理控制台配置服务降级

屏蔽和容错

  • mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。

确认屏蔽后,显示已屏蔽,屏蔽的按钮显示为 恢复

可以看到消费者直接返回 null

  • mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

在明确调用失败后,返回result,而不会直接中断程序,所有的接口都应该支持容错,

2. 指定返回简单值或者null

<dubbo:reference id="xxService" check="false" interface="com.xx.XxService"
timeout="3000" mock="return null" />

<dubbo:reference id="xxService2" check="false" interface="com.xx.XxService2" 
timeout="3000" mock="return 1234" />

如果是标注 则使用@Reference(mock="return null") @Reference(mock="return 简单值") 也支持 @Reference(mock="force:return null")

3.使用java代码 动态写入配置中心

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension() ;

Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://IP:端 口")); 

registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService? 
category=configurators&dynamic=false&application=foo&mock=force:return+null"));

4. 整合整合 hystrix