前言

        Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。

一、Dubbo 简介

        Apache Dubbo是一款高性能的Java RPC框架。其前身是阿里巴巴公司开源的、轻量级的开源Java RPC 框架,可以和Spring框架无缝集成。

        Dubbo提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。        

        服务是 Dubbo 中的核心概念,一个服务代表一组 RPC 方法的集合,服务是面向用户编程、服务发现机制等的基本单位。Dubbo 开发的基本流程是:用户定义 RPC 服务,通过约定的配置 方式将 RPC 声明为 Dubbo 服务,然后就可以基于服务 API 进行编程了。对服务提供者来说是提供 RPC 服务的具体实现,而对服务消费者来说则是使用特定数据发起服务调用。

RPC

        RPC全称为remote procedure call,即远程过程调用。比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上的应用提供的方法,由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。 需要注意的是RPC并不是一个具体的技术,而是指整个网络远程调用过程。

二、Dubbo架构

Dubbo 基于消费端的自动服务发现能力,其基本工作原理如下图:

微服务和bs架构 微服务架构dubbo_微服务

虚线是异步访问,实线是同步访问;蓝色色虚线:在启动时完成的功能;绿色线条是程序运行过程中的执行功能与流程;

Provider:        暴露服务的提供方

Consumer:      调用远程服务的服务消费方

Registry:          服务注册与发现的注册中心

Monitor:           统计服务的调用次数和调用时间的监控中心

Container:        服务运行的容器

调用关系说明:

        0. 服务容器负责启动,加载,运行服务提供者。

        1. 服务提供者在启动时,向注册中心注册自己提供的服务。

        2. 服务消费者在启动时,向注册中心订阅自己所需的服务。

        3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给 消费者。

        4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失 败,再选另一台调用。

        5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中 心。

三、服务注册中心 Zookeeper

Dubbo官方推荐使用Zookeeper作为服务注册中心(Regitry)

Zookeeper的介绍与安装

介绍

Zookeeper 是 Apache Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用 。

Zookeeper的树形目录结构:

微服务和bs架构 微服务架构dubbo_微服务和bs架构_02

流程说明:

        服务提供者(Provider)启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址

        服务消费者(Consumer)启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址

        监控中心(Monitor)启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址

安装

cd conf
mv zoo_sample.cfg zoo.cfg

这里是使用的虚拟机CentOS7作为示例安装与使用。

第一步:在虚拟机上安装jdk;

第二步:将把 zookeeper 的压缩包(我们这里使用的是 zookeeper-3.4.6.tar.gz)上传到linux 系统;

第三步:解压缩包。-C参数后面跟进自己解压的路径;

tar -zxvf zookeeper-3.4.6.tar.gz -C /tool/zookeeper

第四步:进入解压好的目录,需要创建一个data目录,作为配置文件的放置处;

mkdir data

第五步:进入解压目录下的conf目录,将把zoo_sample.cfg 复制一份并改名为zoo.cfg

cd conf
mv zoo_sample.cfg zoo.cfg

第六步:编辑复制的zoo.cfg文件,需要修改data属性,改为第四步创建的data目录下

dataDir=/tool/zookeeper/data

这样zookeeper的准备就做完了。

启动与停止zookeeper

首先我们需要进入zookeeper中,解压好的zookeeper-3.4.6目录下的bin目录下执行linux的shell命令。

微服务和bs架构 微服务架构dubbo_微服务和bs架构_03

微服务和bs架构 微服务架构dubbo_微服务_04

相关命令:

启动服务命令

./zkServer.sh start

停止服务命令

./zkServer.sh stop

查看服务状态:

./zkServer.sh status

客户端连接

./zkCli.sh

Dubbo入门案例

        Dubbo作为一个RPC框架,其最核心的功能就是要实现跨网络的远程调用。我们的案例就简单的创建两个示范应用,一个作为服务的提供方(provider),一个作为服务的消费方(consumer)。通过Dubbo来实现服务消费方远程调用服务提供方的方法。

