分布式RPC框架Apache Dubbo(下)

248 阅读32分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情

4. Dubbo开发-SpringBoot(掌握)

Dubbo作为一个RPC框架,其最核心的功能就是要实现跨网络的远程调用。

本小节就是要创建两个应用,一个作为服务的提供方,一个作为服务的消费方。

通过Dubbo来实现服务消费方远程调用服务提供方的方法。

版本开发要求说明: 开发中所有的版本统一,避免环境问题导致的代码无法运行

  • jdk1.8 环境变量配置要OK
  • springboot 2.3.0
  • dubbo 2.7.5
  • zookeeper 3.5.8
  • maven 环境搭建OK 连接好阿里云远程仓库

目标:完成dubbo提供方和消费方代码实现

开发前准备:

本次开发我们采用apache 2.7.5 文档版本讲解

注解开发文档参照:

dubbo.apache.org/en-us/docs/…

dubbo.apache.org/en-us/ 官方开发步骤参照

1577263464136.png

4.0 开发流程说明

以前传统的开发模式:

1577322942558.png


采用Dubbo 框架SOA面向服务开发流程图说明:

www.processon.com/diagraming/…

1603770645215.png

4.1 搭建maven多模块工程

开发步骤:建议官方 get started ! 注解开发一栏: Annotation Configuaration

官网配置代码

生产者:

1597308745187.png 1597308779256.png

消费者:

1597308792795.png

1597308803957.png 1597308832386.png

按照官方路径说明:

创建3个模块: 模块说明

  • dubbo-api 模块 存放消费者和生产者通用接口组+通用模块
  • dubbo-provider 服务提供者 ,生产者 提供业务方法的实现
  • dubbo-cosumer 服务消费者,请求服务,获取实现

搭建结构:

1597367559304.png

父模块工程pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
    </parent>
​
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

api模块 pom.xml

 因为目前测试。。。暂无依赖配置

api模块 编写通用的服务接口: 接口包名可以自定义

public interface UserService {
    public String  sayHello(String name);
}

01.png

4.2 开发生产者模块

生产者作为服务的提供者 dubbo官方提供非常简单的注解配置: 我们只需要学习两个注解即可:

作用:

@Service 注解 来完成服务的发布 注意导包的时候,要导入dubbo官方提供的包。

@EnabledDubbo 在启动类上添加,自动扫描所有的服务类(有@Service注解的类) 注册到zookeeper上发布

  1. 创建子模块 pom导入api依赖即可
 <dependencies>
        <!--springboot 整合dubbo 开始 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <version>2.7.5</version>
            <type>pom</type>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--springboot 整合dubbo 结束-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
     
       引入上述api模块坐标
        <dependency>
            <groupId>com.it.dubbo</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
     
</dependencies>
  1. 搭建生产者工程目录

image.png 3. 编写服务类: 注意使用注解 不要导错包 @Service

​
注意 : @Service注解不要导错包!!!!!
​
@Service//  该注解 : 1  生成UserService接口实例对象   2. 提供服务类 后续会结合 @EnableDubbo注解 服务注册到zookeeper中心上
public class UserServiceImpl implements UserService {
    @Override
    public String sayHello(String name) {
        System.out.println("----服务访问了一次------后续访问数据源");
        return "hello dubbo "+name;
    }
}
  1. 配置dubbo服务发布: 编写 application.properties文件 复制官方模板修改即可

image.png

修改一下端口号:避免后续启动服务 端口被占用: 8088

# dubbo-provider.properties
dubbo.application.name=annotation-springboot-provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
server.port=8088

确保本地zookeeper服务启动 : 黑窗口打开!

image.png 5. 编写启动类: 启动main方法 发布服务

@SpringBootApplication
@EnableDubbo(scanBasePackages= "com.it.service.impl")// 官方提供的模板,修改扫描的包名: 自己的@Service类所在的包名
public class UserApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class,args);
        System.out.println("---生产者服务启动-----8088");
    }
}
​
  1. 打开 dubbo-admin后台管理界面 查看服务生产者有无注册!

服务生产者发布成功!!!! image.png

4.3 开发消费者模块

同生产者开发类似:

  • @Reference 获取、订阅服务
  1. 创建子模块,dubbo-consumer pom依赖 api即可
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--springboot 整合dubbo 开始 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <version>2.7.5</version>
            <type>pom</type>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--springboot 整合dubbo 结束-->
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
     
       引入上述api模块坐标
        <dependency>
            <groupId>com.it.dubbo</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
     
