传统意义上的远程通信关注的是数据的共享。
微服务的服务与服务之间的通信需求更多来自服务的解耦。

  • 微服务解决问题
    1.保障服务高可用
    2.提高机器的利用率
    3.故障业务降级、流量控制
    4.动态更新服务中的配置信息
    5.集群服务管理和上下线动态感知

Apache Dubbo

官网 https://dubbo.apache.org/zh/
分布式服务RPC框架,同时提供服务治理等功能
dubbo-architecture

Apache Dubbo实现远程通信(不使用注册中心)

演示点对点的通信形式,Dubbo继承了Spring,并且在此基础上做了标签的扩展,整体配置方式和Spring差不大多,可以使用最基本的XML和注解配置。
此点对点Dubbo不需要和其他框架集成,只需要最纯粹的Dubbo依赖
image.png

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.9</version>
</dependency>

  • 服务提供方
    image.png
    1.user-api暴露对外提供接口
    2.user-provider依赖user-api、Dubbo,为api的本地实现
<?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="user-service"/>
    <dubbo:registry address="N/A"/>
    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880"/>
    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.gupaoedu.book.dubbo.IUserService" ref="userService"/>
    <!-- 和本地bean一样实现服务 -->
    <bean id="userService" class="com.gupaoedu.book.dubbo.UserServiceImpl"/>
</beans>
  • 服务消费方
    image.png
    1.order-service依赖user-api、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="order-service"  />
    <dubbo:registry address="N/A" />
    <!-- 生成远程服务代理,可以和本地bean一样使用userService -->
    <dubbo:reference id="userService" interface="com.gupaoedu.book.dubbo.IUserService"
                     url="dubbo://169.254.216.167:20880/com.gupaoedu.book.dubbo.IUserService"/>
</beans>

注意URL需要和服务提供者一致
image.png

Spring Boot集成Apache Dubbo(不使用注册中心)

Dubbo集成到Spring Boot可以享受到Spring Boot生态框架和技术支持,实现标准化,统一开发、部署、运维的形态
可以使用Dubbo Spring Boot组件轻松集成,其整合了自动装配、健康检查、外部化配置等功能
image.png
添加Spring Boot 和Dubbo-Spring-boot相关依赖,其他相关starter组件依需要添加

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.8</version>
</dependency>
  • 服务提供方
    image.png
    1.sample-api暴露接口,远程调用
    2.sample-provider接口方法本地实现
    启动类开启springboot和dubbo的注解扫描@DubboComponentScan
    @SpringBootApplication,实现类使用Dubbo注解@Service实现发布服务
@Service
public class HelloServiceImpl implements IHelloService {
    @Value("${dubbo.application.name}")
    private String serviceName;

    @Override
    public String sayHello(String name) {
        return String.format("[%s]:Hello,%s",serviceName,name);
    }
}

3.添加springboot和dubbo配置文件

spring.application.name=springboot-dubbo-demo

dubbo.application.name=springboot-provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#不使用注册中心
dubbo.registry.address=N/A
  • 服务消费者
    1.使用Dubbo的@Reference注解获得远程代理对象,url
    image.png
@SpringBootApplication
public class SpringbootConsumerApplication {
    @Reference(url = "dubbo://127.0.0.1:20880/com.gupaoedu.book.dubbo.IHelloService")
    private IHelloService helloService;

    public static void main(String[] args) {
        SpringApplication.run(SpringbootConsumerApplication.class, args);
    }
    @Bean
    public ApplicationRunner runner(){
        return args -> System.out.println(helloService.sayHello("Mic"));
    }
}

调用后输出结果:
image.png

快速上手ZooKeeper

