初识 Dubbo

概述

​ Apache Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用智能容错负载均衡,以及服务自动注册和发现

​ Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案、服务治理方案。

​ 面向接口代理:调用接口的方法,在 A 服务器调用 B 服务器的方法,由 dubbo 实现对 B 的调用,无需关心实现的细节,就像 MyBatis 访问 Dao 的接口,可以操作数据库一样。不用关 心 Dao 接口方法的实现。

Dubbo 通过二进制流的形式进行数据传输。

什么是RPC?

​ RPC 【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,是一种技术思想,而不是规范。它允许程序调用另一个地址空间(网络的另一台机器上)的过程或函数,而不用开发人员显式编码这个调用的细节。调用本地方法和调用远程方法一样。

​ RPC 的实现方式可以不同。例如 java 的 rmi, spring 远程调用等。

​ RPC 概念是在上世纪 80 年代由 Brue Jay Nelson(布鲁·杰伊·纳尔逊)提出。使用 PRC 可以将本地的调用扩展到远程调用(分布式系统的其他服务器)。

基本架构

image-20220729162920864

  • 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

支持的协议

支持多种协议:dubbo , hessian , rmi , http, webservice , thrift , memcached , redis。 dubbo 官方推荐使用 dubbo 协议。dubbo 协议默认端口 20880

直连方式

点对点的直连项目:消费者直接访问服务提供者,没有注册中心。消费者必须指定服务提供者的访问地址(url)。

image-20220729163850087

直连案例

创建项目

创建两个 maven 项目,分别对应 消费者服务者

image-20220729171934895

image-20220729173015894

image-20220729173243543

image-20220729173448067

服务提供者

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <dependencies>
    <!--dubbo 依赖-->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
    </dependency>
    <!--Spring 依赖-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.16.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.16.RELEASE</version>
    </dependency>
    </dependencies>
  2. 创建接口类以及实现

    1
    2
    3
    4
    public interface SomeService {

    String hello(String msg);
    }
    1
    2
    3
    4
    5
    6
    public class SomeServiceImpl implements SomeService {
    @Override
    public String hello(String msg) {
    return "Hello " + msg;
    }
    }
  3. 安装 translation 插件(非必要)

    image-20220729180223711

    image-20220729180143095

    选中单词 ctrl shift y 可直接翻译

    image-20220729180357506

  4. 创建 link-provider.xml 配置文件

    在 resources 下创建,名称随意

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 声明服务提供者名称,保证它的唯一性,它是dubbo内部使用的唯一标识 -->
    <dubbo:application name="provider-01" />

    <!-- 指定 dubbo 协议名称和端口号(默认20880) -->
    <dubbo:protocol name="dubbo" port="20880" />

    <!-- 加载接口实现类到spring容器-->
    <bean id="someServiceImpl" class="com.cuc.dubbo.service.impl.SomeServiceImpl" />

    <!-- 暴露服务
    interface:暴露服务的接口全限定类名
    ref:引用接口在spring容器中的标识名称
    registry:注册中心,这里是直连方式不需要用到,值必须设置成 N/A
    -->
    <dubbo:service interface="com.cuc.dubbo.service.SomeService" ref="someServiceImpl" registry="N/A" />

    </beans>
  5. 配置 web.xml

    配置服务提供者监听器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">


    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:link-provider.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    </web-app>
  6. 打包

    先注释掉 <packaging>war</packaging>,打包后解回来

    image-20220729183707235