</dependencies>
  1. 编写Controller 对象 当浏览器发送请求: springmvc接受请求,调用dubbo服务
@RestController
public class UserController {
​
    @Reference  //  注解不要导错包  导入dubbo的才行  注册服务到zookeeper
    private UserService userService;
​
    @GetMapping("hello")
    public  String  hello(String name){
​
        String s = userService.sayHello(name);
​
        return "hello"+s;
    }
}
  1. 编写启动类
@SpringBootApplication
public class ConsumerMain {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerMain.class,args);
        System.out.println("----消费者启动----");
    }
}

注意启动类的位置:要能扫描到controller对象

image.png

  1. 配置dubbo服务

编写application.properties

# dubbo-consumer.properties
dubbo.application.name=annotation-springboot-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.consumer.check=false
server.port=8081
  1. 启动main方法 服务消费者编写完成 查看dubbo-admin窗口

image.png 测试:

浏览器输入地址: 访问消费者 localhost:8081/hello?name=heima

image.png

开发完成!

4.4 Dubbo快速入门开发小结

  1. zookeeper安装和启动
  2. 编写服务通用接口模块
  3. 编写服务生产者和消费者
  4. 启动类,发布服务到zookeeper

image.png

4.5springboot整合dubbo与基于api接口开发的对照关系表

API接口SpringBoot配置
ApplicationConfigyml文件:spring.application
ProtocolConfigyml文件:dubbo.protocol
RegistryConfigyml文件:dubbo.registry
ServiceConfig注解@Service
ReferenceConfig注解@Reference

4.6Springboot开发小结

1.properties核心配置属性
    dubbo.config-center:服务注册初始化zookeeper连接超时时间
    dubbo.registry.address:设置注册中心类型
    dubbo.protocol.name:设置协议类型,一般设置默认的dubbo
    dubbo.protocol.port:设置dubbo协议端口号,-1表示端口从默认的20880开始,如果有端口占用就进行递增
2.服务提供方对外暴露接口服务使用@Service注解
3.服务消费方引用服务接口使用@Reference注解

4.6.1 包扫描

      <!--开启生产者和消费者的注解扫描-->
 @EnableDubbo(scanBasePackages= "com.it.main.xxx")
       配置的是@Reference 和 @Service类所在的包名
用法一致:
   都是在启动类上添加!!
   
 application.properties文件也可以配置扫描包:其作用一样: 
   这样就可以不用在启动类上添加   @EnableDubbo 注解
 dubbo.scan.base-packages:包扫描配置,扫码包下面的@Service和@Reference注解
 
 springboot整合dubbo之后,消费者端可以不用配置包扫描! 确保启动类可以扫描到@Reference注解即可!
 

服务发布注解

@Sericve  :  用于生产者方  发布服务  一般都在服务接口实现类上  XxxServiceImpl上
​
例如:
   @Service
public class UserServiceImpl implements UserService {
​
​
​
@Reference : 用于消费者方  消费者发布服务   一般都Controller类里面 注入 服务类使用
例如:
@RestController
public class UserController {
​
    @Reference  //  获取服务代理实现类  dubbo实现
    private UserService userService;

4.6.2 协议

dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
​
如果端口号配置 -1 表示
dubbo协议缺省端口为20880,rmi协议缺省端口为1099,http和hessian协议缺省端口为80;如果没有配置port,则自动采用默认端口,如果配置为-1,则会分配一个没有被占用的端口。
Dubbo 2.4.0+,分配的端口在协议缺省端口的基础上增长,确保端口段可控。

一般在服务提供者一方配置,可以指定使用的协议名称和端口号。

其中Dubbo支持的协议有:dubbo、rmi、hessian、http、webservice、rest、redis等。

推荐使用的是dubbo协议。

dubbo 协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。

也可以在同一个工程中配置多个协议,不同服务可以使用不同的协议

4.6.3 启动时检查

一般都是在消费者端: 启动顺序一般都是 先启动生产者 再启动消费者

如果先启动消费者:控制台会有异常信息: 可以把消费者 check=false 开启 不检测服务!

dubbo.consumer.check=false

上面这个配置需要配置在服务消费者一方,如果不配置默认check 和 default 值为true。

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题。可以通过将check值改为false来关闭检查。

1592917418780.png 建议在开发阶段将check值设置为false,在生产环境下改为true。

消费端application.properties/yaml配置即可!

4.6.4 yaml文件配置

开发中我们通常会使用yaml 而不使用propperties文件

修改一下即可:

生产者:

server:
  port: 8088

dubbo:
  application:
    name: annotation-springboot-provider
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: 20880

消费者:

# dubbo-consumer.properties
server:
  port: 8081

dubbo:
  consumer:
    check: false
  registry:
    address: zookeeper://127.0.0.1:2181
  application:
    name: annotation-springboot-consumer

5.dubbo的注册中心(了解)

前面的示例课程中我们用到了注册中心,dubbo在服务提供者和服务消费者在启动的时候都会连接注册中心。dubbo的官方文档中推荐使用zookeeper作为注册中心.

5.1 zookeeper(重要)

15.jpg

流程说明