Dubbo除了以点对点的形式来实现服务之间的通信,还可以集成注册中心来实现服务地址的统一管理
早期使用的是ZooKeeper来实现
ZooKeeper官网: https://zookeeper.apache.org/
image.png
ZooKeeper本身不是注册中心,基于ZooKeeper本身的特性可以实现注册中心场景应用
ZooKeeper是一个高性能分布式协调中间件(解决分布式环境中各个服务进程的访问控制问题,例如访问顺序控制

ZooKeeper的安装

ZooKeeper基于Java编写,源码地址: https://github.com/apache/zookeeper
需要JRE,可支持单机和集群部署

  • 1.下载ZooKeeper
    https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz

  • 2.解压后ZooKeeper目录如下
    image.png

  • 3.将conf目录下的zoo_sample.cfg更名为zoo.cfg

  • 4.运行bin目录下的zkServer脚本文件Linux为shell脚本,WIN为CMD

  • 5.通过默认配置的2181端口访问,启动bin目录下的客户端访问服务端

  • 踩坑
    我的是3.6.2最新版本,cfg配置文件需要更改,否则脚本配置文件判断路径会报错,配置文件注释也说明了
    image.png
    image.png
    image.png

ZooKeeper数据结构

ZooKeeper数据模型和分布式文件系统类似,是一种层次化的属性结构,但是ZooKeeper的数据是结构化存储,并没有在物理上体现出文件和目录。
image.png

  • ZooKeeper树中的每个节点被称为Znode维护一个stat状态信息,可以设置一个Value值。
  • ZooKeeper只管理协调有关数据,Value的数据大小不建议设置大,会带来较大的网络开销
  • ZooKeeper每个节点数据都允许读写,但必须按照层级创建
  • 同层级目录下,节点名称必须是唯一的

ZooKeeper节点特性

  • 节点类型划分
    1.持久化节点:节点数据会持久化到磁盘
    2.临时节点,节点生命周期随创建该节点客户端生命周期一致,会话结束自动删除
    3.有序节点:在创建的节点后面增加一个递增序列,保证唯一性(持久化和临时节点都可为有序节点)
    4.容器节点:节点下的最后一个子节点被删除时会被自动删除
    5.TTL节点:为持久化的节点设置存活时间,在该时间内节点没有任何修改并且没有任何子节点会被自动删除

Watcher机制

类似Redis哨兵模式,针对Znode的订阅/通知机制
当Znode节点状态发生变化时或者ZooKeeper客户端连接状态发生变化时,会触发事件通知
在服务注册与发现中,针对服务调用者及时感知到服务提供者的变化提供了非常好的解决方案

  • ZooKeeper提供的Java API注册监听
    1.getData():获取指定节点的value信息,注册监听
    2.getChildren():获取指定节点的所有子节点,注册监听
    3.exists():判断指定节点是否存在,监听

Watcher时间触发是一次性的,客户端只能接收到一次,为了解决该问题,需要客户端在收到的事件回调中再次注册事件

常见应用场景分析

由于ZooKeeper的特性,可以为多种应用场景提供解决方案

分布式锁

锁的本质是排他性,避免在同一时刻多个进程同时访问某一个共享资源。
分布式锁是解决分布式环境下多进程对于共享资源访问带来的安全性问题的方案

  • 利用到的ZooKeeper特性
    1.临时节点
    2.同级节点的唯一性
    3.Watcher机制

  • 获得锁过程
    所有客户端在ZooKeeper服务器上同节点/A下创建一个临时节点/B,由于同级节点的唯一性,只会有一个客户端创建成功获得排他锁,没有获得锁的客户端Watcher监听该节点/A下子节点的变更事件

  • 释放锁过程
    1.获得锁客户端因为异常原因断开客户端连接,临时节点/B会被自动删除
    2.获得锁客户端执行完业务逻辑后,主动删除了创建的/B节点

  • 竞争锁
    当/B节点删除后,监听到的客户端会再次发起创建临时节点/B的操作获得排他锁

Master选举

类似Redis集群主从同步
Master选举是分布式系统中非常常见的场景,保证了服务的可用性,集群模式下,一个机器宕机后集群其他节点会接替故障节点

  • 利用到的ZooKeeper特性
    1.同级节点的唯一性
    2.有序节点
    3.Watcher机制

  • 实现方法一:同级节点唯一性、Watcher机制
    1.多个服务节点注册同一个临时节点/A,成功注册的客户端为Master
    2.为注册的客户端针对Master注册的/A节点进行Watcher监控
    3.假如Master宕机,临时节点/A被删除则其他客户端会发起Master选举,继续注册临时节点/A

  • 实现方法二:有序节点、Watcher机制
    1.所有客户端在/A节点下创建一个有序临时节点/B1、/B2.../Bn
    2.创建的最小节点的客户端为Master节点
    3.后续节点监听前一个节点的删除事件,用于触发重新选举
    4.一旦最小的节点被删除(B1),则余下的最小节点(B2)会被选举为Master

Apache Dubbbo集成ZooKeeper实现注册服务

大规模化后,在远程RPC通信过程中,会遇到以下问题

  • 服务动态上下线感知
    服务调用者要感知到服务提供者上下线的变化,例如IP故障或者集群中的节点下限,服务调用者需要同步更新这些地址
    统一管理服务提供者URL地址,服务调用者获得目标服务相关地址,动态感知服务提供者状态的变化来维护URL地址
  • 负载均衡
    多节点集群环境时,需要服务调用者通过负载均衡算法动态选择一台目标服务提供者服务器进行远程通信,均衡服务器的访问压力,提升整体性能。实现的前提也是需要动态感知服务提供者集群的所有地址

无论何种功能都需要实现服务注册和发现可使用ZooKeeper、Nacos、Redis等作为注册中心,Dubbo官方推荐使用ZooKeeper
image.png

Apache Dubbo集成ZooKeeper实现服务注册(利用到了临时、持久节点、Watcher机制)

  • 1.服务本地实现和远程调用方添加ZooKeeper依赖
   <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.3-beta</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.0.1</version>
        </dependency>
  • 2.提供者配置文件添加相关ZooKeeper信息
spring.application.name=springboot-dubbo-demo

dubbo.application.name=springboot-provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.registry.address=zookeeper://127.0.0.1:2181
  • 3.消费者配置文件添加相关ZooKeeper信息
#更改默认8080端口,防止冲突
server.port=8081
dubbo.application.name=springboot-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
  • 4.消费者调用远程方法不用手动加入URL地址,而会去注册中心获取目标服务的URL地址
    image.png

ZooKeeper注册中心实现原理

image.png
Dubbo服务注册到ZooKeeper后,可以看到如上图所示的树状目录结构

  • 1.提供者注册
    当Dubbo服务启动时,提供者会去ZooKeeper服务器的/dubbo/com...Service/providers目录下创建当前服务的URL,其中com...Service代表服务提供者的全路径类名。URL使用临时节点(服务器下线则URL会被移除),其他为持久化节点
  • 2.服务消费者
    当Dubbo服务启动时,消费者会注册Watcher/dubbo/com...Service/providers节点下的子节点,同时会在/dubbo/com...Service/consumers下写入自己的URL(可以监控消费者)
  • 3.服务调用
    消费者会去/dubbo/com...Service/providers路径下获得所有该服务所有URL,通过负载均衡算法计算出一个地址远程访问

Dubbo Spring Cloud

Dubbo Spring Cloud的出现,使得Dubbo能够完全整合到Spring Cloud的技术栈中,享受Spring Cloud生态中的技术支持和标准化输出,又能够弥补Spring Cloud中服务治理方面的短板
Dubbo Spring Cloud是Spring Cloud Alibaba的核心组件,在构建原生的Spring Cloud标准之上,不仅覆盖了Spring Cloud原生特性,还提供了更加稳定和成熟的实现

实现Dubbo服务提供方

image.png

  • api暴露接口基础模块
    无需要加入特别的依赖
public interface IHelloService {
    String sayHello(String name);
}
  • provider服务提供方本地实现
    依赖api模块
    1.pom依赖
    <!--        Spring Cloud核心模块-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <!--        Spring Cloud Alibaba模块-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>
        <!--        api接口模块-->
        <dependency>
            <groupId>com.gupaoedu.book.springcloud</groupId>
            <artifactId>spring-cloud-dubbo-sample-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--        Zookeeper注册发现模块-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>

2.配置文件

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

spring.application.name=spring-cloud-dubbo-sample
spring.cloud.zookeeper.discovery.register=true
spring.cloud.zookeeper.connect-string=127.0.0.1:2181

3.实现类

@Service
public class HelloServiceImpl implements IHelloService {
    @Value("${dubbo.application.name}")
    private String serviceName;

    @Override
    public String sayHello(String name) {
        return String.format("[%s]:Hello,%s",serviceName,name);
    }
}

4.启动类

@DubboComponentScan
@SpringBootApplication
public class SpringCloudDubboSampleProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudDubboSampleProviderApplication.class, args);
    }
}