消费者

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <dependencies>
    <!--dubbo 依赖-->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
    </dependency>
    <!--Spring 依赖-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.16.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.16.RELEASE</version>
    </dependency>
    <!-- 服务者打的包 -->
    <dependency>
    <groupId>com.cuc</groupId>
    <artifactId>provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    </dependencies>

    不同的是还要引入服务者刚才打的包,直接去 provider 的 pom.xml 文件中复制就行。

    image-20220729184222777

  2. 创建 link-consumer.xml 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!--声明消费者名称,保证唯一,是dubbo内部唯一标识-->
    <dubbo:application name="consumer-01" />

    <!--引用远程接口
    id:远程接口代理对象名称
    url:调用远程接口服务的url地址
    -->
    <dubbo:reference id="someService" interface="com.cuc.dubbo.service.SomeService"
    url="dubbo://localhost:20880" registry="N/A" />

    </beans>
  3. 创建一个控制器方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Controller
    public class SomeController {

    @Autowired
    private SomeService someService;

    @RequestMapping("/hello")
    public String hello(Model model){

    // 调用远程接口服务
    String result = someService.hello("World");

    model.addAttribute("msg",result);

    return "hello";
    }
    }
  4. 创建 SpringMVC 配置文件

    在 resources 下创建 springmvc.xml,名称随意。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--扫描组件-->
    <context:component-scan base-package="com.cuc.dubbo.web" />

    <!--注解驱动-->
    <mvc:annotation-driven />

    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/" />
    <property name="suffix" value=".jsp" />
    </bean>

    </beans>
  5. 配置 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">

    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:link-consumer.xml,classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

    </web-app>
  6. 创建 jsp 页面

    在 webapp 下创建 hello.jsp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>
    <h1>${msg}</h1>
    </body>
    </html>

测试

  1. 配置 Tomcat

    端口不能重复

    image-20220729191814733

    image-20220729191802863

  2. 启动两个 tomcat

  3. 浏览器访问

    image-20220729192126841

存在的问题

消费者可以直接 new 接口实现类,并没有屏蔽底层细节,违背了官网所说的原则。

image-20220729193552847

image-20220729193820694

解决

项目结构

dubbo 官方推荐的项目结构

  • 服务提供者工程(web 工程)

    实现接口工程中的接口

  • 消费者工程(web 工程)

    消费接口工程提供的接口

  • 接口工程(java 工程)

    业务接口和实体类

创建Java工程

就是一个普通的 maven 工程

image-20220729195252137

image-20220729200927093

接口工程

  1. 创建 User 实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class User {
    private Integer id;
    private String username;

    public Integer getId() {
    return id;
    }

    public void setId(Integer id) {
    this.id = id;
    }

    public String getUsername() {
    return username;
    }

    public void setUsername(String username) {
    this.username = username;
    }
    }
  2. 创建 SomeService 接口类

    1
    2
    3
    4
    5
    6
    public interface SomeService {

    String hello(String msg);

    User queryUserById(Integer id);
    }

服务者

  1. 删除原本接口

    即 SomeService

  2. 引入接口工程

    1
    2
    3
    4
    5
    6
    <!--接口工程-->
    <dependency>
    <groupId>com.cuc</groupId>
    <artifactId>interface</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
  3. 实现接口工程的 SomeService 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class SomeServiceImpl implements SomeService {

    @Override
    public String hello(String msg) {
    return "Hello " + msg;
    }

    @Override
    public User queryUserById(Integer id) {
    User user = new User();
    user.setId(id);
    user.setUsername("swj");
    return user;
    }
    }

消费者

  1. 引入接口工程

    需删除之前引入的服务者依赖

    1
    2
    3
    4
    5
    6
    <!-- 接口工程 -->
    <dependency>
    <groupId>com.cuc</groupId>
    <artifactId>interface</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
  2. 修改控制器方法

    这时不会再 new 出接口实现类了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Controller
    public class SomeController {

    @Autowired
    private SomeService someService;

    @RequestMapping("/hello")
    public String hello(Model model){

    String result = someService.hello("World");
    model.addAttribute("msg",result);
    return "hello";
    }

    @RequestMapping("/user/detail")
    public String userDetail(Integer id,Model model){
    User user = someService.queryUserById(id);
    model.addAttribute("user",user);
    return "userDetail";
    }
    }
  3. 创建 userDetail.jsp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>
    <h2>用户编号:${user.id}</h2>
    <h2>用户姓名:${user.username}</h2>
    </body>
    </html>

