Dubbo基础学习

一、基础知识

1、分布式基础理论

1.1 什么是分布式系统

分布式系统(distributed system)是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。是建立在网络之上的软件系统

分布式系统是由多台计算机(或节点)协同工作,通过网络进行通信和协调,以完成共同目标的系统。

在分布式系统中,每个计算机节点都是相对独立的,它们可以运行不同的操作系统,部署不同的应用程序,并且分布在不同的地理位置。这些节点通过网络进行通信,共同协作完成某个任务或提供某项服务。

设计和开发分布式系统是一项复杂的任务,需要考虑诸多因素,如数据一致性、通信协议、容错机制等。分布式系统的优势在于它可以提供更高的可伸缩性、灵活性和可用性,但也伴随着挑战,如数据一致性、通信延迟、故障处理等问题需要精心解决。

1.2 分布式系统的特点

分布式系统的特点包括:

  1. 分布性: 系统中的各个组件分布在不同的计算机上,通过网络连接进行通信。
  2. 并发性: 多个节点可以同时执行任务,实现并发处理,提高系统的性能。
  3. 独立性: 各个节点相对独立,它们可以拥有自己的本地存储、资源和执行环境。
  4. 透明性: 分布式系统应该对用户和应用程序来说是透明的,用户无需关心系统中各个节点的具体情况。
  5. 可靠性: 分布式系统通常设计为具有高可用性和容错性,即使某个节点发生故障,系统仍能继续运行。
  6. 扩展性: 分布式系统应该能够方便地扩展,以适应不同规模和负载的需求。

分布式系统的应用场景非常广泛,涉及到云计算、大数据处理、微服务架构、分布式数据库、分布式存储系统等领域。典型的例子包括云服务提供商的基础设施、社交媒体平台、电子商务系统等。

1.3 发展演变

在这里插入图片描述

  • 单一应用架构
    在这里插入图片描述

  • 垂直的应用架构(应用之间需要交互)在这里插入图片描述

  • 分布式的服务架构(服务器利用率低效)在这里插入图片描述

  • 流动计算架构(负载均衡)在这里插入图片描述

1.4 RPC远程过程调用

RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,它允许程序调用另一台计算机上的过程或函数,就像调用本地的过程一样,而无需显式处理网络细节。RPC 是分布式系统中实现远程服务调用的一种方式,它使得在不同计算机上运行的程序能够通过网络进行通信。

一般来说,RPC 的工作流程如下:

  1. 客户端调用: 客户端程序调用本地的客户端存根(stub)来执行远程调用。
  2. 参数传递: 客户端存根将调用的参数打包成网络消息,并通过网络发送给远程服务器。
  3. 服务器处理: 服务器接收到消息后,将消息解包,调用本地的服务存根执行实际的过程。
  4. 结果返回: 服务器将执行结果打包成网络消息,通过网络发送给客户端。
  5. 客户端接收: 客户端接收到消息后,将结果解包,返回给调用方。

这个过程使得分布在不同计算机上的程序能够像调用本地函数一样调用远程的函数,从而实现了透明的远程调用。

RPC的使用可以简化分布式系统中服务之间的通信,使得开发者能够更方便地构建分布式系统。
在这里插入图片描述

在RPC的实现中,常见的有多种协议和框架,例如:

  1. gRPC: 由Google开发,基于HTTP/2和Protocol Buffers,支持多语言。
  2. Apache Thrift: 由Apache开发,支持多语言,使用IDL(接口定义语言)描述服务接口。
  3. Java RMI(Remote Method Invocation): Java语言特有的RPC实现,支持Java对象的远程调用。
  4. CORBA(Common Object Request Broker Architecture): 一种面向对象的分布式计算系统,支持多语言。
  5. JSON-RPC 和 XML-RPC: 使用 JSON 或 XML 作为数据交换格式的简单RPC协议。

这些RPC框架和协议提供了不同的特性和适用场景,选择适合项目需求的RPC实现是很重要的。

2、Dobbu核心概念

2.1 简介

Dubbo官网文档:Apache Dubbo

Dubbo(Apache Dubbo)是一个高性能、轻量级的开源Java框架,用于构建分布式服务架构。它提供了一套完整的分布式服务治理方案,包括服务的注册与发现、负载均衡、容错机制、远程调用等功能。Dubbo最初由阿里巴巴公司开发,并于2011年成为Apache软件基金会的一个顶级项目。

以下是 Dubbo 的一些主要特点和组件:

  1. 服务自动注册与发现: Dubbo 提供了服务注册中心,服务提供者在启动时将自己注册到注册中心,服务消费者通过注册中心获取服务提供者的地址信息服务实例上下线实时感知,实现服务的动态发现
    在这里插入图片描述

  2. 面向接口代理的高性能RPC远程调用: Dubbo 支持基于RPC(Remote Procedure Call)的远程服务调用,使得服务提供者和服务消费者可以在分布式环境中进行通信。提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节在这里插入图片描述

  3. 智能负载均衡: Dubbo 提供多种负载均衡策略,包括随机、轮询、一致性哈希等,以确保请求能够均匀分布到各个服务提供者(服务器)。智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量

  4. 容错机制: Dubbo 支持多种容错机制,包括失败自动切换、失败快速失败、失败自动恢复等,提高系统的稳定性和容错性。

  5. 高度可扩展能力: Dubbo 的架构设计非常可扩展,可以方便地集成其他框架和中间件,例如,可以与Spring框架无缝集成。遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。

  6. 可视化的服务治理与运维: Dubbo 提供了丰富的服务治理功能,包括监控、统计、限流、路由等,帮助开发者更好地管理和维护分布式服务系统。提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数

  7. 运行期流量调度:内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布(慢慢从旧服务转换为新服务的过程),同机房优先等功能。

  8. 支持多协议: Dubbo 不仅支持基于RPC的Dubbo协议,还支持REST、HTTP等多种协议,增加了对不同场景的适用性。

Dubbo 在构建大规模分布式系统时发挥着重要的作用,特别是在微服务架构中。它提供了一套完整而灵活的解决方案,使得开发者可以更轻松地构建、部署和管理分布式服务。

2.2 Dobbu的设计架构

高性能的Java RPC框架
在这里插入图片描述

3、Dobbu环境搭建

3.1 Zookeeper注册中心

Zookeeper官网:Apache ZooKeeper

zookeeper3.4.1下载地址:Index of /dist/zookeeper/zookeeper-3.4.13 (apache.org)

Dubbo官方文档: http://dubbo.apache.org/en-us/docs/user/quick-start.html

在bin文件下,启动zkServer.cmd会有报错,处理需要在condif文件中将zoo_sample.cfg文件复制一份,将名字改为zoo.cfg。
在zookeeper的文件夹下创建data文件夹,打开zoo.cfg,修改datadir,将dataDir数据保存为我们自定义的文件中(此步骤可省略)

