Dubbo 实战及源码分析

1. Dubbo 的四种配置方式

1.1 XML 配置

Dubbo 是使用Spring 的 Shema 进行扩展标签和解析配置,所以我们能像 Spring 的 XML 配置方式一样进行配置。

1.1.1 服务提供者配置

















1.1.2 服务消费者配置












1.1.3 配置的优先级关系

(1)reference method > serivice method > reference > service > consumer > service。总结下来是:方法级优先,接口次之,全局再次之。级别一样,消费者优先,提供者次之。
(2)Best Practice:由服务提供者设置超时。消费者不需要关心超时时间。

1.2 属性配置

(1)Dubbo 会自动加载classpath 根目录下的 dubbo.properties 文件。
(2)
dubbo.application,name=dubbo-server
dubbo.application.owner=test
dubbo.registry.address=zookeeper://127.0.0.1:2181
(3)属性配置遵循以下约定:
将 XML 配置的标签+属性名,用点分割,多个属性拆成多个行。
e.g. 改写成
dubbo:protocol.name=dubbo 和 dubbo:protocol.port=20880
(4)XML 配置的优先级 > 属性配置

1.3 API 配置

我们可以通过代码调用 Dubbo 的 API 进行配置,一般用于 Test, Mock 等。

1.4 注解配置

Dubbo 在 2.5.7 版本之后新增注解方式配置,更像SpringBoot 配置。

1.4.1 对服务提供者的配置

(1) 使用 java config 形式配置公共模块:

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DubboConfiguration {

public ApplicationConfig applicationConfig() {
    ApplicationConfig applicationConfig = new ApplicationConfig();
    applicationConfig.setName("dubbo-server");
    return applicationConfig;
}

@Bean
RegistryConfig registryConfig() {
    RegistryConfig registryConfig = new RegistryConfig();
    registryConfig.setAddress("zookeeper://127.0.0.1:2181");
    registryConfig.setClient("curator");
    return registryConfig;
}
}

(2)使用Service 注解暴露服务:

import com.alibaba.dubbo.config.annotation.Service;
import com.bill.api.IHelloService;

@Service(timeout = 5000)
public class HellServiceImpl implements IHelloService {

    @Override
    public String sayHello(String name) {
        return name;
    }
}

(3)指定 Dubbo 的扫描路径:

import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;

@DubboComponentScan(basePackages = "com.bill.service")
public class ServerApp {
    // springboot 启动

}

1.4.2 对服务消费者的配置

(1)使用 Java Cofig 形式配置公共模块:

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DubboConfiguration {

@Bean
public ApplicationConfig applicationConfig() {
    ApplicationConfig applicationConfig = new ApplicationConfig();
    applicationConfig.setName("dubbo-client");
    return applicationConfig;
}

@Bean
public ConsumerConfig consumerConfig() {
    ConsumerConfig consumerConfig = new ConsumerConfig();
    consumerConfig.setTimeout(3000);
    return consumerConfig;
}

}

(2)使用 Reference 注解引用服务:

import com.alibaba.dubbo.config.annotation.Reference;
import com.bill.api.IHelloService;

public class ClientService {

@Reference
public IHelloService helloService;

public void doSomething() {
    helloService.sayHello("helloWorld");
}
}

(3)指定 Dubbo 的扫描路径:

@DubboComponentScan(basePackages = "com.bill.service")
public class ServerApp {
    // springboot 启动
}

2. 服务的注册与发现

2.1 ZK 注册中心

(1)Zookeeper 注册中心。 ZK 是一个数型的目录服务,为分布式应用提供一致性服务。(目前生产推荐使用方式)
(2)ZK 集群模式配置

2.1.1 Dubbo 如何在ZK 中建立目录结构

Dubbo 实战及源码分析_第1张图片

2.2 服务暴露

(1)在服务中心注册后,如何提供服务给消费者,使用 dubbo:service/ 标签进行服务暴露。
(2) //延迟 5 秒暴露服务:
// 或者设置为-1 延迟到spring 初始化完成在暴露服务。
(3)使用 execute是限制服务并发量,不要超过服务器承载能力:
或者限定接口中某个方法:



(4)Best Practice : 如果线程数据超过给定的值会报异常:
RejectedExecutionException: Thread pool is exhausted!
(5)为了保证服务的稳定性,除了限制并发线程,还可以限制服务端的连接数
accepts=“10”/>

2.3 引用服务

2.3.1 异步方式调用

(1)在默认情况下,使用同步方式远程调用。如果想使用一步方式,设置 async="true"

async=“true”/>

异步调用是可以设置是否需要等待发送或返回值:
(2) sent=“true”: 等待消息发出,发送失败抛异常。
(3) sent=“false”: 不等待消息发出,将消息放在I/O队列,即刻返回。
(4)return=“falsue”: 完全忽略返回。

2.3.2 远程调用回调函数

(1) oninvoke() : 发起远程调用前触发。
(2)onreturn():远程调用之后的回调事件。
(3) onthrow(): 远程调用出现异常时触发。在该事件中发现服务降级

public class NotifyImpl implements INotify {

@Override
public void onreturn(String resStr, String inStr) {
    //do something
}

@Override
public void onthrow(Throwable ex, String inStr) {
    // do something
}
}

在这里插入图片描述

(4) 异步回调:async=true onreturn=“xxxx”
同步回调 : async=false onreturn=“xxx”
异步无回调:async=true
同步无回调:async=false

4. Dubbo 中高效的I/O 线程模型

4.1 对 Dubbo I/O 模型的分析