测试

启动 服务者工程 以及 消费者工程

image-20220729202530324

image-20220729202753283

此时出错是因为 dubbo 传输是的是二进制流(需要序列化),字符串等类型会自动序列化,但是当服务提供者返回实体对象给消费者时,由于没有序列化,进而报错。

序列化对象

在接口工程修改 User 实体类

1
2
3
public class User implements Serializable {
...
}

再次测试

image-20220729203941189

ZK 案例

注册中心

概述

​ 对于服务提供方,它需要发布服务,而且由于应用系统的复杂性,服务的数量、类型也不断膨胀;对于消费者,它最关心如何获取到它所需要的服务,而面对复杂的应用系统, 需要管理大量的服务调用。

​ 而且,对于服务提供方和服务消费方来说,他们还有可能兼具这两种角色,既需要提供服务,又需要消费服务。 通过将服务统一管理起来,可以有效地优化内部应用对服务发布/使用的流程和管理。服务注册中心可以通过特定协议来完成服务对外的统一。Dubbo 提供的注册中心有如下几种类型可供选:

  • Multicast 注册中心:组播方式
  • Redis 注册中心:使用 Redis 作为注册中心
  • Simple 注册中心:就是一个 dubbo 服务。作为注册中心。提供查找服务的功能。
  • Zookeeper 注册中心:使用 Zookeeper 作为注册中心

官方推荐使用 Zookeeper 注册中心。

工作方式

image-20220730080541785

image-20220730080947404

流程说明:

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

ZK搭建

地址:https://sscarf.com/2022/07/29/Zookeeper/

zookeeper 启动默认占用 2181 以及 8080 端口

可在 zoo.cfg 配置文件修改:

  • clientPort=2181
  • admin.serverPort=8888

创建项目

接口工程不变,再创建两个web工程,同直连案例

image-20220730090202026

引入依赖

zk-consumerzk-provider 中引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependencies>
<!--dubbo 依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!--Spring 依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<!--接口工程-->
<dependency>
<groupId>com.cuc</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Zookeeper注册中心 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.3.0</version>
</dependency>
</dependencies>

服务提供者

  1. 创建接口实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class SomeServiceImpl implements SomeService {

    @Override
    public String hello(String msg) {
    return "Hello " + msg;
    }

    @Override
    public User queryUserById(Integer id) {
    User user = new User();
    user.setId(id);
    user.setUsername("swj");
    return user;
    }
    }
  2. 创建配置文件 zk-provider.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 声明服务提供者名称 -->
    <dubbo:application name="zk-provider-01" />

    <!-- 指定 dubbo 协议名称和端口号 -->
    <dubbo:protocol name="dubbo" port="20880" />

    <!-- 指定注册中心 -->
    <dubbo:registry address="zookeeper://hadoop101:2181" />

    <!-- 加载接口实现类到spring容器-->
    <bean id="someServiceImpl" class="com.cuc.dubbo.service.impl.SomeServiceImpl" />

    <!-- 暴露服务 -->
    <dubbo:service interface="com.cuc.dubbo.service.SomeService" ref="someServiceImpl" />


    </beans>
  3. 配置 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">

    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:zk-provider.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    </web-app>

消费者

  1. 创建控制器类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Controller
    public class SomeController {
    @Autowired
    private SomeService someService;

    @RequestMapping("/hello")
    public String hello(Model model){

    // 调用远程接口服务
    String result = someService.hello("World");
    model.addAttribute("msg",result);
    return "hello";
    }

    @RequestMapping("/user/detail")
    public String userDetail(Integer id,Model model){
    User user = someService.queryUserById(id);
    model.addAttribute("user",user);
    return "userDetail";
    }
    }
  2. 创建配置文件 zk-consumer.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!--声明消费者名称-->
    <dubbo:application name="zk-consumer-01" />

    <!--指定注册中心-->
    <dubbo:registry address="zookeeper://hadoop101:2181" />

    <!--引用远程接口-->
    <dubbo:reference id="someService" interface="com.cuc.dubbo.service.SomeService" url="dubbo://localhost:20880" />

    </beans>
  3. 创建配置文件 springmvc.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--扫描组件-->
    <context:component-scan base-package="com.cuc.dubbo.web" />

    <!--注解驱动-->
    <mvc:annotation-driven />

    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/" />
    <property name="suffix" value=".jsp" />
    </bean>

    </beans>
  4. 配置 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">

    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:zk-consumer.xml,classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

    </web-app>
  5. 创建两个 jsp 页面

    同直连