  • 服务提供者启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址
  • 服务消费者启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址
  • 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。

使用: yml文件直接配置即可

dubbo:
  registry:
    address: zookeeper://127.0.0.1:2181

5.2 redis

16.jpg

使用 Redis 的 Key/Map 结构存储数据结构:

  • 主 Key 为服务名和类型
  • Map 中的 Key 为 URL 地址
  • Map 中的 Value 为过期时间,用于判断脏数据,脏数据由监控中心删除

使用,需要在maven中添加依赖:

       <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>

配置文件中使用:

dubbo:
  registry:
    address: redis://127.0.0.1:6379

image.png

5.3 Multicast

Multicast 注册中心不需要启动任何中心节点,只要广播地址一样,就可以互相发现。 17.jpg

  1. 提供方启动时广播自己的地址
  2. 消费方启动时广播订阅请求
  3. 提供方收到订阅请求时,单播自己的地址给订阅者,如果设置了 unicast=false,则广播给订阅者
  4. 消费方收到提供方地址时,连接该地址进行 RPC 调用。

组播受网络结构限制,只适合小规模应用或开发阶段使用。组播地址段: 224.0.0.0 - 239.255.255.255

5.4 Nacos

Nacos 是 Dubbo 生态系统中重要的注册中心实现,其中 dubbo-registry-nacos 则是 Dubbo 融合 Nacos 注册中心的实现。

我们后续课程讲spring cloud aliaba还会用到nacos,所以nacos这块咋们本次课程不深入讲解.

5.5 Simple

Simple 注册中心本身就是一个普通的 Dubbo 服务,可以减少第三方依赖,使整体通讯方式一致。

服务暴露:

dubbo:
  protocol:
    port: 9090

引用配置:

dubbo:
  registry:
    address: 127.0.0.1:9090

6.dubbo协议概述(了解)

dubbo框架中支持调用的协议有很多,如:dubbo、hessian、rmi、http等,推荐使用 Dubbo 协议,各协议性能对比:

19.png

Dubbo协议

Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。

反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。

20.jpg

  • Transporter: mina, netty, grizzy
  • Serialization: dubbo, hessian2, java, json
  • Dispatcher: all, direct, message, execution, connection
  • ThreadPool: fixed, cached

特性

缺省协议,使用基于 mina 1.1.7 和 hessian 3.2.1 的 tbremoting 交互。

  • 连接个数:单连接
  • 连接方式:长连接
  • 传输协议:TCP
  • 传输方式:NIO 异步传输
  • 序列化:Hessian 二进制序列化
  • 适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。
  • 适用场景:常规远程服务方法调用

约束