配置完毕后,我们再次在conf下启动zkServer.cmd,这次可以成功启动

继续运行zkCli.cmd,可以连接到zookeeper的服务器。

此时,我们zookeeper的注册中心及环境以及搭建完毕。

3.2 Dobbu监控中心

下载地址:apache/dubbo-admin: The ops and reference implementation for Apache Dubbo (github.com)

1、下载dubbo-admin

dubbo-admin下载地址 :https://github.com/apache/dubbo-admin/tree/master

2、解压后进入目录修改指定zookeeper地址
进入如下地址:dubbo-admin-master\dubbo-admin\src\main\resources\application.properties"
将zookeeper的监控中心的地址配置为本地端口

#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

配置完毕后,我们在dubo-zookeeper\dubbo-admin-master\dubbo-admin文件夹下cmd打包测试下。

mvn clean package
在target文件中打包完成的jar包

cmd命令 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar运行打包好的jar包
启动成功后,可以看到一个7001的端口

此时我们的zookeeper的服务都为启动状态,在浏览器中访问 localhost:7001,访问到注册中心,输入账号密码root。

此时,我们zookeeper监控中心的配置完成。注意,要访问到监控中心,一定要启动zookeeper注册中心的启动类

4、dubbo-helloworld

让服务提供者注册到注册中心(暴露服务)

1.导入dubbo依赖(spring底层)和引入zookeeper客户端(curator-framework )

<!-- 导入dubbo依赖 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>
<!-- 引入zookeeper客户端 -->
<!-- 2.6版本前后用这个 -->
<dependency>
	<groupId>com.101tec</groupId>
	<artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>
<!-- 2.6版本之后用这个 -->
<dependency>
	<groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>

2、用spring配置声明暴露服务(provider.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://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
		http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <!--1、指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
   <dubbo:application name="user-service-provider"></dubbo:application>
    <!--2、指定注册中心的位置-->
    <!--<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>-->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
    <!--3、指定通信规则(通信协议? 服务端口)-->
    <dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
    <!--4、暴露服务 让别人调用 ref指向服务的真正实现对象-->
    <dubbo:service interface="com.lemon.gmail.service.UserService" ref="userServiceImpl"></dubbo:service>

    <!--服务的实现-->
    <bean id="userServiceImpl" class="com.lemon.gmail.service.impl.UserServiceImpl"></bean>
</beans>

3、编写一个ProviderApplication启动类程序,运行测试配置

public class MailApplication {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext("provider.xml");
        applicationContext.start();
        System.in.read();
    }
}

让服务消费者去注册中心订阅服务提供者的服务地址

1、在 order-service-consumer 服务消费者项目中引入依赖

  <!--dubbo-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.2</version>
        </dependency>
        <!--注册中心是 zookeeper,引入zookeeper客户端-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>

2、创建consumer.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://dubbo.apache.org/schema/dubbo"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
		http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
   <!--包扫描-->
    <context:component-scan base-package="com.lemon.gmail.service.impl"/>

    <!--指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
    <dubbo:application name="order-service-consumer"></dubbo:application>
    <!--指定注册中心的位置-->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>

    <!--调用远程暴露的服务,生成远程服务代理-->
    <dubbo:reference interface="com.lemon.gmail.service.UserService" id="userService"></dubbo:reference>
</beans>

3、把当前OrderServiceImpl实现类中加上注解

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    public UserService userService;
    public void initOrder(String userID) {
        //查询用户的收货地址
        List<UserAddress> userAddressList = userService.getUserAddressList(userID);
        
        //为了直观的看到得到的数据,以下内容也可不写
        System.out.println("当前接收到的userId=> "+userID);
        System.out.println("**********");
        System.out.println("查询到的所有地址为:");
        for (UserAddress userAddress : userAddressList) {
            //打印远程服务地址的信息
            System.out.println(userAddress.getUserAddress());
        }
        
    }
}

4、编写一个ConsumerApplication启动类程序,运行测试配置

public class ConsumerApplication {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");
        OrderService orderService = applicationContext.getBean(OrderService.class);

        //调用方法查询出数据
        orderService.initOrder("1");
        System.out.println("调用完成...");
	    System.in.read();
    }
}

注意:消费者的运行测试需要先启动提供者。
启动服务提供者、消费者。及zookeeper的和dubbo-admin,查看监控信息。
localhost:7001

5、监控中心

dubbo-monitor-simple 监控服务调用的相关信息

下载地址:https://github.com/apache/incubator-dubbo Dubbo OPS

进入dubbo-monitor-simple文件,执行cmd命令,mvn package打包成jar包
将 dubbo-monitor-simple-2.0.0-assembly.tar.gz 压缩包解压至当前文件夹,解压后config文件查看properties的配置是否是本地的zookeeper。
打开解压后的 assembly.bin 文件,start.bat 启动dubbo-monitor-simple监控中心
在浏览器 localhost:8080 ,可以看到一个监控中心。
在服务提供者和消费者的xml中配置以下内容,再次启动服务提供和消费者启动类。

<!--dubbo-monitor-simple监控中心发现的配置-->
<dubbo:monitor protocol="registry"></dubbo:monitor>
<!--<dubbo:monitor address="127.0.0.1:7070"></dubbo:monitor>-->

6、整合springboot

版本对应:
在这里插入图片描述
说明文档:https://github.com/apache/incubator-dubbo-spring-boot-project

创建Maven项目 boot-user-service-provider 服务提供者
导入以下依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath />
    </parent>

    <dependencies>
        <dependency>
            <groupId>com.lemon.gmail</groupId>
            <artifactId>gmail-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
    </dependencies>

把 user-service-provider 中的service拿到此项目中。
注意,以此方法为返回的需要更改 interface包中的void为 List

@Service//dubbo的服务暴露
@Component
public class UserServiceImpl implements UserService {
	public List<UserAddress> getUserAddressList(String userId) {

		UserAddress address1 = new UserAddress(1, "河南省郑州巩义市宋陵大厦2F", "1", "安然", "150360313x", "Y");
		UserAddress address2 = new UserAddress(2, "北京市昌平区沙河镇沙阳路", "1", "情话", "1766666395x", "N");

		return Arrays.asList(address1,address2);
	}
}

配置 application.properties

dubbo.application.name=boot-user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

#连接监控中心
dubbo.monitor.protocol=registry

BootProviderApplication 启动类配置

@EnableDubbo //开启基于注解的dubbo功能
@SpringBootApplication
public class BootProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootProviderApplication.class, args);
    }
}

启动注册中心,启动当前服务提供者,可以在浏览器看到一个服务提供者。