实现Dubbo服务调用方

image.png
1.pom文件依赖

  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.gupaoedu.book.springcloud</groupId>
            <artifactId>spring-cloud-dubbo-sample-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>

2.配置文件

dubbo.cloud.subscribed-services=spring-cloud-dubbo-sample
server.port=8081
spring.application.name=spring-cloud-dubbo-consumer
spring.cloud.zookeeper.discovery.register=false
spring.cloud.zookeeper.connect-string=127.0.0.1:2181

3.远程调用方法

@RestController
public class HelloController {

//    @Reference(mock = "com.gupaoedu.book.springcloud.springclouddubboconsumer.MockHelloService",
//            cluster = "failfast")
    @Reference
    private IHelloService iHelloService;

    @GetMapping("/say")
    public String sayHello(){
        return iHelloService.sayHello("Mic");
    }
}

4.启动类

@SpringBootApplication
public class SpringCloudDubboConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudDubboConsumerApplication.class, args);
    }
}
  • 测试
    image.png
    可知确实通过端口8081通过/say映射方法远程调用了provider的方法实现

Apache Dubbo的高级应用

Apach Dubbo在服务治理方面的功能非常完善,比如集群容错、服务路由、负载均衡、服务降级、服务限流、服务监控、安全验证等。
官方使用介绍文档: https://dubbo.apache.org/zh/docs/