  • 参数及返回值需实现 Serializable 接口
  • 参数及返回值不能自定义实现 List, Map, Number, Date, Calendar 等接口,只能用 JDK 自带的实现,因为 hessian 会做特殊处理,自定义实现类中的属性值都会丢失。
  • Hessian 序列化,只传成员属性值和值的类型,不传方法或静态变量,兼容情况
数据通讯情况结果
A->B类A多一种 属性(或者说类B少一种 属性)不抛异常,A多的那 个属性的值,B没有, 其他正常
A->B枚举A多一种 枚举(或者说B少一种 枚举),A使用多 出来的枚举进行传输抛异常
A->B枚举A多一种 枚举(或者说B少一种 枚举),A不使用 多出来的枚举进行传输不抛异常,B正常接 收数据
A->BA和B的属性 名相同,但类型不相同抛异常
A->BserialId 不相同正常传输

接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署。输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新部署。

输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。

总结:服务器端和客户端对领域对象并不需要完全一致,而是按照最大匹配原则。

配置协议

默认配置:

dubbo:
  protocol:
    name: dubbo

配置协议选项:

属性对应URL参数类型是否必填缺省值作用描述兼容性
idstring可选dubbo配置关联协议BeanId,可以在<dubbo:service protocol="">中引用此ID,如果ID不填,缺省和name属性值一样,重复则在name后加序号。2.0.5以上版本
namestring必填dubbo性能调优协议名称2.0.5以上版本
portint可选dubbo协议缺省端口为20880,rmi协议缺省端口为1099,http和hessian协议缺省端口为80;如果没有配置port,则自动采用默认端口,如果配置为**-1**,则会分配一个没有被占用的端口。Dubbo 2.4.0+,分配的端口在协议缺省端口的基础上增长,确保端口段可控。服务发现服务端口2.0.5以上版本
hoststring可选自动查找本机IP服务发现-服务主机名,多网卡选择或指定VIP及域名时使用,为空则自动查找本机IP,-建议不要配置,让Dubbo自动获取本机IP2.0.5以上版本
threadpoolthreadpoolstring可选fixed性能调优线程池类型,可选:fixed/cached2.0.5以上版本
threadsthreadsint可选200性能调优服务线程池大小(固定大小)2.0.5以上版本
iothreadsthreadsint可选cpu个数+1性能调优io线程池大小(固定大小)2.0.5以上版本
acceptsacceptsint可选0性能调优服务提供方最大可接受连接数2.0.5以上版本
payloadpayloadint可选8388608(=8M)性能调优请求及响应数据包大小限制,单位:字节2.0.5以上版本
codeccodecstring可选dubbo性能调优协议编码方式2.0.5以上版本
serializationserializationstring可选dubbo协议缺省为hessian2,rmi协议缺省为java,http协议缺省为json性能调优协议序列化方式,当协议支持多种序列化方式时使用,比如:dubbo协议的dubbo,hessian2,java,compactedjava,以及http协议的json等2.0.5以上版本
accesslogaccesslogstring/boolean可选服务治理设为true,将向logger中输出访问日志,也可填写访问日志文件路径,直接把访问日志输出到指定文件2.0.5以上版本
pathstring可选服务发现提供者上下文路径,为服务path的前缀2.0.5以上版本
transportertransporterstring可选dubbo协议缺省为netty性能调优协议的服务端和客户端实现类型,比如:dubbo协议的mina,netty等,可以分拆为server和client配置2.0.5以上版本
serverserverstring可选dubbo协议缺省为netty,http协议缺省为servlet性能调优协议的服务器端实现类型,比如:dubbo协议的mina,netty等,http协议的jetty,servlet等2.0.5以上版本
clientclientstring可选dubbo协议缺省为netty性能调优协议的客户端实现类型,比如:dubbo协议的mina,netty等2.0.5以上版本
dispatcherdispatcherstring可选dubbo协议缺省为all性能调优协议的消息派发方式,用于指定线程模型,比如:dubbo协议的all, direct, message, execution, connection等2.1.0以上版本
queuesqueuesint可选0性能调优线程池队列大小,当线程池满时,排队等待执行的队列大小,建议不要设置,当线程池满时应立即失败,重试其它服务提供机器,而不是排队,除非有特殊需求。2.0.5以上版本
charsetcharsetstring可选UTF-8性能调优序列化编码2.0.5以上版本
bufferbufferint可选8192性能调优网络读写缓冲区大小2.0.5以上版本
heartbeatheartbeatint可选0性能调优心跳间隔,对于长连接,当物理层断开时,比如拔网线,TCP的FIN消息来不及发送,对方收不到断开事件,此时需要心跳来帮助检查连接是否已断开2.0.10以上版本
telnettelnetstring可选服务治理所支持的telnet命令,多个命令用逗号分隔2.0.5以上版本
registerregisterboolean可选true服务治理该协议的服务是否注册到注册中心2.0.8以上版本
contextpathcontextpathString可选缺省为空串服务治理2.0.6以上版本

6.Dubbo管理控制台

我们在开发时,需要知道Zookeeper注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我们可以通过部署一个管理中心来实现。其实管理中心就是一个web应用,部署到tomcat即可。

6.1 安装使用

源码下载地址:github.com/apache/dubb…

代码使用develop分支。

windows:
 java -jar {dubbo-admin.jar目录}/dubbo-admin.jar
​
linux:
 nohup java -jar {dubbo-admin.jar目录}/dubbo-admin.jar &

运行完以后访问浏览器地址:http://localhost:8101

运行dubbo-admin.jar需要注意的几点:

    1.默认端口为8101如果端口存在冲突需要更改默认端口
    
    2.zookeeper连接地址为127.0.0.1:2181如果不是则需要进行更改
    
    更改可以直接用WinRAR打开,
    在BOOT-INF/classes/application.properties中修改,先解压到桌面或其它地方进行编辑,编辑完以后把最新文件拖放到压缩文件进行覆盖,然后再重新运行ok。

20.png

运行后效果: 浏览器直接访问 地址即可:

21.png

点击服务查询菜单会跳转到登入页面:

22.png

此时输入默认的账号密码:root/root进行登入.

6.2控制台基本使用

6.2.1服务查询

查询结果列说明:

属性名称描述
服务名服务提供方暴露的服务接口路径
服务的组名(dubbo的高级特性会再次使用到)
版本服务的版本号(dubbo的高级特性会再次使用到)
应用服务提供方启动应用时注册的应用名称

6.2.2服务详情

在服务查询的结果页中,如果存在服务数据,那么在操作列对应的每一行具体服务都有一个“详情”按钮,点击详情进入到服务详情页面(可以查看具体的提供者和消费者列表):

image.png

7. Dubbo的负载均衡(了解)

7.1 什么是负载均衡

负载均衡,它的职责是将网络请求,或者其他形式的负载“均摊”到不同的机器上。
避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。
通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载。在为高负载服务器分流的同时,还可以避免资源浪费.
​
Dubbo是一个分布式服务框架,能避免单点故障和支持服务的横向扩容。一个服务通常会部署多个实例。如何从多个服务 Provider 组成的集群中挑选出一个进行调用,就涉及到一个负载均衡的策略。
​
负载均衡(Load Balance):其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。

详细解释: 负载均衡 英文 : LoadBalance

LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载“均摊”到不同的机器上。
避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。
通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载。在为高负载服务器分流的同时,还可以避免资源浪费,一举两得。负载均衡可分为软件负载均衡和硬件负载均衡。
​
Dubbo 需要对服务消费者的调用请求进行分配,避免少数服务提供者负载过大。服务提供者负载过大,会导致部分请求超时。因此将负载均衡到每个服务提供者上,是非常必要的。
​
Dubbo 提供了4种负载均衡实现,分别是:
​
 - 基于权重随机算法的 Random LoadBalance
 - 基于最少活跃调用数算法的 LeastActive LoadBalance
 - 基于加权轮询算法的 RoundRobin LoadBalance。
 
这几个负载均衡算法代码不是很长,下面我们就通过代码来演示负载均衡实际效果: 

7.2 负载均衡代码演示

代码实现:负载均衡演示 实际开发中需要三台服务器来进行负载均衡!

我们将服务提供者复制2份 代表2台机器,分别注册到zookeeper 并且发布UserService服务

工程改造如下:三天服务器的服务开发

模拟一个消费者 : 2个生产者

通过: RpcContext.getContext().getUrl().getPort(); // 查看服务器的响应情况!

业务层对象:UserServiceImpl

@Service
//  该注解 : 1  生成UserService接口实例对象   2. 提供服务类 后续会结合 @EnableDubbo注解 服务注册到zookeeper中心上
public class UserServiceImpl implements UserService {
    @Override
    public String sayHello(String name) {
        int remotePort = RpcContext.getContext().getUrl().getPort(); //  获取不同服务器的端口号。。。
        System.out.println("----服务器tps访问了一次------后续访问数据源"+remotePort);
        return "dubbo "+name;
    }
}
​
Main启动类: 分别2台服务器:
​
@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.it.main.impl")// 官方提供的模板,修改扫描的包名: 自己的@Service类所在的包名
public class ProviderMain1 {
​
    public static void main(String[] args) {
        SpringApplication.run(ProviderMain1.class,args);
        System.out.println("---1台生产者服务启动-----");
    }
}
​
​
​
@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.it.main.impl1")// 官方提供的模板,修改扫描的包名: 自己的@Service类所在的包名
public class ProviderMain2 {
​
    public static void main(String[] args) {
        SpringApplication.run(ProviderMain2.class,args);
        System.out.println("---2台生产者服务启动-----");
    }
}
​
​
​
依次启动:

我们的2台服务器搭建完成,只需要启动main方法 即可完成三台服务器的启动