boot-order-service-consumer 服务消费者
创建Maven项目 boot-order-service-consumer 服务消费者
导入以下依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>com.lemon.gmail</groupId>
            <artifactId>gmail-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
    </dependencies>

把order-service-consumer项目中的service复制到当前项目。

@Service//(暴露服务)
public class OrderServiceImpl implements OrderService {

    @Reference//引用远程提供者服务(消费服务)
    UserService userService;

    public List<UserAddress> initOrder(String userID) {
        //查询用户的收货地址
        List<UserAddress> userAddressList = userService.getUserAddressList(userID);

        System.out.println("当前接收到的userId=> "+userID);
        System.out.println("**********");
        System.out.println("查询到的所有地址为:");
        for (UserAddress userAddress : userAddressList) {
            //打印远程服务地址的信息
            System.out.println(userAddress.getUserAddress());
        }
        return userAddressList;
    }
}

创建 OrderController 控制器

@Controller
public class OrderController {
    @Autowired
    OrderService orderService;

    @RequestMapping("/initOrder")
    @ResponseBody
    public List<UserAddress> initOrder(@RequestParam("uid")String userId) {
        return orderService.initOrder(userId);
    }
}

创建application.properties 配置

server.port=8081
dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181

#连接监控中心 注册中心协议
dubbo.monitor.protocol=registry

BootConsumerApplication 启动类创建

@EnableDubbo //开启基于注解的dubbo功能
@SpringBootApplication
public class BootConsumerApplication {
    public static void main(String[] args){
        SpringApplication.run(BootConsumerApplication.class,args);
    }
}

配置完毕,此时启动zookeeper注册中心及监控。
启动springboot配置的服务提供者和消费者
在浏览器输入 localhost:7001 查看结果

http://localhost:8081/initOrder?uid=1 查询到地址信息

二、dubbo配置

dubbo配置官网参考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-service.html

1、 配置原则、覆盖策略

在这里插入图片描述

JVM 启动 -D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口。
XML 次之,如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效。
Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。

2、 启动时检查

在 Dubbo 中,配置启动时检查的配置项通常位于 dubbo.properties 或者 dubbo.xml 配置文件中,可以使用以下配置项进行设置:

(1) dubbo.consumer.check 用于配置 Dubbo 消费者是否启用启动时检查。可以设置为 truefalse,默认值为 true

dubbo.consumer.check=true

(2) dubbo.provider.check 用于配置 Dubbo 提供者是否启用启动时检查。可以设置为 truefalse,默认值为 true

dubbo.provider.check=true

这些配置项的含义如下:

  • dubbo.consumer.check 设置为 true 时,Dubbo 消费者在启动时会检查依赖的服务是否可用。
  • dubbo.provider.check 设置为 true 时,Dubbo 提供者在启动时会检查其依赖是否可用。

示例 XML 配置:

<dubbo:application name="example-consumer" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:consumer check="true" />

<!-- 其他配置 -->

上述示例中,<dubbo:consumer check="true" /> 配置项表示启用 Dubbo 消费者的启动时检查。

需要注意的是,如果启动时检查发现依赖服务不可用,Dubbo 将在启动时抛出异常,阻止服务的正常启动。这有助于及早发现配置或依赖问题,提高服务的可用性。

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=“true”。

可以通过 check=“false” 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check=“false”,总是会返回引用,当服务恢复时,能自动连上。

以order-service-consumer消费者为例,在consumer.xml中添加配置

<!--配置当前消费者的统一规则,当前所有的服务都不启动时检查-->
 <dubbo:consumer check="false"></dubbo:consumer>

提供了一种配置启动时检查的机制,这可以确保 Dubbo 服务在启动时能够检查和验证依赖的各种配置项、依赖服务是否可用,以及其他必要的条件是否满足。配置启动时检查有助于在系统启动阶段就发现潜在的问题,提高系统的稳定性。

3、 timeout超时设置

默认:1000ms

全局超时配置
<dubbo:provider timeout="5000" />

指定接口以及特定方法超时配置
<dubbo:provider interface="com.foo.BarService" timeout="2000">
    <dubbo:method name="sayHello" timeout="3000" />
</dubbo:provider>

无论是通过配置文件还是代码,设置超时的目的是为了在服务调用时避免等待时间过长,增强系统的健壮性。实际的超时时间需要根据具体的业务场景和网络环境进行调整。

配置覆盖关系:

  • 方法级优先,接口级次之,全局配置再次之,即小Scope优先(精确优先)
  • 如果级别一样,则消费方优先,提供方次之(消费者设置优先)

在这里插入图片描述

4、 retries重试次数

在 Dubbo 中,可以通过 retries 参数设置服务调用的重试次数。retries 参数表示当远程服务调用失败时,Dubbo 将会自动重试的次数。这个参数可以用于增强系统的容错性,尤其是在网络不稳定或者服务提供者不稳定的情况下。

以下是在 Dubbo 中设置 retries 参数的两种方式:通过 Dubbo 配置文件和通过代码。

(1)通过 Dubbo 配置文件设置 retries

<!-- dubbo-consumer.xml -->
<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.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 配置服务消费者 -->
    <dubbo:consumer retries="3"> <!-- 设置重试次数为3次 -->
        <!-- 声明需要引用的服务接口 -->
        <dubbo:reference id="productService" interface="com.example.ProductService"/>
    </dubbo:consumer>

    <!-- 配置订单服务 -->
    <bean id="orderService" class="com.example.OrderService">
        <!-- 注入商品服务 -->
        <property name="productService" ref="productService"/>
    </bean>
</beans>

(2) 通过代码设置 retries

在服务消费者的代码中,可以通过 ReferenceConfig 对象设置重试次数:

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import com.example.ProductService;

public class OrderService {
    public static void main(String[] args) {
        // 配置应用信息
        ApplicationConfig application = new ApplicationConfig();
        application.setName("order-service-consumer");

        // 配置注册中心信息
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://127.0.0.1:2181");

        // 配置服务引用
        ReferenceConfig<ProductService> reference = new ReferenceConfig<>();
        reference.setApplication(application);
        reference.setRegistry(registry);
        reference.setInterface(ProductService.class);
        reference.setRetries(3); // 设置重试次数为3次

        // 引用服务
        ProductService productService = reference.get();

        // 下单逻辑
        Long productId = 1L;
        productService.getProductById(productId);
    }
}

在上述代码中,通过 reference.setRetries(3) 设置重试次数为3次。

需要注意的是,重试机制会导致请求可能被执行多次,因此在一些幂等性(执行多次的操作和执行依次是一样的:查询、删除)的操作上使用重试是比较安全的(不能用于非幂等操作)。具体的重试次数需要根据业务场景和网络环境的实际情况来进行调整。

