一、架构演进   

  • 单体架构
  • 垂直应用架构:根据业务边界拆分,每个功能称为一个服务
    • 公共的业务service,导致很多部分的冗余
  • 分布式应用架构:把公共服务器的service提取成一个服务
    • 网络通讯的问题,使用RPC框架
    • 服务集群的负载均衡处理
    • 服务如何被发现
    • 服务如何被治理
    • 服务之间如何高效的通信
  • 微服务架构,主要解决以下几个问题
    • 服务的注册和发现
    • 服务的搞笑通信
    • 服务的治理:服务权重、负载均衡、服务熔断、限流等等

二、注册中心

1、概述

主流的注册中心:

  • Nacos
  • Zookeeper
  • Multicast
  • Redis
  • Simple
  • eureka

2、搭建zk集群

Zookeeper应用及原理_MG-net的博客-CSDN博客

三、RPC及Dubbo

1、什么是PRC

rpc是一种协议,一种远程过程调用协议,规定了双方通信采用什么格式、以及数据如何传输。

  • 指明调用类或者接口
  • 指名嗲用的方法及参数

2、手写远程过程调用

  • 准备一个注册中心,保存服务的地址
  • 服务提供者
    • 注册到注册中心
    • 告诉注册中心服务实现类
    • 准备服务协议,根据不同情况,使用不同的协议如http等
    • 接受到参数后,通过参数进行类反射调用
    • 最后将结果返回
  • 服务消费者
    • 使用代理调用服务提供者的实现类
    1. 封装本次调用的参数,类名、方法名、参数名、参数
    2. 获取地址列表
    3. 负载均衡选择服务提供者的地址
    4. 使用指定协议进行调用 

总结,RPC解决的问题:

  • 数据怎么封装
  • 数据怎么传输,适配不同的协议

3、什么的Dubbo

dubbo除了提供RPC协议,还提供了服务发现和注册、流量调度、可视化服务治理和运维、智能容错和负载均衡。所以dubbo是一个服务框架。

4、Dubbo是怎么实现远程调用的

5、Dubbo的初体验

1、服务提供者配置

在zk中,可以看到相关的节点文件:

2、服务消费者 

服务代理的过程: 

可以看出,在dubbo中,服务提供者把提供的服务注册到注册中心中,消费者可以在像使用自己本地的Bean一样使用远程的提供者的服务。dubbo底层采用netty框架进行网络通信。 

6、Dubbo的内部结构

 

  1. 初始化提供者
  2. 提供者注册到注册中心
  3. 消费者订阅注册中心中的服务,并且把服务提供者的地址缓存到本地
  4. 当提供者下线的时候,注册中心需要消费者服务下线
  5. 消费者同步的调用服务
  6. 监控

四、Springboot中的dubbo

1、服务提供者

double的pom依赖:

  <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.0.6</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
            <version>3.0.6</version>
        </dependency>

在启动类上增加注解:@EnableDubbo

在实现类上增加注解:@DubboService

配置文件配置dubbo的相关信息:

server:
  port: 9001
dubbo:
  application:
    name: dubbo_provider
  protocol:
    name: dubbo
    port: 20882
  registry:
    address: zookeeper://127.0.0.1:2181

启动后,服务提供着就自动注册到zk中了。

2、服务消费者

引入pom依赖:

 <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.0.6</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
            <version>3.0.6</version>
        </dependency>

在启动类上增加注解:@EnableDubbo

注入类的时候,使用注解:@DubboReference,这个注解做了2件事

  • 订阅服务
  • 建立代理对象

配置文件中配置dubbo信息:

server:
  port: 8001
dubbo:
  application:
    name: consumer
  registry:
    address: zookeeper://127.0.0.1:2181

3、@EnableDubbo的用法

注解流程:

@EnableDubbo->@EnableDubboConfig、@DubboCompentScan

五、dubbo的用法示例

1、version版本号

服务提供者可以指定版本号,这样消费者可以调用需要指定的版本号即可。

2、指定protocol协议

dubbo协议可支持的协议:

  • rest:
  • http
  • dubbo(netty实现)
  • 自定义协议

在配置文件:

在配置文件中,可以使用 protocols 进行多协议配置。

在提供者或者消费者端配置需要使用的协议即可:

如果配置服务未指定协议,但是配置文件配置了多个协议,那么就会生成配置个数的服务,例如配置了2种协议dubbo、rest,那么就会生成2个服务一个是dubbo协议、一个是rest协议。

3、使用rest访问dubbo的服务

需要注意依赖的引入。使用到jboss的依赖

       <!-- Dubbo rest protocol -->
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>3.15.1.Final</version>
        </dependency>

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>

同时需要注意,使用服务器,tomcat 

4、消费者通过制定url连接制定服务提供着

如果服务提供者配置了多个协议,那么在启动服务的时候,就会根据不同的协议提供多个服务。

如果在消费者这边 指明消费某个服务,需要配置:

@RestController
@RequestMapping("site")
public class SiteContorller {

    //订阅服务
    //生成代理对象
    @DubboReference(version = "v2", url = "dubbo://127.0.0.1:20882/com.mg.boot.api.SiteService")
    private SiteService siteService;

    @GetMapping("getName")
    public String getName(String name){
        return siteService.getName(name);
    }

}

5、服务超时

服务超时,就是调用接口超过服务消费者设置的最长时间。

在dubbo中,直接使用timeout,设置服务的超时时间。

服务提供者:如果超时,会打印超时日志,并且会执行完毕。

@DubboService(version = "timeout", timeout = 4000)
public class TimeOutSiteSerivce implements SiteService {

    @Override
    public String getName(String name) {
        try {
            Thread.sleep(5000);
        }catch (Exception e){}
        System.out.println("服务。。。。");
        return "timeout service " + name;
    }
}

服务消费者:如果超时,则进行重试,重试失败抛出异常。

    @DubboReference(version = "timeout", timeout = 6000)
    private SiteService siteService;

服务提供者在任何情况下,都会执行业务逻辑。重试2次,就会执行3次(第一次是正常调用)。 

6、集群容错

@DubboReference(version = "timeout", timeout = 3000, cluster = "failover", retries = 1)

dubbo提供的集群容错方案:

  • failover(默认、推荐):当出现失败的时候,会进行重试其他的服务器,默认重试2次,会出现幂等性问题,但是可以在业务方面进行解决:
    • 方案1:把数据的业务ID作为数据库的联合主键,此时业务ID不能重复
    • 方案2:使用分布式锁来控制重复消费问题
  • failfast:当出现问题的时候,立即放回错误,不重试,通常用于非幂等操作,如新增记录
  • failsafe:当出现问题的时候,记录日志
  • failback:失败就失败,开启定时任务重发,通常用于通知操作
  • forking:并行访问多个服务器,获取一个结果既视为成功,forks=2,会浪费很多服务器资源
  • broadcast:广播所有服务提供者,逐个调用,任意一台报错则报错

结论:使用dubbo的时候,不推荐把重试关闭,在非幂等操作场景下,服务提供者需要提供幂等解决方案。

7、服务降级

所谓的服务降级,就是当达到流量高峰的时候,需要保留核心业务的使用。

mock:接口提供假数据的功能。

  • mock=force:return+null 表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时,对调用放的影响。
  • mock=fail:return+null 表示消费方对该服务方法在调用失败后,在返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
 @DubboReference(version = "timeout", timeout = 3000, mock = "fail:return timeout")
    private SiteService siteService;

8、本地存根

本地存根,就是在服务消费者执行部分逻辑。

首先在消费方开启本地存根功能:

@DubboReference(version = "timeout", timeout = 1000, stub = "true")
    private SiteService siteService;

同时在接口的相同包下建立存根类,这样就会在注入接口的时候,在封装一层存根类的逻辑:

public class SiteServiceStub implements SiteService {

    private final SiteService siteService;

    public SiteServiceStub(SiteService siteService) {
        this.siteService = siteService;
    }


    @Override
    public String getName(String name) {
        try {
            return siteService.getName(name);
        }catch (Exception e){
            return "stub" + name;
        }
    }
}

9、参数回调