  1. yaml文件需要修改一下: 三台服务器端口 以20880 开始 依次递增

1597386111515.png 2台服务器启动的 tomcat端口号设置可以通过idea 设置

server:
  port: ${port}    # idea工具配置指定端口 多台服务器tomcat端口 由启动类配置决定

dubbo:
  application:
    name: annotation-springboot-provider1
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: -1    # dubbo官方文档  -1 端口号随机 不会冲突

【配置】启动类: ${port} 说明:

  1. 点击编辑

1597417298081.png

  1. 对启动类进行编辑端口 - 复制两个应用程序。

image.png

对应的 vm options选项 :输入 指定的端口号: -Dserver.port=8081

Main class 指定到对应的启动类即可

依次输入: -Dserver.port=8081 和 -Dserver.port=8082

依次启动2个main 启动类 : 2台服务器启动,服务注册到zookeeper上

image.png

至此我们完成了2台服务器的搭建!

此时我们启动客户端项目,每刷新浏览器一次 获取服务资源! 默认机制: Random !

其他的均衡策略: 语法 : @Service(loadBalance="策略关键字")

Dubbo提供了4中负载均衡策略如下:

1、Random LoadBalance

随机,按权重设置随机概率,默认使用此策略

在一个截面上碰撞的概率越高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整权重。直接在注解上配置参数即可

1597417708076.png 在实际项目我们配置权重或负载均衡时不在代码中写,我们动态使用默认配置,需要调节时通过dubbo管控台上进行配置 (开发时,推荐使用 图形化管理控制)

启动2台服务器: main1,2

根据 : 倍权或者 半权 来设置 访问该服务器的频次

2、RoundRobin LoadBalance

轮询,按公约后的权重设置轮询比率。

存在慢的提供者累计请求问题,比如:第二台机器很慢,但没挂,当请求调用到第二台就卡在那儿,久而久之,所有请求都卡在调第二台上。

我们只演示一种:

在业务层@Service注解添加 负载均衡关键字即可:

@Service(loadbalance = "roundrobin")
//  该注解 : 1  生成UserService接口实例对象   2. 提供服务类 后续会结合 @EnableDubbo注解 服务注册到zookeeper中心上
public class UserServiceImpl implements UserService {
​
    @Override
    public String sayHello(String name) {
        int remotePort = RpcContext.getContext().getUrl().getPort(); //  获取不同服务器的端口号。。。
        System.out.println("----服务器tps访问了一次------后续访问数据源"+remotePort);
        return "dubbo "+name;
    }
}

重启动生成者和消费者: 浏览器访问: 轮询的结果:

1597417821835.png 1597417838477.png

3、LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

说明: 使用场景很少!

例如,每个服务维护一个活跃数计数器。当A机器开始处理请求,该计数器加1,此时A还未处理完成。若处理完毕则计数器减1。而B机器接受到请求后很快处理完毕。那么A,B的活跃数分别是10。当又产生了一个新的请求,则选择B机器去执行(B活跃数最小),这样使慢的机器A收到少的请求

8. Dubbo超时

8.1 何谓超时

服务消费者引用服务提供者的服务时可能由于网络原因导致长时间未返回相应,此时大量的线程将会阻塞,引起性能下降等问题。可以通过引入服务超时来解决该问题,一般配合retries(重试次数)使用。单位毫秒,默认值1000毫秒

8.2 Dubbo超时的原理

dubbo默认采用了netty做为网络组件,它属于一种NIO的模式。
消费端发起远程请求后,线程不会阻塞等待服务端的返回,而是马上得到一个ResponseFuture,消费端通过不断的轮询机制判断结果是否有返回。
因为是通过轮询,轮询有个需要特别注要的就是避免死循环,所以为了解决这个问题就引入了超时机制,只在一定时间范围内做轮询,如果超时时间就返回超时异常

8.3 超时解决的是什么问题

当前端大量请求并发出现时,很有可能将业务线程池中的线程消费完,因为默认缺省的线程池是固定大小,
对调用的服务设置超时时间,是为了避免因为某种原因导致线程被长时间占用,最终出现线程池用完返回拒绝服务的异常。

8.4 超时代码实现

作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间、合理的重试次数等

往往在 Provider 配置超时,Consumer 不配置则会使用 Provider 的配置值

远程服务调用超时时间(毫秒),缺省值为1000.

项目设计超时原则:Provider 上尽量多配置 Consumer 端的属性,让 Provider 实现者一开始就思考 Provider 服务特点、服务质量等问题。

代码演示:

  1. 未配置超时演示 : 如果服务端耗时过长,长时间没有给客户端响应,将会抛出异常

模拟服务器端超时 可以采用延迟服务器端代码执行 添加Thread.sleep 模拟延迟效果

业务实现代码添加 UserServiceImpl.java

@Service
//  该注解 : 1  生成UserService接口实例对象   2. 提供服务类 后续会结合 @EnableDubbo注解 服务注册到zookeeper中心上
public class UserServiceImpl implements UserService {
    @Override
    public String sayHello(String name) {
        try {
            Thread.sleep(3000); // 演示网络延迟  。。。超时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int remotePort = RpcContext.getContext().getUrl().getPort(); //  获取不同服务器的端口号。。。
        System.out.println("----服务器tps访问了一次------后续访问数据源"+remotePort);
        return "dubbo "+name;
    }
}

客户端正常访问: 超时抛出异常

1597390575696.png

image.png

配置超时代码演示 timeout=5000 表示 服务端可以等待5秒给与响应而不会立刻抛出异常

@Service(timeout = 5000)

@Service(timeout = 5000)
//  该注解 : 1  生成UserService接口实例对象   2. 提供服务类 后续会结合 @EnableDubbo注解 服务注册到zookeeper中心上
public class UserServiceImpl implements UserService {
    @Override
    public String sayHello(String name) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int remotePort = RpcContext.getContext().getUrl().getPort(); //  获取不同服务器的端口号。。。
        System.out.println("----服务器tps访问了一次------后续访问数据源"+remotePort);
        return "dubbo "+name;
    }
}

此时虽然客户端有延迟,但是服务器端不会立刻抛出异常,在超时时间内,服务器和客户端正常通信!

超时配置:往往是根据业务场景来配置超时时间的!默认超时时间1秒,

企业开发中,由于不同的业务场景,当超时请求时,为了不影响程序核心功能,我们往往会进行 相关的容错或降级的处理!

超时的时间不宜太短或过长,在分布式系统中,如果某一个应用程序响应时间过长很可能会导致系统瘫痪或宕机,

因此在实际开发中,超时的程序一般都会进行服务容错配置或服务降级处理

9. Dubbo服务降级

9.1 Dubbo服务降级目的

降级的目的是为了保证核心服务可用

降级的优势

  • 可以通过服务降级功能,临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
  • 当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些不重要不紧急的服务或任务进行服务的延迟使用暂停使用

降级场景说明

- 对一些非核心服务进行人工降级,在大促之前通过降级开关关闭那些推荐内容、评价等对主流程没有影响的功能
- 故障降级,比如调用的远程服务挂了,网络故障、或者RPC服务返回异常。 
​
那么可以直接降级,降级的方案比如设置默 认值、采用兜底数据(系统推荐的行为广告挂了,可以提前准备静态页面做返回)等等
​
限流降级,在秒杀这种流量比较集中并且流量特别大的情况下,因为突发访问量特别大可能会导致系统支撑不了。这个时候可以采用限流来限制访问量。当达到阀值时,后续的请求被降级,比如进入排队页面,比如跳转到错误页(活动太火爆,稍后重试等)
​

看图理解:

以刚刚的用户管理和订单管理 调用同一个应用服务: 地址服务管理为例,采用分布式架构,用户流量增加,此时很容易出现网络访问延迟现象,那么一个请求应用如果占用资源时间过长,就会导致整个系统宕机或瘫痪!

这时我们通常会采用服务降级处理。

1577695300930.png 通过对服务降级,我们可以确保系统的核心功能正常运作,其他的资源得以正常运作!

9.2 Dubbo的降级方式

Dubbo的降级方式采用: Mock方式

服务降级: 在客户端编写mock对象,来处理降级业务逻辑: 当服务出现超时或阻塞,(消费者端)编写一个服务处理类,用于处理服务降级业务实现!

通过超时异常来模拟服务异常超时的场景。

当服务端超时 ,超时后将执行服务接口名Mock.java中的提供的二级解决方案(B计划)来降级。

我们可以在消费者端添加如下代码:

编写代码规则: 需要对哪一个服务进行降级,只需要对该服务接口编写一个实现类实现该服务接口即可!

实际开发中,

客户端往往对于访问的超时有一定的估算,

如果超时,为了尽快给与用户相应,防止系统资源耗尽,一般客户端都会编写一个超时服务mock对象,进行降级方案的处理,

大白话就是,A计划失败,那么就执行B计划方案

代码演示: 上述案例 演示消费者服务降级处理

模拟超时,客户端访问服务超时,进行服务降级

降级规则:

编写Mock对象编写降级业务逻辑   注意 该对象的路径 一定要在服务接口同包名下创建
类名:  服务接口名+Mock  实现服务接口 编写对应的降级业务方法的实现!
Mock类会自动被创建!

9.3 降级代码演示

模拟客户端超时请求,进行降级处理

服务端服务方法内采用sleep模拟网络延迟情况:

服务端代码:

@Service(timeout = 5000)
@Transactional
public class UserServiceImpl  extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public String mock() {
        try {
            Thread.sleep(3000); //  模拟网络延迟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = RpcContext.getContext().getAttachment("name");
        System.out.println("service  mock----"+name);
      return "正常";
    }
}

客户端代码:

@Reference(timeout = 2000)
private UserService userService;
​
@GetMapping("mock")
public String  mock(){
    RpcContext.getContext().setAttachment("name","lisi"); //  隐式传递参数给服务端
    return  userService.mock();
}

此时访问会爆超时异常!

1601180335979.png 对此我们可以采用服务降级策略

编写mock业务处理类

1601180232114.png 配置降级策略

@Reference(timeout = 2000,mock = "cn.itcast.user.service.UserServiceMock")
private UserService userService;
​
@GetMapping("mock")
public String  mock(){
    RpcContext.getContext().setAttachment("name","lisi"); //  隐式传递参数给服务端
    return  userService.mock();
} 

再次访问:浏览器请求

image.png 降级业务类编写小结:

注意: Mock对象编写规则: 一般在客户端编写Mock对象 要求和服务接口包名同名

1577698064041.png

服务降级在出现分布式集群,由于服务器因各种原因(网络延迟等)响应时间较长造成的资源耗费就可以快速解决,客户有了友好体验,也解决了服务器的压力问题!


服务降级小结:

客户端超时时间优先级>服务器端超时配置,客户端没有配置,以服务器端配置为主!

服务降级 一般都是和超时结合一起使用