5、 version多版本

在 Dubbo 中,可以通过多版本(Multiple Versions)的特性来支持在同一接口上发布不同版本的服务。这样做的主要目的是允许服务提供者在升级服务时保留旧版本,同时允许消费者选择使用特定的服务版本。实现灰度发布

以下是在 Dubbo 中配置多版本的示例:

(1) 通过 Dubbo 配置文件设置多版本:

<!-- dubbo-provider.xml -->
<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.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 配置服务提供者 -->
    <dubbo:service interface="com.example.ProductService" ref="productService" version="1.0.0"/>
    <!-- 配置同一接口的另一个版本 -->
    <dubbo:service interface="com.example.ProductService" ref="productServiceV2" version="2.0.0"/>

    <!-- 配置商品服务实现 -->
    <bean id="productService" class="com.example.ProductServiceImpl"/>
    <!-- 配置另一个版本的商品服务实现 -->
    <bean id="productServiceV2" class="com.example.ProductServiceV2Impl"/>
</beans>

在上述配置中,通过 version 属性为不同版本的服务指定了版本号。

(2) 通过代码设置多版本:

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import com.example.ProductService;
import com.example.ProductServiceImpl;
import com.example.ProductServiceV2;
import com.example.ProductServiceV2Impl;

public class ServiceProvider {
    public static void main(String[] args) {
        // 配置应用信息
        ApplicationConfig application = new ApplicationConfig();
        application.setName("product-service-provider");

        // 配置注册中心信息
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://127.0.0.1:2181");

        // 配置服务提供者1
        ServiceConfig<ProductService> service = new ServiceConfig<>();
        service.setApplication(application);
        service.setRegistry(registry);
        service.setInterface(ProductService.class);
        service.setRef(new ProductServiceImpl());
        service.setVersion("1.0.0"); // 设置版本号
        service.export();

        // 配置服务提供者2
        ServiceConfig<ProductServiceV2> serviceV2 = new ServiceConfig<>();
        serviceV2.setApplication(application);
        serviceV2.setRegistry(registry);
        serviceV2.setInterface(ProductServiceV2.class);
        serviceV2.setRef(new ProductServiceV2Impl());
        serviceV2.setVersion("2.0.0"); // 设置版本号
        serviceV2.export();
    }
}

在上述代码中,通过 service.setVersion("1.0.0")serviceV2.setVersion("2.0.0") 设置了不同版本的服务的版本号。

使用多版本的优势在于,它允许服务提供者和服务消费者在服务升级时进行平滑过渡,新版本的服务可以与旧版本并存,而消费者可以选择使用特定的版本。在服务引用时,可以通过设置 version 属性指定要引用的版本。例如:

<!-- dubbo-consumer.xml -->
<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.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 配置服务消费者 -->
    <dubbo:consumer version="1.0.0">
        <!-- 声明需要引用的服务接口 -->
        <dubbo:reference id="productService" interface="com.example.ProductService"/>
    </dubbo:consumer>

    <!-- 配置订单服务 -->
    <bean id="orderService" class="com.example.OrderService">
        <!-- 注入商品服务 -->
        <property name="productService" ref="productService"/>
    </bean>
</beans>

在上述配置中,通过 version="1.0.0" 指定了服务引用的版本。

6、 本地存根stub

在这里插入图片描述

在 Dubbo 中,本地存根(Local Stub)是一种在服务消费者端创建的一个本地代理,用于对远程服务进行封装和扩展。本地存根的目的是在客户端对远程服务进行一些预处理、缓存或其他特殊处理,以提高客户端的性能或实现一些定制化的逻辑

实现本地存根:

// ProductServiceStub.java
public class ProductServiceStub implements ProductService {
    private final ProductService productService;

    // 构造函数,传入真实的远程服务实现
    public ProductServiceStub(ProductService productService) {
        this.productService = productService;
    }

    @Override
    public Product getProductById(Long productId) {
        // 在实际远程调用之前,可以在这里进行一些本地的处理
        System.out.println("本地存根处理:在调用远程服务前的一些逻辑");

        // 调用真实的远程服务
        Product product = productService.getProductById(productId);

        // 在实际远程调用之后,可以在这里进行一些本地的处理
        System.out.println("本地存根处理:在调用远程服务后的一些逻辑");

        return product;
    }
}

配置 Dubbo 服务提供者:

<!-- dubbo-provider.xml -->
<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.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 配置服务提供者 -->
    <dubbo:service interface="com.example.ProductService" ref="productService"/>

    <!-- 配置商品服务实现 -->
    <bean id="productService" class="com.example.ProductServiceImpl"/>
</beans>

配置 Dubbo 服务消费者:

<!-- dubbo-consumer.xml -->
<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.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 配置服务消费者 -->
    <dubbo:consumer>
        <!-- 声明需要引用的服务接口,并指定本地存根 -->
        <dubbo:reference id="productService" interface="com.example.ProductService">
            <dubbo:stub interface="com.example.ProductService" ref="productServiceStub"/>
        </dubbo:reference>
    </dubbo:consumer>

    <!-- 配置本地存根 -->
    <bean id="productServiceStub" class="com.example.ProductServiceStub">
        <!-- 注入真实的远程服务实现 -->
        <property name="productService" ref="productService"/>
    </bean>

    <!-- 配置订单服务 -->
    <bean id="orderService" class="com.example.OrderService">
        <!-- 注入商品服务 -->
        <property name="productService" ref="productService"/>
    </bean>
</beans>

在上述配置中,通过 <dubbo:stub> 元素指定了引用服务时使用的本地存根。本地存根实现(ProductServiceStub)中可以在远程调用前后执行一些本地的逻辑。

这样配置完成后,Dubbo 在服务引用时将会使用本地存根来对远程服务进行代理,可以在本地存根中添加一些逻辑来处理远程服务调用前后的操作。

7、dubbo与springboot整合的三种方式

1.导入dubbo-starter。在application.properties配置属性,使用@Service【暴露服务】,使用@Reference【引用服务】,使用@Enabledubbo注解开启dubbo功能

2.保留dubbo.xml配置文件。导入dubbo-starter,使用@ImportResource导入dubbo配置文件即可

3.使用注解API的方式:将每一个组件手动创建到容器中,让dubbo扫描组件

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
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-demo-provider");
        return applicationConfig;
    }

    // 配置注册中心信息
    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("zookeeper://127.0.0.1:2181");
        return registryConfig;
    }
    
    // 配置通信协议
    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo"); // 传输协议,默认为 dubbo
        protocolConfig.setPort(20880); // Dubbo 默认端口
        return protocolConfig;
    }


    // 配置服务提供者
    @Bean
    public ServiceConfig<ProductService> productServiceConfig(ProductService productService) {
        ServiceConfig<ProductService> serviceConfig = new ServiceConfig<>();
        serviceConfig.setApplication(applicationConfig());
        serviceConfig.setRegistry(registryConfig());
        serviceConfig.setInterface(ProductService.class);
        serviceConfig.setRef(productService);
        serviceConfig.setVersion("1.0.0"); // 设置版本号
        return serviceConfig;
    }
}