服务提供方开发

maven的pom.xml引入的相关依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cqh</groupId>
    <artifactId>dubbo-provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.0.5.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!--dubbo相关构件-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.7</version>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.24.0-GA</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <port>8081</port>
                    <!--请求路径-->
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

在webapp目录下添一个web.xml文件

微服务和bs架构 微服务架构dubbo_微服务和bs架构_05

 web.xml配置文件:

<?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">

<!--    <disolay-name>Archetype Created Web Application</disolay-name>-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

之后我们在main.java目录下创建接口与实现类(作为提供服务)

接口 HelloService:

public interface HelloService {

    public String sayHello(String name);
}

创建一个实现类实现该接口 HelloServiceImpl(注意这里的@Service注解是使用的alibaba dubbbo包下的Service):

import com.alibaba.dubbo.config.annotation.Service;
import com.kkb.service.HelloService;

@Service
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
        return "hello : " + name;
    }
}

我们还需要在src/main/resources下创建applicationContext-service.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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://code.alibabatech.com/schema/dubbo
            http://code.alibabatech.com/schema/dubbo/dubbo.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样 -->
    <dubbo:application name="dubbodemo_provider" />
    <!-- 连接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址-->
    <dubbo:registry address="zookeeper://192.168.157.128:2181"/>
    <!-- 注册 协议和port,端口号默尔为20880-->
    <dubbo:protocol name="dubbo" port="20881"></dubbo:protocol>
    <!-- 扫描指定包,加入@Service注解的类会被发布为服务 -->
    <dubbo:annotation package="com.cqh.service.impl" />
</beans>

上面的配置文件web.xml用于指定我们上面开启的zookeeper服务IP地址、指定使用zookeeper的端口与协议、用于提供服务的包名等。这样我们tomcat开启后,就会在zookeeper上注册我们提供的服务,用于其他服务来远程调用等。

服务消费方开发

另开一个moudle,pom.xml与web.xml配置文件是答题相同的,只需要该部分内容:

pom.xml 需要将引入的tomcat插件的端口好改为 8002 

web.xml 中需要引入servlet的相关配置,需要接收url请求

添加内容:

<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:applicationContext-web.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

我们需要沿用服务提供方中我们需要调用服务的接口 HelloService

然后我们创建一个controller用于接受请求 HelloController:

import com.alibaba.dubbo.config.annotation.Reference;
import com.cqh.service.HelloService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/dubbo")
public class HelloController {

    @Reference
    private HelloService helloService;

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello(String name) {
        //  远程调用
        String result = helloService.sayHello(name);
        System.out.println(result);
        return result;
    }

最后,在src/main/resources下创建applicationContext-web.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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       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/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://code.alibabatech.com/schema/dubbo
            http://code.alibabatech.com/schema/dubbo/dubbo.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样 -->
    <dubbo:application name="dubbodemo_consumer" />
    <!-- 连接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址-->
    <dubbo:registry address="zookeeper://192.168.157.128:2181"/>
    <!-- 注册 协议和port,端口号默尔维尼为20880-->
    <dubbo:protocol name="dubbo" port="20881"></dubbo:protocol>

    <!-- 扫描指定包,加入@Service注解的类会被发布为服务 -->
    <dubbo:annotation package="com.kkb.controller"/>
</beans>

这个配置文件中指定我们的zookeeper服务注册中心的IP地址、使用的协议端口等、指定需要远调用服务的包名。

提供者暴露服务的整体机制:

  •         在服务提供者初始化的时候,会通过 Config 组件中的 ServiceConfig 读取服务的配置信息。这个配置信息有三种形式,分别是 XML 文件,注解(Annoation)和属性文件(Properties 和 yml)。
  •         在读取配置文件生成服务实体以后,会通过 ProxyFactory 将 Proxy 转换成 Invoker。
  •         此时,Invoker 会被定义 Protocol,之后会被包装成 Exporter。
  •         最后,Exporter 会发送到注册中心,作为服务的注册信息。

服务注册中心运作机制:

  • 提供者(Provider)启动时,会向注册中心写入自己的元数据信息(调用方式)。
  • 消费者(Consumer)启动时,也会在注册中心写入自己的元数据信息,并且订阅服务提供者,路由和配置元数据的信息。
  • 服务管理中心(duubo-admin)启动时,会同时订阅所有消费者,提供者,路由和配置元数据的信息。
  • 当提供者离开或者新提供者加入时,注册中心发现变化会通知消费者和服务治理中心。
     

服务消费者大概机制:

微服务和bs架构 微服务架构dubbo_微服务和bs架构_06

服务消费者首先持有远程服务实例生成的 Invoker,然后把 Invoker 转换成用户接口的动态代理引用,服务引用的入口点在 ReferenceBean。

大致说明一下远程调用过程:

        我们在浏览器中调r的url:http://localhost:8082/dubbo/hello?name=wang,访问我们的HelloController,controller中我们沿用的服务提供方provider的HelloService接口,也就是说我们只是在服务消费方consumer调用自己的HelloService接口方法(这个接口并没有被实现),但是我们在这个引用的helloService对象上有一个Dubbo的注解@Reference,这样Dubbo就是创建该接口动态代理对象,在我们指定的zookeeper中寻找到服务提供方提供注册的服务(相当于订阅过程),然后远程服务实例生成的 Invoker,然后把 Invoker 转换成用户接口的动态代理引用;而我们的服务提供方(provider)提供的服务:HelloServiceImpl这个实现类,我们添加了@Servcice注解,这样就把我们的服务注册到了我们指定的zookeeper上,于是实现了远程服务调用。

示例图:

将provider与consumer的tomcat运行,这样会将自己注册至制zookeeper处:

微服务和bs架构 微服务架构dubbo_spring_07

可以看到我们注册的consumers于providers的相关信息:

微服务和bs架构 微服务架构dubbo_maven_08

 这样我们url调用查看:

微服务和bs架构 微服务架构dubbo_微服务_09

 协议

我们在applicationContext-**.xml的配置文件中有协议与端口的配置

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

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

其中Dubbo支持的协议有:dubbo、rmi、hessian、http、webservice、rest、redis等。 推荐使用的是dubbo协议。 dubbo 协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。不适合传送大数据量的服务,比如传文件,传视频等,除非请求 量很低。 也可以在同一个工程中配置多个协议,不同服务可以使用不同的协议。

例:

<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.lxs.api.HelloService" ref="helloService"
protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.lxs.api.DemoService" ref="demoService"
protocol="rmi" />

Dubbo无法发布被事务代理的Service问题

如果我们在服务提供者provider提供的服务上添加@Transactionnal事务控制注解后,服务就发布不成功了。原因是事务控制的底层原理是为服务提供者类创建代理对象,而默认情况下Spring是基于JDK动态代理方式创建 代理对象,而此代理对象的完整类名为com.sun.proxy.$Proxy42(最后两位数字不是固定的),导致 Dubbo在发布服务前进行包匹配时无法完成匹配,进而没有进行服务的发布。

微服务和bs架构 微服务架构dubbo_xml_10

 解决方案:

1.        修改applicationContext-service.xml配置文件,开启事务控制注解支持时指定proxy-target-class 属性,值为true。其作用是使用cglib代理方式为Service类创建代理对象。

<!--开启事务控制的注解支持-->
<tx:annotation-driven transaction-manager="transactionManager" proxy-targetclass="true"/>

2.        修改HelloServiceImpl类,在Service注解中加入interfaceClass属性,值为HelloService.class, 作用是指定服务的接口类型。

@Service(interfaceClass = HelloService.class)
@Transactional
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "hello " + name;
}
}

此处也是必须要修改的,否则会导致发布的服务接口为SpringProxy,而不是HelloService接口。

微服务和bs架构 微服务架构dubbo_微服务_11