测试

  1. 配置 Tomcat

  2. 启动 ZK 集群

  3. 启动服务提供者、消费者项目

  4. 效果

    image-20220730094351557

    image-20220730094414583

  5. 查看集群信息

    image-20220730094808621

Dubbo 配置

配置原则

尽量在服务提供者配置参数,因为服务提供者更了解服务的各种参数。

关闭检查

​ dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=true。通过 check="false"关闭检查, 比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

例 1:关闭某个服务启动时的检查

1
<dubbo:reference interface="com.foo.BarService" check="false" />

例 2:关闭注册中心启动时的检查

1
<dubbo:registry check="false" />

默认启动服务时检查注册中心存在并已运行。注册中心不启动会报错。

重试次数

​ 消费者访问提供者,如果访问失败,则切换重试访问其它服务器,但重试会带来更长延迟。 访问时间变长,用户的体验较差。多次重新访问服务器有可能访问成功。可通过 retries="2" 来设置重试次数(不含第一次)。

1
2
3
<dubbo:service retries="2" />
<!-- 或者 -->
<dubbo:reference retries="2" />

超时时间

​ 由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。

1
2
3
<dubbo:reference interface="com.foo.BarService" timeout="2000" />
<!-- 或者 -->
<dubbo:server interface="com.foo.BarService" timeout="2000" />

版本号

​ 每个接口都应定义版本号,为后续不兼容升级提供可能。当一个接口有不同的实现,项目早期使用的一个实现类, 之后创建接口新的实现类。区分不同的接口实现使用 version。 特别是项目需要把早期接口的实现全部换位新的实现类,也需要使用 version。

​ 可以用版本号从早期的接口实现过渡到新的接口实现,版本号不同的服务相互间不引用。

后面有案例

监控中心

概述

​ dubbo 的使用,其实只需要有注册中心,消费者,提供者这三个就可以使用了,但是并不能看到有哪些消费者和提供者,为了更好的调试,发现问题,解决问题,因此引入 dubbo-admin。 通过 dubbo-admin 可以对消费者和提供者进行管理。可以在 dubbo 应用部署做动态的调整,服务的管理。

dubbo-admin

图形化的服务管理页面。安装时需要指定注册中心地址,即可从注册中心中获取到所有的提供者/消费者进行配置管理。

dubbo-monitor-simple

简单的监控中心

dubbo-admin

  1. 下载 dubbo-admin

    image-20220730161007548

  2. 进入目录修改 zk 地址

    修改地址:dubbo-admin\src\main\resources\application.properties

    1
    dubbo.registry.address=zookeeper://hadoop101:2181
  3. 测试

    dubbo-admin 目录下 cmd 执行命令打包

    1
    C:\Users\usesr\Desktop\incubator-dubbo-ops-master\dubbo-admin>mvn clean package

    target 文件中可看到打包后的 jar 包

    image-20220730161902824

    之后在 jar 包所在目录 cmd 执行 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar 运行打包好的 jar 包。

    运行后在浏览器中访问 localhost:7001,访问到注册中心,输入账号密码 root

    image-20220730162747361