在上述代码中:

  • @Configuration 注解标记了这是一个 Spring 配置类。
  • @Bean 注解用于创建 Dubbo 相关组件的实例,比如 ApplicationConfigRegistryConfigServiceConfig
  • @Service 注解用于标记 Dubbo 服务提供者的实现类,这样 Spring Boot 就能够扫描到并将其纳入 Spring 容器中。

在 Spring Boot 主类中,添加 @Import(DubboConfiguration.class) 来导入上述 Dubbo 的配置类:

三、高可用特性

1、zookeeper宕机与dubbo直连

现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
原因

健壮性↓

  • 监控中心宕掉不影响使用,只是丢失部分采样数据

  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务

  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台

  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯

  • 服务提供者无状态,任意一台宕掉后,不影响使用

  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

    高可用:通过设计,减少系统不能提供服务的时间

2、集群下dubbo负载均衡配置

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

负载均衡策略如下

基于权重的轮循负载均衡机制

轮循,按公约后的权重设置轮循比率。 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
在这里插入图片描述
在这里插入图片描述

最少活跃数-负载均衡机制:总是找到响应速度最快的服务器

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
在这里插入图片描述

一致性hash-负载均衡机制:方法名参数名一样落在一台服务器运行

一致性 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” />
在这里插入图片描述

3、整合hystrix库,服务熔断与降级处理

在 Dubbo 中,整合 Hystrix 可以用于服务熔断和降级处理。Hystrix 是 Netflix 提供的一种用于处理分布式系统的延迟和故障的库。在 Dubbo 中使用 Hystrix,通常使用 Alibaba 的 Sentinel 作为 Dubbo 的容错机制,因为 Sentinel 对 Hystrix 进行了集成,提供了更多的特性。

3.1 服务降级

服务降级:当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。

可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。

向注册中心写入动态配置覆盖规则:

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));

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

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

服务熔断和服务降级是分布式系统中为了提高系统的稳定性和容错性而采取的两种策略。

  1. 服务熔断(Circuit Breaker)

通俗解释: 就像电路中的断路器一样,服务熔断是一种保护机制,当系统的某个服务出现故障或不稳定时,会切断对该服务的请求,防止问题扩大影响其他服务,以免整个系统崩溃。一旦服务熔断触发,将暂时拒绝对该服务的请求,避免资源的持续浪费。

例子: 假设你的系统中有一个支付服务,如果支付服务在短时间内出现多次失败,服务熔断机制会暂时停止向支付服务发送请求,避免对支付服务的继续压力,直到支付服务稳定下来。

  1. 服务降级(Fallback)

通俗解释: 服务降级是系统在面临异常或高负载时,为了保持核心功能的可用性临时屏蔽一些非核心或可选功能。简单来说,就是在一些异常情况下,系统可以提供一个简化版的服务,以保证用户至少能够得到基本的响应,而不至于因为某个小问题导致整个系统崩溃

例子: 继续以支付服务为例,如果支付服务出现问题,系统可以采用服务降级策略,暂时关闭一些不是必须的功能,比如优惠券服务或积分服务,以确保支付这个核心功能的正常运作。

对比:

  • 服务熔断 更注重的是对整个服务的状态进行监控,当服务的失败率达到一定阈值时,触发熔断机制。
  • 服务降级 更注重的是在某个特定服务或功能受到影响时,采取相应的措施,使得系统的核心功能仍能正常运行。

这两种策略通常结合使用,以提高系统的整体鲁棒性(指系统对于异常情况和不完善输入的处理能力。)和用户体验,达到动态调配服务器资源的效果。在实际应用中,服务熔断和服务降级通常都伴随着监控、日志记录等机制,以便及时发现问题并进行处理。

3.2 集群容错

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

集群容错模式

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
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。

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

<dubbo:service cluster=“failsafe” /><dubbo:reference cluster=“failsafe” />

3.3 整合hystrix

服务熔断错处理配置参考=> https://www.cnblogs.com/xc-xinxue/p/12459861.html

Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
Hystrix 是 Netflix 提供的一种用于处理分布式系统的延迟和故障的库。在整合 Hystrix 之前,请确保已经引入了 Hystrix 相关的依赖。在 Spring Boot 中,你可以使用 Spring Cloud Netflix 来集成 Hystrix。

以下是整合 Hystrix 的一般步骤:

  1. 引入依赖: 在你的 Spring Boot 项目中,添加以下依赖:

    <!-- Hystrix 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  2. 启用 Hystrix: 在 Spring Boot 主类上添加 @EnableHystrix 注解,启用 Hystrix 功能:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    
    @SpringBootApplication
    @EnableHystrix
    public class YourApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(YourApplication.class, args);
        }
    }
    
  3. 使用 @HystrixCommand 注解: 在需要进行熔断或降级处理的方法上使用 @HystrixCommand 注解,指定熔断和降级的处理逻辑:

    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import org.springframework.stereotype.Service;
    
    @Service
    public class YourService {
    
        @HystrixCommand(fallbackMethod = "fallbackMethod")
        public String yourMethod() {
            // 实际的服务逻辑
            // 可能会抛出异常
    
            return "正常返回结果";
        }
    
        // 降级处理逻辑
        private String fallbackMethod() {
            return "降级处理结果";
        }
    }
    

    在上述代码中,yourMethod 方法上使用了 @HystrixCommand 注解,并指定了 fallbackMethod,即降级处理逻辑。

  4. 配置 Hystrix:application.propertiesapplication.yml 中添加 Hystrix 的配置:

    # 开启 Hystrix 监控
    management.endpoints.web.exposure.include=hystrix.stream
    
  5. 监控 Hystrix Dashboard(可选): 如果你想要监控 Hystrix 的熔断和降级情况,可以使用 Hystrix Dashboard。在项目中添加以下依赖:

    <!-- Hystrix Dashboard 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    

    并在主类上添加 @EnableHystrixDashboard 注解。

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    
    @SpringBootApplication
    @EnableHystrixDashboard
    public class YourApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(YourApplication.class, args);
        }
    }
    

    访问 http://your-app-url/hystrix,输入 Hystrix Stream 地址(默认为 /actuator/hystrix.stream)即可监控 Hystrix Dashboard。

以上步骤是简单的 Hystrix 集成步骤,实际应用中可以根据需要进行更详细的配置和适配。

