Dubbo 实战及源码分析

1. Dubbo 的四种配置方式

1.1 XML 配置

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

1.1.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://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<!-- 提供者的应用名 -->
<dubbo:application name="dubbo-server"  />

<!-- 使用ZK 注册中心的地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />

<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.bill.api.IHelloService" ref="helloService" timeout="20000" />

<!-- 和本地bean一样实现服务 -->
<bean id="helloService" class="com.bill.HellServiceImpl" />

1.1.2 服务消费者配置

<?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-4.3.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<!-- 消费者的应用名 -->
<dubbo:application name="dubbo-client"  />

<!-- 使用ZK 注册中心的地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!-- 生成远程服务代理 -->
<dubbo:reference id="helloService" interface="com.bill.api.IHelloService"/>

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” prot=“20880” /> 改写成
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 集群模式配置: <dubo:registry address=“zookeeper://192.168.0.0:2181?backup=192.168.1.1:2181,192.168.1.2:2181”

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

在这里插入图片描述

2.2 服务暴露

(1)在服务中心注册后,如何提供服务给消费者,使用 dubbo:service/ 标签进行服务暴露。
(2)<dubbo:service delay=“5000”/> //延迟 5 秒暴露服务:
<dubbo:service delay="-1"/> // 或者设置为-1 延迟到spring 初始化完成在暴露服务。
(3)使用 execute是限制服务并发量,不要超过服务器承载能力: <dubbo:service interface=“com.bill.xService” executes=“10”/>
或者限定接口中某个方法:
<dubbo:service interface=“com.bill,xService”>
<dubbo:method name=“sayHello” executes=“10”/>
</dubbo:service>
(4)Best Practice : 如果线程数据超过给定的值会报异常:
RejectedExecutionException: Thread pool is exhausted!
(5)为了保证服务的稳定性,除了限制并发线程,还可以限制服务端的连接数
<dubbo:provider protocol=“dubbo” accepts=“10”/>

2.3 引用服务

2.3.1 异步方式调用

(1)在默认情况下,使用同步方式远程调用。如果想使用一步方式,设置 async="true"
<dubbo:reference id=“helloService” interface=“com.a.b.ServiceA”>
<dubbo:method name=“sayHello” async=“true”/>
</dubb:reference>
异步调用是可以设置是否需要等待发送或返回值:
(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 中线程配置相关参数

<dubbo:protocol name=“dubbo” dispatcher=“all” threadpool=“fixed” threads=“100” accepts=“100”/>
参见 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 集群容错机制的原理

如果单个服务节点因为故障无法提供服务,可以根据配置的**集群容错模式,**调用其他的可用的服务节点,提高服务的可用性。
在这里插入图片描述

5.2 集群容错模式的配置

(1)服务者配置:
<dubbo:service cluster=“failfast”/>
(2)服务消费者
<dubbo:reference cluster=“failfast”/>

5.3 六种集群容错模式

(1)Failover (默认)
在调用失败后会自动切换,尝试调用其他节点。一些幂等性操作,比如读操作可以选择这个模式。
在服务端:
<dubbo:service retries=“2”/>
在消费端:
<dubbo:reference retries=“2”/>
在方法级别配置:
dubbo:reference
<dubbo:method name=“sayHello” retries=“2”/>
</dubbo:reference>
(2)FailFast
如果调用失败,忽略失败的调用,记录失败的调用到日志文件中。
(3)FailBack
记录失败的请求,定时重发。
(4)Forking
并行调用多个服务器,只要有一个成功便返回。

5.4 集群的负载均衡

5.4.1 负载均衡配置方式

(1)服务端:
<dubbo:service inteface="…" loadbalance=“roundrobin”>
(2)客户端:
<dubbo:reference="…" loadbalance=“roundrobin”/>
(3)方法级别:
dubbo:reference="..."
<dubb:method name="…" loadbalance="roundrobin/>
</dubbo:service/>

5.4.2 四种负载均衡策略

(1) 按照权重随机模式。
(2)按照权重轮训模式。
(3)最少活跃调用数。响应慢的收到更少的请求的一种调用。
(4)一致性Hash。带有相同参数的请求总是被发给同一个提供者。默认用160个节点,要修改成320,如下:
<dubbo: parameter key=“hash.nodes” value=“320”/>

8. 解剖Dubbo 源码及其实现

8.1 Dubbo 总体设计架构

(1)从服务模型的角度抽象出服务者和消费者。

在这里插入图片描述

(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文件

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
        xmlns="http://www.yihaomen.com/schema/people"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://www.yihaomen.com/schema/people"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">
    <xsd:import namespace="http://www.springframework.org/schema/beans" />

<xsd:element name="people">
    <xsd:complexType>
        <xsd:complexContent>
            <xsd:extension base="beans:identifiedType">
                <!--//复合元素之上以某个复合元素为基础,然后添加一些元素,具体的解释看http://www.w3school.com.cn/schema/schema_complex.asp; -->
                <xsd:attribute name="name" type="xsd:string" />
                <xsd:attribute name="age" type="xsd:int" />
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>
</xsd:element>

</xsd:schema>

(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 文件中应用

<?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:comtest="http://www.yihaomen.com/schema/people"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.yihaomen.com/schema/people http://www.yihaomen.com/schema/people.xsd ">
    <!-- //和spring.handlers先配置的连接一样 -->

<comtest:people id="cutesource" name="一号门" age="1024"/>
    </beans>

8.6 Dubbo 服务暴露的过程

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

8.8 集群容错和负载均衡

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

9. 一次RPC调用的过程

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值