SpringBoot框架和SSM框架Dubbo入门教程

在学习之前我们需要,安装Zookeeper,和Dubbo管理控制台,这方面我博客里有教程自行查找

介绍

http://dubbo.apache.org/zh/docs/v2.7/user/examples/ (官方文档)

当服务越来越多时,容量的评估,小服务资源的浪费等问题逐渐显现,此时需要增加一个调度中心基于访问压力实时管理集群容量,提供集群利用率。其中,用于提高机器利用率的资源调度和治理中心是关键。

Dubbo 是阿里巴巴开源项目的一个分布式服务框架。其致力于提供高性能和透明化的 RPC 远程调用方案,以及 SOA 服务治理方案。

原理图

Java dubbo使用 dubbo springboot教程_Java dubbo使用

调用关系说明:

  1. 服务容器启动、加载和运行服务提供者;
  2. 服务提供者在启动时,向注册中心注册自己提供的服务;
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务;
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更给消费者;
  5. 服务消费者从地址列表中,基于软负载均衡算法选一台服务提供者进行调用,如果调用失败再选另一台;
  6. 服务消费者和服务提供者在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

节点角色说明

节点

角色说明

Container

服务运行容器

Provider

暴露服务的服务提供者

Consumer

调用远程服务的服务消费者

Registry

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

Monitor

统计服务的调用此处和调用时间的监控中心

Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 入侵,只需用 Spring 加载 Dubbo 配置即可。

官方推荐使用 Zookeeper 作为注册中心,因此本次测试使用 Zookeeper,具体相关教程,在本人博客里都能找到

搭建服务提供者

创建一个普通的Maven项目 项目名称 dubbo-provider-springboot

Java dubbo使用 dubbo springboot教程_zookeeper_02

需要的Maven

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    <dependencies>
<!--        开发 web 项目必须添加的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- dubbo starter 这里已经集成了zookeeper的客户端jar -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
        <!--fastjson   json解析-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.51</version>
        </dependency>
    </dependencies>
    <!--    自动查找主类  用于打包 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

application.yml

server:
  port: 8062  #Tomcat端口号
dubbo:
  application:
    name:  dubbo-provider-springboot   #当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样
  registry:
    address: 49.232.169.170:2181   # 换成自己的zookeeperip
    protocol: zookeeper  #使用的什么注册中心
    check: false  #检测 zookeeper 是否可用   在开发阶段建议 关掉 false
    timeout: 3000
  protocol:      #  协议和port   端口默认是20880  端口号别和其他服务重复
    name: dubbo
    port: 30003
  monitor:
    protocol: register

服务搭建

User1Service

package com.example.service;

/**
 * 该地方只定义接口, 一般为公用接口
 * 可以定义许多接口; 这里为了演示, 只定义一个
 */
public interface User1Service {

    String getUserById();
}

User1ServiceImpl

package com.example.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.example.service.User1Service;
@Service //这里是dubbo的注解, 将接口暴露在外, 可以供调用, 这里就是放在了注册中心
public class User1ServiceImpl implements User1Service {



    @Override
    public String getUserById() {
        return "hello-springboot" ;
    }
}

启动类

package com.example;

import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 开启基于注解的dubbo功能
@EnableDubbo
@SpringBootApplication
public class DubboApplication {

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

启动启动类 查询Dubbo Admin

Java dubbo使用 dubbo springboot教程_Java dubbo使用_03

以上就是Dubbo的服务搭建了

需要注意的是 如果有实体类的话必须继承 Serializable 进行序列化 因为是网络传输

如何使用这些服务呢? 我们下面将创建消费者 调用这些服务

搭建消费者

创建一个普通的Maven项目 名称 dubbodemo-consumer-springboot

Java dubbo使用 dubbo springboot教程_dubbo_04

添加Maven (和服务提供者一样)

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    <dependencies>
        <!--        开发 web 项目必须添加的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- dubbo starter 这里已经集成了zookeeper的客户端jar -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
        <!--fastjson   json解析-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.51</version>
        </dependency>

<!--        使用 thymeleaf   模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

    </dependencies>
    <!--    自动查找主类  用于打包 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

application.yml

server:
  port: 8068  
dubbo:
  application:
    name:  dubbo-consumer-springboot   #当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样
  registry:
    address: 49.232.169.170:2181   # 换成自己的zookeeperip
    protocol: zookeeper  #使用的什么注册中心
    check: false   #检测 zookeeper 是否可用   在开发阶段建议 关掉 false
    timeout: 3000  
  monitor:
    protocol: register
  consumer:   #检测服务 是否可用  在开发阶段建议 关掉 false
    check:  false
    timeout: 3000

添加服务提供者的接口

HelloService

package com.itheima.service;

public interface HelloService {
    public String sayHello();
}

User1Service

package com.example.service;

/**
 * 该地方只定义接口, 一般为公用接口
 * 可以定义许多接口; 这里为了演示, 只定义一个
 */
public interface User1Service {

    String getUserById();
}

添加controller

package cn.consumer.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.example.service.User1Service;
import com.itheima.service.HelloService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/demo")
public class HelloController {
    @Reference
    private HelloService helloService;
    @Reference
    private User1Service user1Service;

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