四、Dubbo底层原理

1、RPC原理

在这里插入图片描述

一次完整的RPC调用流程(同步调用,异步另说)如下:

  1. 服务消费方(client)调用以本地调用方式调用服务;

  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;

  3. client stub找到服务地址,并将消息发送到服务端;

  4. server stub收到消息后进行解码;

  5. server stub根据解码结果调用本地的服务;

  6. 本地服务执行并将结果返回给server stub;

  7. server stub将返回结果打包成消息并发送至消费方;

  8. client stub接收到消息,并进行解码;

  9. 服务消费方得到最终结果。

    dubbo只用了两步1和8,中间的过程是透明的看不到的。

    RPC框架的目标就是要2~8这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。

2、Netty通信原理

Netty是一个异步事件驱动的网络应用程序框架, 用于快速开发可维护的高性能协议服务器和客户端。它极大地简化并简化了TCP和UDP套接字服务器等网络编程。

2.1 BIO

(Blocking IO)阻塞 I/O 模型

![外链图片转存失败,

BIO(Blocking I/O)是 Java 中最传统的 I/O 模型之一,也被称为阻塞 I/O 模型。在 BIO 模型中,每个 I/O 操作都会阻塞线程,直到操作完成。这就意味着当一个线程在进行 I/O 操作时,它会一直阻塞,无法执行其他任务。这样的模型通常适用于连接数较小且连接时间较短的应用场景。

以下是 BIO 模型的一般流程:

  1. 服务器端启动一个 ServerSocket 来监听客户端的连接请求。
  2. 客户端发起连接请求,服务器端接受连接请求后创建一个 Socket 与客户端建立连接。
  3. 服务器端为每个连接创建一个新的线程,该线程负责处理该连接的读写操作。
  4. 在连接的线程中,通过 InputStream 从客户端读取数据,通过 OutputStream 向客户端写入数据。
  5. 如果没有数据可读,线程将一直阻塞在读取操作上,直到有数据可读。
  6. 同样,如果写入操作阻塞,线程将一直等待写入完成。

BIO 的优点是实现简单,易于理解和使用,适用于连接数目比较小且固定的场景。然而,它的主要缺点是性能较低。因为每个连接都需要独立的线程来处理,当连接数目较大时,线程数量也会变得非常庞大,导致系统资源消耗过多。此外,由于每个线程都需要阻塞等待 I/O 操作完成,因此在高并发场景下,大量线程的阻塞会导致资源浪费和性能下降。

在实际的网络编程中,BIO 往往会被更为高效的 NIO(Non-blocking I/O)和异步 I/O(AIO)取代,这两者在处理连接数较多的场景时具有更好的性能和资源利用率。
在这里插入图片描述

2.2 NIO

(Non-Blocking IO)非阻塞 I/O:多路复用在这里插入图片描述
NIO(Non-blocking I/O,即非阻塞 I/O)是 Java 中的一种 I/O 模型,相对于传统的阻塞 I/O(BIO),NIO 提供了更为灵活和高效的 I/O 操作方式。NIO 主要通过以下几个关键组件实现:

  1. 通道(Channel): 通道是 NIO 中的核心概念,它类似于流,但具有双向操作的能力。通道可以连接到文件、网络套接字等,实现了数据的读和写。
  2. 缓冲区(Buffer): 缓冲区是 NIO 中用于存储数据的对象。所有数据都是通过缓冲区来处理的。缓冲区实质上是一个数组,可以在缓冲区中存储数据,并通过指针(position、limit、capacity)来操作数据。
  3. 选择器(Selector): 选择器是 NIO 中用于处理多个通道的机制。一个单独的线程可以通过选择器监视多个通道的 I/O 事件,例如读就绪、写就绪等,从而实现了单线程处理多个通道的能力。

相对于传统的 BIO 模型,NIO 的主要优势在于:

  • 非阻塞: NIO 提供了非阻塞的 I/O 操作,一个线程可以处理多个通道,而不需要为每个连接创建一个独立的线程。这提高了系统的并发性和资源利用率。
  • 选择器: 通过选择器,一个线程可以有效地监视多个通道上的事件,降低了线程数量,提高了系统的可伸缩性。
  • 缓冲区: NIO 使用缓冲区进行数据的读写,提供了更灵活、高效的数据操作方式。

NIO 在网络编程中得到广泛应用,特别是在高并发的服务器开发中。它使得在一个线程中能够同时处理多个客户端连接成为可能,大大提高了服务器的性能和扩展性。

2.3 Netty基本原理

可参考https://www.sohu.com/a/272879207_463994
在这里插入图片描述

Netty 是一个基于 Java NIO(New I/O)的异步事件驱动的网络框架,用于快速开发可维护的高性能服务器和客户端。Netty 主要基于以下两个核心概念:Channel(通道)和事件处理机制。

1. Channel 和 EventLoop:

  • Channel: 通道是网络数据的容器,负责读取和写入数据。Netty 中的 Channel 提供了对底层传输的抽象,例如 Socket 或文件 I/O。通过 Channel,可以进行数据的异步读写和事件的监听。
  • EventLoop: 事件循环负责处理注册到 Channel 上的所有事件,比如连接、读取、写入等。一个 EventLoop 实例通常会处理多个 Channel,并且它在整个生命周期中都是单线程执行的。这样设计可以避免多线程同步问题,简化了并发编程。

2. Netty 的核心组件:

  • Bootstrap: 启动器,用于配置和启动 Netty 应用程序。
  • Channel: 通道,代表了一个开放的连接,可以执行读取、写入、连接、绑定等操作。
  • EventLoopGroup: 事件循环组,管理 EventLoop 的组。
  • Pipeline: 管道,用于处理请求和响应的处理器链。
  • Handler: 处理器,实际处理业务逻辑的组件。

3. Netty 的通信原理:

  1. 初始化: 创建一个 Bootstrap 实例,配置 EventLoopGroup,设置通信相关的参数,如协议、编码解码器等。
  2. 连接: 使用 Bootstrap 连接到远程主机。在连接建立时,会创建一个 Channel 对象。
  3. Pipeline 配置: 每个 Channel 都有一个关联的 PipelinePipeline 中包含一系列的 Handler,负责处理数据的读取、写入等操作。在 Pipeline 中,数据将按照处理器链的顺序进行处理。
  4. 事件驱动: Netty 的通信是事件驱动的。当有数据可读或写入完成时,Netty 会触发相应的事件,然后由 EventLoop 负责处理这些事件。
  5. Handler 处理: 数据在 Pipeline 中经过一系列的处理器。每个处理器负责特定的任务,如协议解析、数据处理、编码解码等。
  6. 数据读写: 读取和写入的操作是异步的。当有数据可读时,会触发读取事件,然后通过 Channel 读取数据。当数据要写入时,会触发写入事件,通过 Channel 写入数据。
  7. 关闭连接: 当连接不再需要时,调用 Channel 的关闭方法,关闭连接。

Netty 的优势在于其强大的异步事件处理机制和灵活的组件设计,使得开发者能够方便地构建高性能、可维护的网络应用。通过合理配置和使用 Handler,可以实现各种网络通信场景,如实时通信、长连接、HTTP、WebSocket 等。

2.4 Netty与NIO、BIO

Netty 是基于 NIO(Non-blocking I/O)的网络编程框架,因此 Netty 与 NIO 有直接的关系。首先,我们来简要了解一下它们之间的关系:

  1. BIO(Blocking I/O): 传统的阻塞 I/O 模型,每个连接都需要一个独立的线程来处理,当连接数较多时,线程数会成倍增加,导致资源浪费和性能下降。
  2. NIO(Non-blocking I/O): 提供了非阻塞的 I/O 操作,允许一个线程处理多个通道,通过选择器(Selector)实现了单线程处理多个连接的能力,提高了系统的并发性和资源利用率。
  3. Netty: 是一个构建在 NIO 之上的高性能网络编程框架。Netty 封装了 NIO 的复杂性,提供了更简单、灵活、可扩展的 API,使得开发者能够更轻松地构建高性能的网络应用。

关系总结如下:

  • Netty与BIO: Netty可以使用BIO作为底层通信模型,但由于BIO的阻塞特性,不是Netty的最佳选择。Netty的优势在于它更适用于高并发和大规模连接的场景。
  • Netty与NIO: Netty是基于NIO构建的。Netty通过封装NIO提供了更高级别的抽象,简化了NIO的使用,同时提供了更多的功能和灵活性。

Netty的设计目标是提供一个高性能、可维护、可扩展的网络框架,它在底层使用了NIO,但同时通过自身的抽象和封装,让开发者更容易使用,并在性能和功能上提供了更多的优势。因此,Netty是NIO的一种封装和扩展

3、Dubbo原理

3.1 框架设计

在这里插入图片描述

Business层:完成业务逻辑

  • Service业务逻辑层: Dubbo 服务提供者的业务逻辑实现和服务接口定义的一部分。

RPC层:完成远程过程调用

  • config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
  • registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
  • monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
  • protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter

Remoting层:解决远程通信

  • exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
  • transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
  • serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool

3.2 启动解析、加载配置信息

在 Dubbo 中,启动时的配置信息解析和加载是整个系统初始化的关键部分。

这一过程涉及到读取配置文件、解析配置信息、初始化相关组件等步骤。

以下是 Dubbo 启动时解析加载配置信息的一般流程:

  1. 读取配置文件: Dubbo 的配置信息通常存储在 XML 或 Properties 文件中。在启动时,Dubbo 需要读取这些配置文件,获取系统的各种配置参数。

    <!-- 示例:Dubbo XML 配置文件 -->
    <dubbo:application name="example-provider" />
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:protocol name="dubbo" port="20880" />
    <dubbo:service interface="com.example.UserService" ref="userService" />
    
  2. 解析配置信息: 读取配置文件后,Dubbo 需要解析这些配置信息。Dubbo 使用自定义的配置解析器来解析 XML 或 Properties 文件中的配置项,将其转化为系统可用的配置对象。

    // 示例:Dubbo XML 配置解析器
    public class DubboXmlConfigParser {
        public static DubboConfig parse(String xmlContent) {
            // 解析 XML 内容,转化为 DubboConfig 对象
            // ...
        }
    }
    
  3. 初始化配置对象: 解析完成后,Dubbo 需要将解析得到的配置信息初始化到相应的配置对象中。这些配置对象包括服务提供者配置、服务消费者配置、注册中心配置等。

    // 示例:初始化 Dubbo 配置对象
    DubboConfig dubboConfig = DubboXmlConfigParser.parse(xmlContent);
    
  4. 初始化相关组件: 根据配置信息,Dubbo 需要初始化各个组件,如注册中心、协议(Protocol)、服务提供者、服务消费者等。这一步包括创建实例、初始化参数、启动服务等操作。

    // 示例:初始化注册中心、协议、服务提供者
    Registry registry = createRegistry(dubboConfig);
    Protocol protocol = createProtocol(dubboConfig);
    ServiceProvider serviceProvider = createServiceProvider(dubboConfig);
    
  5. 启动服务: 最后,Dubbo 需要启动各个组件,开始提供服务。这包括服务提供者开始监听请求、服务消费者开始调用远程服务等。

    // 示例:启动服务提供者、服务消费者
    serviceProvider.start();
    

整个过程包括了配置文件的读取、解析、初始化和组件的启动等步骤,确保 Dubbo 在启动时能够正确加载配置信息并顺利启动服务。

3.3 服务暴露

在这里插入图片描述

在 Dubbo 中,"服务暴露"是指将服务提供者的服务注册到注册中心并监听网络请求,使得服务可以被远程消费者调用。服务暴露的过程涉及到将服务实例注册到注册中心、开启监听端口等操作。

以下是 Dubbo 中服务暴露的一般流程:

  1. 服务提供者配置: 在服务提供者的配置文件(如 XML 配置文件)中配置服务的接口、实现类、注册中心地址、监听端口等信息。

    <!-- 示例:Dubbo 服务提供者 XML 配置 -->
    <dubbo:application name="example-provider" />
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:protocol name="dubbo" port="20880" />
    <dubbo:service interface="com.example.UserService" ref="userService" />
    
  2. 服务暴露: 在服务提供者启动时,Dubbo 会读取配置文件,解析配置信息,并将服务暴露出去。这一步通常在服务提供者的启动代码中完成。

    // 示例:Dubbo 服务提供者启动代码
    public class Provider {
        public static void main(String[] args) {
            // 读取配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
    
            // 获取服务实例
            UserService userService = context.getBean(UserService.class);
    
            // 服务暴露
            userService.export();
        }
    }
    
  3. 服务注册: Dubbo 将服务的信息注册到指定的注册中心,以便服务消费者能够通过注册中心发现和调用服务。在上述配置中,<dubbo:registry> 指定了注册中心的地址。

    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    
  4. 监听端口: Dubbo 使用指定的协议(如 <dubbo:protocol> 中指定的 dubbo 协议)在指定的端口上监听服务请求。服务消费者将通过这个端口发起远程调用。

    <dubbo:protocol name="dubbo" port="20880" />
    

总体来说,服务暴露的过程包括了配置服务提供者信息、读取配置文件、将服务注册到注册中心、监听端口等步骤。这使得服务提供者能够在启动时正确地将服务提供给远程消费者。

3.4 服务引用

在这里插入图片描述

在 Dubbo 中,"服务引用"是指服务消费者获取并调用远程服务的过程。这一过程涉及到消费者配置服务引用信息、从注册中心获取服务提供者地址、生成服务代理等操作。以下是 Dubbo 中服务引用的一般流程:

  1. 服务消费者配置: 在服务消费者的配置文件(如 XML 配置文件)中配置服务引用信息,包括服务接口、注册中心地址、调用超时等信息。

    <!-- 示例:Dubbo 服务消费者 XML 配置 -->
    <dubbo:application name="example-consumer" />
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:reference id="userService" interface="com.example.UserService" />
    
  2. 服务引用: 在服务消费者启动时,Dubbo 会读取配置文件,解析配置信息,并将服务引用出去。这一步通常在服务消费者的启动代码中完成。

    // 示例:Dubbo 服务消费者启动代码
    public class Consumer {
        public static void main(String[] args) {
            // 读取配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
    
            // 获取服务代理
            UserService userService = context.getBean(UserService.class);
    
            // 调用远程服务
            User user = userService.getUserById(123);
            System.out.println("User: " + user);
        }
    }
    
  3. 服务发现: Dubbo 通过注册中心发现远程服务的地址。在上述配置中,<dubbo:registry> 指定了注册中心的地址。

    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    
  4. 生成服务代理: Dubbo 使用服务引用信息生成服务代理。消费者通过这个代理对象来调用远程服务,实际上是通过网络请求到达服务提供者。

    // 示例:获取服务代理
    UserService userService = context.getBean(UserService.class);
    

总的来说,服务引用的过程包括了配置服务消费者信息、读取配置文件、从注册中心获取服务提供者地址、生成服务代理等步骤。这使得服务消费者能够在启动时正确地引用并调用远程服务。

3.5 服务调用

在这里插入图片描述

服务调用是指在 Dubbo 框架中,服务消费者通过服务引用获取到的服务代理对象,调用服务提供者提供的远程服务的过程。以下是 Dubbo 中服务调用的一般流程:

  1. 服务提供者暴露服务: 在服务提供者启动时,通过配置文件(如 XML 配置文件)将服务暴露出去。

    <!-- 示例:Dubbo 服务提供者 XML 配置 -->
    <dubbo:application name="example-provider" />
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:protocol name="dubbo" port="20880" />
    <dubbo:service interface="com.example.UserService" ref="userService" />
    
  2. 服务消费者引用服务: 在服务消费者启动时,通过配置文件将服务引用出去。

    <!-- 示例:Dubbo 服务消费者 XML 配置 -->
    <dubbo:application name="example-consumer" />
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:reference id="userService" interface="com.example.UserService" />
    
  3. 服务调用: 在服务消费者代码中,通过服务引用获取到服务代理对象,然后通过这个代理对象调用服务提供者的方法。

    // 示例:Dubbo 服务消费者代码
    public class Consumer {
        public static void main(String[] args) {
            // 读取配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
    
            // 获取服务代理
            UserService userService = context.getBean(UserService.class);
    
            // 调用远程服务
            User user = userService.getUserById(123);
            System.out.println("User: " + user);
        }
    
  4. 服务代理执行远程调用: 在服务代理中,Dubbo 使用底层的远程通信框架(例如 Netty)将调用请求发送到服务提供者。服务提供者接收到请求后,执行相应的业务逻辑,并将结果返回给服务消费者。

  5. 服务提供者响应: 服务提供者执行完业务逻辑后,将结果返回给服务消费者。服务消费者在接收到结果后,继续执行相应的业务逻辑。

总体来说,服务调用的过程包括了服务提供者的服务暴露、服务消费者的服务引用、通过服务代理调用远程服务等步骤。这使得服务提供者和服务消费者能够通过 Dubbo 框架实现分布式系统中的服务调用。

4、dubbo与RPC、Netty

让我们逐个了解 Dubbo、RPC、和 Netty,并理清它们之间的关系:

  1. RPC(Remote Procedure Call): RPC 是一种远程过程调用协议,允许在不同地址空间的程序之间进行通信。它的目标是使远程调用看起来像本地调用一样。RPC 通常包括客户端和服务器端,客户端通过调用远程服务提供的接口来发起远程过程调用,而服务器端则暴露接口供客户端调用。
  2. Netty: Netty 是一个基于 NIO(Non-blocking I/O)的网络编程框架,用于快速开发可维护的高性能服务器和客户端。Netty 提供了一种异步事件驱动的模型,使得开发者能够轻松处理大量并发连接。Netty 主要用于构建网络应用,包括但不限于实现自定义协议、WebSocket、HTTP 等。
  3. Dubbo: Dubbo 是一个分布式服务框架,用于构建大规模分布式系统。Dubbo 提供了服务治理的功能,包括服务注册与发现、负载均衡、容错处理、服务路由等。Dubbo 使用 RPC 协议来实现分布式系统中的服务调用和通信,支持多种通信协议,包括 Netty。

关系总结如下:

  • Dubbo和RPC: Dubbo 是一个基于 RPC 的分布式服务框架,它使用 RPC 作为底层通信协议。Dubbo 封装了 RPC 的复杂性,提供了更高级别的抽象和服务治理功能。
  • Dubbo和Netty: Dubbo 可以使用 Netty 作为底层的通信框架。Netty 提供了高性能的异步网络通信,而 Dubbo 则在其基础上构建了服务治理的框架,简化了分布式服务的开发和管理。

当我们谈论 Dubbo、RPC、和 Netty 时,可以用以下比喻来理解它们之间的关系:

  1. RPC(Remote Procedure Call): 想象你在电话中给朋友打电话,你可以通过电话请求朋友做一些事情。在计算机领域,RPC 就像是你通过网络请求远程计算机上的服务,就像你在远程计算机上调用本地的函数一样。
  2. Netty: 如果把 RPC 比喻成电话,那么 Netty 就是电话的线路和通信设备。Netty 是一个提供高效通信的框架,就像电话线路一样,让你可以更轻松地在计算机之间传递信息。
  3. Dubbo: Dubbo 则是在 RPC 的基础上构建的更高级别的服务框架。如果我们继续用电话的比喻,Dubbo 就是一个提供更多服务和管理功能的通信平台,让你能够更好地组织和管理你的通话,比如知道哪个朋友在线、调度电话线路等。

综合起来,Dubbo 是建立在 RPC 之上,使用 Netty 进行底层通信的分布式服务框架。RPC 就像是打电话的方式,Netty 是实现电话通信的基础设施,而 Dubbo 则是一个更高层次的服务平台,使得分布式系统的构建和管理更为方便。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值