集群容错

当消费者调用服务提供者的接口时,如果因为网络或者服务器宕机等原因出现请求失败,需要有一定机制处理该失败错误

容错模式

cluster
Dubbo默认提供了6中容错模式,默认为Failover Cluster,也可以自行扩展

  • Failover Cluster:失败自动切换
    调用失败后切换其他提供者默认重试2次,可通过retire=2修改,常用于读操作,因为事务操作会有数据重复问题
  • Failfast Cluster:快速失败
    调用失败后立即报错,即只发起一次调用。常用于幂等写操作,避免在不确定因素情况下导致数据重复插入的问题
  • Failsafe Cluster:失败安全
    出现异常时直接忽略
  • Failback Cluster:失败后自动回复
    调用失败异常时,记录该失败的请求定时重发。用于消息通知操作,保证该请求一定发送成功
  • Forking Cluster:并行调用集群多个服务,只要其中一个成功即返回,通过forks=2来设置最大并行数
  • Broadcast Cluster:广播所有服务提供者,任意一个报错则失败,常用于通知所有的服务提供者更新缓存或者本地资源信息

配置方式

在DUbbo的提供者@Service注解和消费者@Reference注解上增加相应的参数
image.png
image.png
按照上述设置后,在提供者没有返回消息的情况下直接快速失败,而不是再重试2次,总计3次,如下图所示
image.png
image.png

负载均衡

分为硬件主流为F5、软件主流Nginx

DUbbo负载均衡策略