消费者为服务提供者提供一个回调接口,也就是参数回调。

  • 在接口层中增加方法,增加回调参数
    package com.mg.boot.api;
    
    public interface SiteServiceCallBack {
    
        String siteName(String name);
    
        default String siteName(String name , String key, SiteServiceCallBackListener siteServiceCallBackListener){
            return null;
        }
    }
    
  • 增加回调接口和实现类
    
    import java.io.Serializable;
    
    public class SiteServiceCallBackListenerImpl implements SiteServiceCallBackListener, Serializable {
        @Override
        public String change(String data) {
            System.out.println("change:" + data);
            return "change:" + data;
        }
    }
  • 服务提供者,指明了哪个方法、第几个参数是回调参数
    @DubboService(version = "callback", methods = {@Method(name="siteName", arguments = {@Argument(index = 2, callback = true)})}, callbacks = 3)
    public class SiteServiceCallBackImpl implements SiteServiceCallBack {
    
    
        @Override
        public String siteName(String name) {
            return null;
        }
    
        @Override
        public String siteName(String name , String key, SiteServiceCallBackListener siteServiceCallBackListener){
            siteServiceCallBackListener.change("p data");
            return "cc:" + name;
        }
    
    }
  • 服务消费者
        @DubboReference(version = "callback")
        private SiteServiceCallBack siteServiceCallBack;
    
        @GetMapping("getName")
        public String getName(String name){
            return siteServiceCallBack.siteName(name, "key",  new SiteServiceCallBackListenerImpl());
        }

10、异步调用

主要使用 CompletableFuture 接口进行一步数据的返回。

 @Override
    public CompletableFuture<String> siteNameAsync(String name){
        System.out.println("data:" + name);
        return CompletableFuture.supplyAsync(()->{
            return siteName(name);
        });
    }

六、dubbo的负载均衡策略

官方提供的集中负载均衡的策略:

  • 随机
  • 轮训
  • 一致性hash:相同参数的请求,总是发送到一个提供者
  • 最少活跃调用数:就是处理慢的提供者(服务配置低)接收到少的请求,根据调用的计数差
    • 消费者在本地缓存所有的服务提供者
    • 调用的时候,会选择本地所有服务提供者中active最小的
    • 选定该服务提供者和后,并对其active+1
    • 完成服务后,对其active-1
    • 当active越大,则证明该服务提供者处理性能越差

七、安装Dubbo admin监管平台

可以使用本地安装,也可以使用docker安装。这里介绍受用docker安装方式。

docker run --name dubbo-admin -d -p 7001:7001 dubbo-admin:1.0

在admin中,可以配置相关权重、方法路由规则等等。 

八、Dubbo的SPI可扩展机制

1、java中的sip机制

典型的使用sip,就是jdbc。java内部之约定了jdbc的接口,并没有提供具体实现。当需要连接mysql数据库的时候,就需要mysql实现这套jdbc的接口。这就是java中的sip机制。

如mysql jdbc:

java的DriverManager就会读取这个文件,获取实现类的全路径。

简单理解就是定义一套规范,实现来自不同的供应者,当需要使用哪个供应者的相关内容,引入即可。 

2、sip机制的缺点

自己实现的时候,就使用SeviceLoader 进行加载 配置文件中配置的实现类。 

可以看出java中sip存在,当配置多个实现类的时候,使用ServiceLoader可以获取多个,没有办法指定某一个,不够灵活。

3、dubbo中的sip机制

可以看到,在dubbo中,可以指明需要哪个实现类。

在dubbo中,有很多地方使用spi,例如服务调用协议的部分,根据配置文件中的配置,进行协议的使用。

同时,还可以在spi实现类中实现AOP的效果,其实就是在实现代码的时候,在前面或者后面增加相关功能。

九、Dubbo源码分析

1、Dubbo服务调用过程

a、服务消费者获取代理对象

b、 在注册中心获取服务集群(缓存在消费者本地的地址列表),使用ZookeeperRegistry

c、使用服务器集群中的Invoker调用器,Invoker封装了一次调用流程的整体内容

d、参数、方法名称等封装在RpcInvcation中,使用DubboInvoker进行调用

2、关于DubboInvoker的装饰

通过源码可以看出,Invoker调用采用的是装饰着模式,每一层封装不同的功能。

  • DubboInvoker:使用dubbo协议调用提供者
  • AsyncToSyncInvoker:把异步转换成同步
  • ProtocolFilterInvocker:在调用过程中,提供了大量的功能如监控中心发送调用数据、TSP限流等等,所以主要负责维护过滤器链
  • ListenerInvocker:负责监听链,提供业务回调窗口

3、权重轮训算法

权重轮训就是根据权重去轮训相关内容,例如A、B、C三台权重6、2、2,那调用应该是ABACA的顺序。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