  • 客户端配置超时和降级:
  • 在@Reference注解里面配置 mock实现类和超时时间
  • 注意: mock类规则: 服务接口同包里面!

9.4 Dubbo服务阶段小结

超时: 是并发访问的一种消极解决方案! 网络并发量一旦达到一定的量,极容易造成服务器宕机!

服务消费者引用服务提供者的服务时可能由于网络原因导致长时间未返回相应,此时大量的线程将会阻塞,引起性能下降等问题。可以通过引入服务超时来解决该问题!

基于网络并发量大,为保证服务器不会宕机、死机!

我们基于超时提供如下的解决方案:

方案1: 服务容错 通过人为根据实际的网络环境进行调节对应的策略

为了减少某一个节点故障的影响范围,所以我们才需要去构建容错服务,来优雅的处理这种中断的响应结果!
容错机制:
​
- 查询语句容错策略建议使用默认Failover Cluster ,- 而增删改 建议使用 Failfast Cluster 或者 使用 Failover Cluster 策略,防止出现数据 重复添加等等其它问题

方案2:服务降级:降级的目的是为了保证核心服务可用 丢弃不重要或不紧急的服务,保证核心服务可用

- 当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些不重要或 不紧急的服务或任务进行服务的延迟使用或暂停使用。
​
此场景一般都是在集群 高并发量才会产生! 一般网站 通过负载均衡和超时 基本可以负荷网络并发量!

10.Dubbo其他配置说明:

retries

远程服务调用重试次数,不包括第一次调用,默认值为2 结合超时timeout一起使用!

register 服务端配合

该协议的服务是否注册到注册中心

配置服务是否注册 true 服务正常发布 false 服务不会发布出去!

用法: 表示当前服务 不注册到zookeeper上 ,服务不会发布出去!

1601177253852.png 效果:

1601177227796.png

隐式传参

通过RpcContext.getContext().setAttachment()进行传参

说明:远程调用服务之间数据共享,但是不会直接以方法参数形式传递,而是隐式传递!

演示:

消费端设置传递数据代码:

RpcContext.getContext().setAttachment("name","lisi"); //  隐式传递参数给服务端

服务端接收参数代码:

RpcContext.getContext().getAttachment("name"); //  服务端获取隐式传递参数

11小结

  1. 掌握dubbo服务端和消费端代码开发和相关配置
  2. 理解RPC远程调用
  3. 可以使用dubbo-admin查看相关服务者和消费者
  4. 了解dubbo负载均衡策略-理解
  5. 了解配置超时和服务降级Mock处理