Dubbo提供了4中负载均衡策略,默认为random,也可基于SPI机制扩展

  • Random LoadBalance:随机算法
    性能好的服务型设置大权重值,权重值越大,随机概率越大
  • RoundRobin LoadBalance:轮询
    按照公约后的权重值设置轮询比例
  • LeastActive LoadBalance:最少活跃调用书
    处理较慢的节点将会收到更少的请求
  • ConsistentHash LoadBalance:一致性Hash
    相同参数的请求总是发送到同一个服务提供者

配置方式

image.png

服务降级

当服务器访问压力较大时,对不重要的服务进行降级,释放出更多的资源以保证核心服务的正常运行

  • 降级划分
    1.按照自动化分为自动降级、人工降级
    2.按照功能划分为读服务降级、写服务降级

  • 人工降级
    电商大促,暂时关闭非核心服务如评价,推荐,以保证交易系统

  • 自动降级
    系统出现某些异常的时候自动触发
    1.故障降级,远程服务挂了、网络故障等,可设置兜底数据相应给客户端
    2.限流降级,请求流量达到阈值时,后续请求被拦截或进入排队系统或返回降级页面,如人数太多,请稍后再尝试的提醒。
    Dubbo提供了一种Mock配置来实现服务降级,提供者出现异常时,客户端你不抛出异常,通过降级配置返回兜底数据
    image.png

服务降级配置

  • 再消费者创建自动降级的类实现接口方法,重写降级后的方法
public class MockHelloService implements IHelloService {
    @Override
    public String sayHello(String s) {
        return "Sorry,服务无法访问,返回降级数据";
    }
}
  • 调用类@Reference注释增加Mock参数
@RestController
public class HelloController {

    @Reference(mock = "com.gupaoedu.book.springcloud.springclouddubboconsumer.MockHelloService")
//    @Reference
    private IHelloService iHelloService;

    @GetMapping("/say")
    public String sayHello(){
        return iHelloService.sayHello("Mic");
    }
}

在提供者中断点调试等待,结果返回降级方法
image.png

主机绑定规则和其他相关配置

https://dubbo.apache.org/zh/docs/v2.7/user/examples/set-host/
Dubbo服务治理相关配置很多,官方文档也较详细
image.png

Apach Dubbo核心源码分析

Dubbo中很多设计值得学习和借鉴,核心点为:

  • SPI机制
  • 自适应扩展点
  • IoC、AOP
  • Dubbo与Spring的集成

源码构建

idea git仓库下载dubbo源码https://github.com/apache/dubbo.git
//略

Dubbo核心SPI

image.png
Dubbo的SPI自适应扩展点、指定名称扩展点、激活扩展点

Java SPI扩展点实现

SPI全称Service Provider Interface,原本为JDK内置的服务提供发现机制,用来做服务的扩展发现
例如数据库连接的驱动类在JDK中并没有实现,而是由不同的数据库厂家实现,Oracle、MySQL数据库的驱动类实现该接口,JDK利用SPI机制在classpath下找到相应的数据库驱动来获得指定的数据库连接
插拔式的扩展加载方式遵循一定的协议约定,所有的扩展点必须要放在resource/META-INF/service目录下,SPI默认会扫描该路径下的属性文件完成加载

Dubbo自定义扩展点

Dubbo和Spring没有使用JDK内置的SPI机制,利用了SPI思想做了优化和调整
Dubbo SPI相关逻辑被封装在ExtensionLoader类中
image.png

  • Dubbo SPI扩展规则
    1.需要在resource目录下创建上图中的三个目录任意一个,并在目录下创建接口全路径命名的文件,Dubbo会去该3个目录下加载相应扩展点
    2.内容是以K-V形式的数据,K为字符串,V为扩展接口的实现,该方式可以按照需要加载指定的类

Dubbo SPI扩展点源码

//TODO

无处不在的自适应扩展点

//TODO

IoC

SPI机制有使用到
//TODO

AOP

SPI机制有使用到
//TODO

Dubbo和Spring完美集成的原理







这个家伙很懒,啥也没有留下😋