(1)Dubbo 的服务提供者有两种线程池类型:I/O处理线程池,业务调度线程池。
(2)I/O 线程数是核数+1,服务调用的线程数默认是200。
(3)如果事务处理能迅速处理,直接在I/O上处理。如果事务处理慢,或者要发起新的I/O, 比如查询数据库,就派发到事务线程池中处理。

4.2 Dubbo 中线程配置相关参数


参见 http://dubbo.apache.org/zh-cn/docs/user/demos/thread-model.html

4.3 在Dubbo 线程方面踩过的坑

(1)**线程数设置过少的问题。**适当增加线程数解决。Dubbo 默认是200 个线程。
Caused by: java.util.concurrent.RejectExecutionException:thread pool is EXHAUSTED!
(2)**线程数设置太多。**线程数设置太多,会受Linux 用户线程数(默认1024)的限制, 通常用 ulimit -u 解决。
java.lang.OutOfMemoryError: unable to create new native thread.
(3)**连接不上服务器。**可能是超过了服务端最大允许的连接数。可以通过设置 accepts 值解决。

4.4 对Dubbo 线程池使用的建议

(1)在消费者和提供者之间默认只会建立一个TCP长连接。生产上,不建议在消费端设置 connections 属性 去增加连接数。
(2)I/O 线程是异步读取数据,所以它消耗更多的CPU资源,默认为CPU个数+1 比较合理。
(3)数据在被读取和反序列化后,会交给业务线程池处理,业务线程池没有设置等待队列,失败后快速重试其他服务提供者,而不是排队。

5. 集群的容错机制与负载均衡

5.1 集群容错机制的原理

如果单个服务节点因为故障无法提供服务,可以根据配置的**集群容错模式,**调用其他的可用的服务节点,提高服务的可用性。
Dubbo 实战及源码分析_第2张图片

5.2 集群容错模式的配置

(1)服务者配置:

(2)服务消费者

5.3 六种集群容错模式

(1)Failover (默认)
在调用失败后会自动切换,尝试调用其他节点。一些幂等性操作,比如读操作可以选择这个模式。
在服务端:
retries=“2”/>
在消费端:

在方法级别配置:
dubbo:reference


(2)FailFast
如果调用失败,忽略失败的调用,记录失败的调用到日志文件中。
(3)FailBack
记录失败的请求,定时重发。
(4)Forking
并行调用多个服务器,只要有一个成功便返回。

5.4 集群的负载均衡

5.4.1 负载均衡配置方式

(1)服务端:

(2)客户端:

(3)方法级别:
dubbo:reference="..."
Dubbo 实战及源码分析_第3张图片

(2)纵向分层
服务接口层(Service), 配置层(Config), 服务代理层(Proxy),服务注册层(Registry),集群层(Cluster),监控层(Monitor),远程调用层(Protocol)。信息交换层(Exchange),网络传输层(Transport),数据序列化层(Serialize)。

(3)SPI (Service Provider Interface) 是java 提供的一种服务加载方式。在Jar 包META-INF/Services/中创建一个用接口命名的文件。

8.2 配置文件

Dubbo 的配置文件采用Spring schema 形式扩展的。
(1)设置配置属性和JavaBean

public class People {

private String name;
private int age;
private String id;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}
}

(2)编写XSD文件



    


    
        
            
                
                
                
            
        
    

(3)编写 NamespaceHandler 和 BeanDefinitionParser来解析上面的文件。

public class MyNamespaceHandler extends NamespaceHandlerSupport {

@Override
public void init() {
    registerBeanDefinitionParser("people", new PeopleBeanDefinitionParser());
}

}

public class PeopleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{

@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
    String name = element.getAttribute("name");
    String age = element.getAttribute("age");
    String id = element.getAttribute("id");
    if (StringUtils.hasText(id)) {
        builder.addPropertyValue("id", id);
    }
    if (StringUtils.hasText(name)) {
        builder.addPropertyValue("name", name);
    }
    if (StringUtils.hasText(age)) {
        builder.addPropertyValue("age", age);
    }
}

@Override
protected Class getBeanClass(Element element) {
    return People.class;
}
}

(4) 编写spring.handlers 和 spring.schemas 来串联所有部件。
spring.handler

http\://www.yihaomen.com/schema/people=com.dubbotest.xml.MyNamespaceHandler

spring.schemas

http\://www.yihaomen.com/schema/people.xsd=META-INF/people.xsd

(5)在spring bean 文件中应用



    


    

8.6 Dubbo 服务暴露的过程

(1)ServiceBean 是Dubbo配置标签 的实现。它实现了Spring 的 InitializingBean 的 afterPropertiesSet(),在afterPropertiesSet() 方法中引用了export 的 doExportUrls(), 向服务中心注册服务和暴露服务。
(2)使用Dubbo 的SPI ,Javassist 动态代理生成服务类对象。

8.8 集群容错和负载均衡

参见我的集群容错和负载均衡源码学习。

9. 一次RPC调用的过程

(1)比如调用helloService.sayHello 方法。消费者配置消费的接口, zk 地址。



(2)消费者调用本地的helloService接口的sayHello方法。
(3)sayHello方法会被Java 动态代理拦截,生成一个Invoker实例,实例里面封装了协议类型(dubbo协议),确定数据结构:类名,方法名,参数类型,参数值,requestId, 超时时间 等等。
(4)序列化:把数据结构对象转化为二进制编码。hessian序列化。
(5)通信。 netty.
(6)服务提供端会反序列化请求,根据服务接口的配置,选择负载均衡策略,集群容错的策略。
(7)服务店的server,sayhello方法执行完成后,把requestId, 返回值序列化。发送给服务调用方。

你可能感兴趣的:(dubbo)