dubbo-monitor-simple

  1. 下载

    image-20220730164127086

  2. 修改

    修改地址 dubbo-monitor-simple\src\main\resources\conf

    image-20220730164411771

  3. 打包

    1
    C:\Users\usesr\Desktop\incubator-dubbo-ops-master\dubbo-monitor-simple>mvn package
  4. 解压打包后的 dubbo-monitor-simple-2.0.0-assembly.tar.gz

    image-20220730165047562

  5. 双击 start.bat 启动 dubbo-monitor-simple 监控中心

    image-20220730165006406

  6. 访问 localhost:8080

    image-20220730165738698

  7. 在服务提供者和消费者的xml中配置以下内容

    1
    <dubbo:monitor protocol="registry"></dubbo:monitor>
  8. 重启项目后监控中心捕获到了服务提供和消费者信息

    image-20220730170620409

整合 SpringBoot

三种方式

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

  2. 保留 Dubbo 相关的 xml 配置文件

    导入 dubbo-starter,使用 @ImportResource 导入 Dubbo 的 xml 配置文件

  3. 使用 注解API 的方式

    在配置类中将每一个组件手动配置到容器中,让 dubbo 来扫描其他的组件

下面使用第一种方式

创建项目

接口工程不变,新建服务提供者以及消费者 SpringBoot 工程。