        @GetMapping("/user")
        @ResponseBody
        public String getName1(){
            //远程调用
            String result = user1Service.getUserById();
            System.out.println(result);
            return result;
        }

}

启动类

DubboApplication

package cn.consumer;

import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 开启基于注解的dubbo功能
@EnableDubbo
@SpringBootApplication
public class DubboApplication  {

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

启动启动类

查看Dubbo Admin 现在为止应该有2个服务 和 2个消费者 而且每一个消费者都对应着2个服务

Java dubbo使用 dubbo springboot教程_Java dubbo使用_05

我们进入消费者服务里 可以看到 对应的 2个服务提供的接口

Java dubbo使用 dubbo springboot教程_dubbo_06

在浏览器输入http://localhost:8068/demo/hello,查看浏览器输出结果

在浏览器输入http://localhost:8068/demo/user,查看浏览器输出结果

Dubbo相关配置说明

yml 和 xml 意思都一样 我们以xml 来解释

包扫描(配合注解)

<dubbo:annotation package="com.itheima.service" />

在Springboot 的启动类上 添加 @EnableDubbo 就代替上面的

服务提供者和服务消费者都需要配置,表示包扫描,作用是扫描指定包(包括子包)下的类。

配合Dubbo的 @Service 生成服务

协议

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

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

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

推荐使用的是dubbo协议。

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

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

但是需要添加各个协议的Maven支持 自己到网上查询

例如:

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

在Springboot里也能配置多个协议 但是需要添加各个协议的Maven 自己上百度

配置也有所变化 原来的 protocol 换成 protocols

dubbo:
  # 定义两个协议 dubbo,rest(使用jetty作为服务器)
  protocols:
    dubbo:
      name: dubbo
      port: 20880
    rest:
      name: rest
      port: 9999
      server: jetty

但是调用方式都一样 在使用的类上

@Service(protocol = "协议")

在Springboot里 有两种 Dubbo 但是效果一样

<dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

这一种使用的是 @Service

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

这一种使用的是 @DubboService

启动时检查

<dubbo:consumer check="false"/>

面这个配置需要配置在消费者一方,如果不配置默认check值为true。Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题。可以通过将check值改为false来关闭检查。

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

负载均衡

特别特别简单 有两种方式 第一种在代码里配置 第二种是在 Dubbo Amin 里配置 而我们选择第二种方式

  1. 进入DubboAdmin 里

Java dubbo使用 dubbo springboot教程_spring boot_07

Java dubbo使用 dubbo springboot教程_zookeeper_08

Java dubbo使用 dubbo springboot教程_java_09

  1. 在创建一个相同的服务配置代码全部都相同 除了 [ Tomcat端口号,Dubbo服务的端口号]
    spirngweb 和 Springboot 一样 我们以Springboot 举例 因为好创建
  1. 创建普通Maven项目 名称为 dubbo-provide-springboot1
  2. 将dubbo-provide-springboot里的代码和配置文件全部复制到dubbo-provide-springboot1里
  3. 修改dubbo-provide-springboot1 里的application.yml (Tomcat 端口号 和Dubbo端口 )
server:
  port: 8072
  
dubbo:
  protocol:     #  协议和port   端口默认是20880
    name: dubbo
    port: 30323
  1. 修改User1ServiceImpl里的代码 (主要是测试负载均衡 区分)
@Override
    public String getUserById() {
        return "hello-springboot1" ;
    }
  1. 启动Stringboot 的启动类
  2. 查看DubboAmdin
  3. Java dubbo使用 dubbo springboot教程_zookeeper_10

我们来访问 com.example.service.User1Service 这个服务

在浏览器输入http://localhost:8068/demo/user, 查看浏览器输出结果 (多次访问)

可以发现 游览器 结果 在 hello-springboot1 和 hello-springboot 来回切换 这样就代表 负载均衡成功

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

前面我们已经完成了Dubbo的入门案例,通过入门案例我们可以看到通过Dubbo提供的标签配置就可以进行包扫描,扫描到@Service注解的类就可以被发布为服务。

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

解决方式操作步骤:

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

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

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

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

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

如果是Springboot 的话 就没必要 因为 Springboot2x 默认使用的就是 cglib代理方式

但是在使用类上添加指定接口还是需要的

@Service(interfaceClass = HelloService.class)

解决上面案例中问题

思考一: 上面的Dubbo入门案例中我们是将HelloService接口从服务提供者工程(dubbodemo_provider)复制到服务消费者工程(dubbodemo_consumer)中,这种做法是否合适?还有没有更好的方式?

答: 这种做法显然是不好的,同一个接口被复制了两份,不利于后期维护。更好的方式是单独创建一个maven工程 然后打包进入Maven仓库里,需要依赖此接口的工程只需要在自己工程的pom.xml文件中引入maven坐标即可。

思考二: 在服务消费者工程(dubbodemo_consumer)中只是引用了HelloService接口,并没有提供实现类,Dubbo是如何做到远程调用的?

答: Dubbo底层是基于代理技术为HelloService接口创建代理对象,远程调用是通过此代理对象完成的。另外,Dubbo实现网络传输底层是基于Netty框架完成的。

思考三: 上面的Dubbo入门案例中我们使用Zookeeper作为服务注册中心,服务提供者需要将自己的服务信息注册到Zookeeper,服务消费者需要从Zookeeper订阅自己所需要的服务,此时Zookeeper服务就变得非常重要了,那如何防止Zookeeper单点故障呢?

答: Zookeeper其实是支持集群模式的,可以配置Zookeeper集群来达到Zookeeper服务的高可用,防止出现单点故障。