服务提供者

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--Dubbo-->
    <dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
    </dependency>
    <!--接口工程-->
    <dependency>
    <groupId>com.cuc</groupId>
    <artifactId>interface</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    </dependencies>
  2. 配置 application.properties

    省去服务的暴露,改用注解暴露

    1
    2
    3
    4
    5
    6
    7
    server.port=8082
    dubbo.application.name=boot-provider
    dubbo.registry.address=hadoop101:2181
    dubbo.registry.protocol=zookeeper

    dubbo.protocol.name=dubbo
    dubbo.protocol.port=20880
  3. 实现接口

    注意:@Service 在 com.alibaba.dubbo.config.annotation.Service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service // 暴露服务
    @Component
    public class SomeServiceImpl implements SomeService {

    @Override
    public String hello(String msg) {
    return "Hello " + msg;
    }

    @Override
    public User queryUserById(Integer id) {
    User user = new User();
    user.setId(id);
    user.setUsername("swj");
    return user;
    }
    }
  4. 启动类中开启扫描

    1
    2
    3
    4
    5
    6
    7
    8
    @EnableDubbo // 开启基于注解的dubbo功能
    @SpringBootApplication
    public class BootProviderApplication {

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

消费者

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--Dubbo-->
    <dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
    </dependency>
    <!--接口工程-->
    <dependency>
    <groupId>com.cuc</groupId>
    <artifactId>interface</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    </dependencies>
  2. 配置 application.properties

    省去了接口的注入,改用注解

    1
    2
    3
    server.port=8081
    dubbo.application.name=boot-consumer
    dubbo.registry.address=zookeeper://hadoop101:2181
  3. 控制器方法(调用接口)

    注意:@Reference 在 com.alibaba.dubbo.config.annotation.Reference

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @RestController
    public class SomeController {

    @Reference // 引用远程提供者服务
    SomeService someService;

    @RequestMapping("/hello")
    public String hello(){
    String result = someService.hello("World");
    return result;
    }

    @RequestMapping("/user/detail")
    public User userDetail(Integer id, Model model){
    User user = someService.queryUserById(id);
    model.addAttribute("user",user);
    return user;
    }
    }
  4. 启动类中开启扫描

    1
    2
    3
    4
    5
    6
    7
    8
    @EnableDubbo // 开启基于注解的dubbo功能
    @SpringBootApplication
    public class BootConsumerApplication {

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

测试

  • image-20220730203543073
  • image-20220730203605103

版本号案例

基于 SpringBoot 实现的版本号案例

  1. 服务提供者的实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service(version = "1.0")
    @Component
    public class SomeServiceImpl implements SomeService {

    @Override
    public String hello(String msg) {
    return "Hello " + msg;
    }

    @Override
    public User queryUserById(Integer id) {
    User user = new User();
    user.setId(id);
    user.setUsername("swj");
    return user;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service(version = "2.0")
    @Component
    public class SomeServiceImpl2 implements SomeService {

    @Override
    public String hello(String msg) {
    return "Hello " + msg + "!";
    }

    @Override
    public User queryUserById(Integer id) {
    User user = new User();
    user.setId(id);
    user.setUsername("SuWeiJin");
    return user;
    }
    }
  2. 消费者的控制器类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @RestController
    public class SomeController {

    @Reference(version = "1.0")
    SomeService someService;

    @Reference(version = "2.0")
    SomeService someService2;

    @RequestMapping("/hello")
    public String hello(){

    String result1 = someService.hello("World");
    String result2 = someService2.hello("World");
    return result1+"|"+result2;
    }

    @RequestMapping("/user/detail")
    public User userDetail(Integer id){
    User user1 = someService.queryUserById(id);
    User user2 = someService2.queryUserById(id);
    User user = new User();
    user.setId(id);
    user.setUsername(user1.getUsername()+"|"+user2.getUsername());
    return user;
    }
    }
  3. 测试

    image-20220730205205125

    image-20220730205218137

高可用

即通过设计,减少系统不能提供服务的时间

原因:

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

负载均衡

Dubbo 有四种负载均衡机制,默认为 random 随机调用

Random LoadBalance

基于权重的随机负载均衡机制

image-20220730214607325

此处每次访问有 2/7 概率访问提供者1,4/7 概率访问提供者2,1/7 概率访问提供者3。

RoundRobin LoadBalance

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

image-20220730213248097

第一个请求随机访问其中一个提供者,之后的所有请求都按照顺序访问提供者,如果被访问的提供者在当前被访问次数已满,则跳过继续访问下一个提供者。

此处第一个请求随机访问了 1 号提供者,之后所有请求都以轮询方式访问提供者,直到第六个请求要访问 3 号提供者时,该提供者可供访问次数已被第三个请求占用,所以转而去请求提供者 1,同时提供者 1 的两次请求次数也被使用了,则去访问 2 号提供者。

LeastActive LoadBalance

最少活跃数负载均衡机制

image-20220730214619198

相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

一致性hash 负载均衡机制

image-20220730214734481

相同参数的请求总是发到同一提供者。 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

服务降级

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

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

1
2
3
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://hadoop101: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 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

也可直接在 dubbo-admin 可视化页面设置

集群容错

简介

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

  • Failover Cluster

    失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟

  • Failfast Cluster

    快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录

  • Failsafe Cluster

    失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作

  • Failback Cluster

    失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作

  • Forking Cluster

    并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。

  • Broadcast Cluster

    广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息

配置

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

1
2
3
<dubbo:service cluster=“failsafe” />

<dubbo:reference cluster=“failsafe” />

整合 hystrix

Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能

  1. 引入依赖

    spring boot 官方提供了对 hystrix 的集成,直接在 pom.xml 里加入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>1.4.4.RELEASE</version>
    </dependency>
  2. 启动类上添加 @EnableHystrix 启用 hystrix starter

    1
    2
    3
    4
    5
    @EnableHystrix //开启服务容错功能
    @SpringBootApplication
    public class ProviderApplication {
    ...启动方法
    }
  3. Provider 端

    在 Dubbo 的 Provider 上增加 @HystrixCommand 配置,这样子调用就会经过 Hystrix 代理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Service(version = "1.0.0")
    public class HelloServiceImpl implements HelloService {

    @HystrixCommand(commandProperties = {
    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
    @Override
    public String sayHello(String name) {
    throw new RuntimeException("Exception to show hystrix enabled.");
    }
    }
  4. Consumer 端

    对于 Consumer 端,则可以增加一层 method 调用,并在 method 上配置 @HystrixCommand。当调用出错时,会走到fallbackMethod = “reliable” 的调用里。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Reference(version = "1.0.0")
    private HelloService demoService;

    @HystrixCommand(fallbackMethod = "reliable")
    public String doSayHello(String name) {
    return demoService.sayHello(name);
    }
    public String reliable(String name) {
    return "hystrix fallback value";
    }