SpringBoot+Dubbo+Zookeeper 实现负载均衡、服务降级

SpringBoot + Dubbo + Zookeeper 实现负载均衡、服务降级

参考网址

http://dubbo.apache.org/zh-cn/ Dubbo 官网

http://dubbo.apache.org/zh-cn/docs/user/quick-start.html Dubbo 文档

框架组成

springboot 2.3.4+ zookeeper 3.6.2 + dubbo 2.6.5;

工具:IDEA 2019.1.2

Dubbo 体系结构

img

执行流程:

  1. 启动注册中心:例如 zookeeper,接受 provider 的注册,接受 consumer 的订阅,当 provider 有变化的时候,会主动通知 consumer;

  2. 启动Provider:provider是服务的提供者,例如提供 com.xian.service.impl.UserServiceImpl 这个服务,它是服务的具体实现,provider 启动的时候会向 zookeeper 这个注册中心注册,表明自己可以提供哪些服务;

  3. 启动Consumer:consumer 是服务的消费者,例如通过 com.xian.controller.UserController 去调用 UserServiceImpl,consumer 启动的时候会去 zookeeper 这个注册中心 订阅自己需要的服务;

  4. 启动Registry:注册中心,例如 zookeeper,接受 provider 的注册,接受 consumer 的订阅,当 provider 有变化的时候,会主动通知 consumer;

  5. consumer 调用 provider 的具体服务,例如 UserController 调用 UserServiceImpl,但是具体由那一台 provider 服务器提供服务,取决于 负载均衡调度算法;

  6. Monitor:监控中心,统计服务被调用的次数和时间,服务的消费者和提供者会在内存中累计调用次数和
    调用时间,定时每分钟向监控中心发送一次统计数据;

创建 Project

菜单栏:File| New Project, 创建一个空的 Project

image-20201019232727306

填 Project 的名称和存放路径,然后点击右下角的【Finish】:

image-20201019232831892

要在这个 Project 下创建 3 个子 module,最终目录 module 结构如下:

image-20201019233928017

module:dubbo-spring-common

创建工程

image-20201019205800074

image-20201019205812309

填项目的坐标:GroupId, ArtifactId

image-20201019205953315

image-20201019210017912

工程结构

image-20201021074738515

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.xian</groupId>
    <artifactId>dubbo-spring-common</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

</project>

User.java

package com.xian.entity;

import java.io.Serializable;

/**
 * Author: xian
 * Date: 2020-10-19 21:02
 * Description: 描述
 */
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;

    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;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

UserService.java

package com.xian.service;

import com.xian.entity.User;

/**
 * Author: xian
 * Date: 2020-10-19 21:04
 * Description: 描述
 */
public interface UserService {
    public User findById(int id);
}

module:dubbo-springboot-provider

创建工程

image-20201025090713994

image-20201025091032877

image-20201025091225676

image-20201025091245886

image-20201025091258818

删除不需要的文件、文件夹

image-20201025091340664

将【java 】文件夹标记为 【Source Root】

image-20201025091912823

工程结构

image-20201025094301608

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xian</groupId>
    <artifactId>dubbo-springboot-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>dubbo-springboot-provider</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 依赖 dubbo-common -->
        <dependency>
            <groupId>com.xian</groupId>
            <artifactId>dubbo-spring-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

将 application.properties 重命名为 application.yml

image-20201025093133080

server:
  port: 8881

dubbo:
  application:
    name: dubbo-springboot-provider
  registry:
    address: zookeeper://127.0.0.1:2181 # 配置 zookeeper 的地址
  protocol:
    name: dubbo
    port: 9991
  monitor:
    protocol: registry	# 配置监控中心的地址从注册中心 registry 里面找

UserServiceImpl.java

这里导入的 Service 包是阿里 dubbo 下面的包;

package com.xian.service.impl;

import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Component;
import com.alibaba.dubbo.config.annotation.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Author: xian
 * Date: 2020-10-25 9:35
 * Description: 描述
 */
@Component
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User findById(int id) {

        System.out.println("UserServiceImpl.findById====================== ");

        User user = new User();
        user.setId(id);
        user.setUsername("xian");
        user.setPassword("123");

        return user;
    }
}

DubboSpringbootProviderApplication.java

添加 @EnableDubbo 开启dubbo

package com.xian;

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

@SpringBootApplication
@EnableDubbo
public class DubboSpringbootProviderApplication {

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

}

module:dubbo-springboot-consumer

创建工程

image-20201025094447149

image-20201025094545726

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xian</groupId>
    <artifactId>dubbo-springboot-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>dubbo-springboot-consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 依赖 dubbo-common -->
        <dependency>
            <groupId>com.xian</groupId>
            <artifactId>dubbo-spring-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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

        <!-- 依赖 thymleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

server:
  port: 8882

dubbo:
  application:
    name: dubbo-springboot-consumer
  registry:
    address: zookeeper://127.0.0.1:2181
  monitor:
    protocol: registry

spring:
  thymeleaf:
    cache: false

UserController.java

package com.xian.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Author: xian
 * Date: 2020-10-25 9:53
 * Description: 描述
 */
@Controller
@RequestMapping("/user")
public class UserController {

    @Reference
    private UserService userService;

    @RequestMapping("/findUser")
    public String findUser(int id, Model model) {
        User user = userService.findById(id);
        model.addAttribute("user", user);
        return "success";
    }

}

DubboSpringbootConsumerApplication.java

添加 @EnableDubbo 开启dubbo

package com.xian;

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

@SpringBootApplication
@EnableDubbo
public class DubboSpringbootConsumerApplication {

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

}

启动 zookeeper

下载

https://zookeeper.apache.org/releases.html

image-20201017105102475

解压

image-20201017110906252

配置

建文件夹:data

image-20201017110934139

进入文件夹 conf,

image-20201025111958525

复制 zoo_sample.cfg 并另存为 zoo.cfg

编辑 zoo.cfg,修改变量 dataDir 的值为:

dataDir=../data

启动 Server

进入 bin 目录,双击 zkServer.cmd

启动效果:

image-20201025111320213

客户端连接

进入 bin 目录,双击 zkCli.cmd,回车,输入 ls /

双击 zkCli.cmd

image-20201025111430952

启动 dubbo-springboot-provider

启动这个工程下的应用程序 DubboSpringbootProviderApplication.java ,可以看到启动端口是 8881:

image-20201025103946535

启动 dubbo-springboot-consumer

启动这个工程下的应用程序 DubboSpringbootProviderApplication.java ,可以看到启动端口是 8882

image-20201025104007273

访问

image-20201025103654363

可以到能访问到 provider 提供的服务了;

查看 zookeeper 客户端

在 zookeeper 客户端下,可以看到根节点里面多了 dubbo 节点,dubbo 里面有服务的提供者 provider、服务的消费者 consumer、配置中心等节点;

image-20201025111743432

查看 Dubbo 管理中心

Dubbo 管理中心名字是 dubbo-admin,要去 github 下载,通过 maven 打包,得到的 jar 包用来运行,然后就可以看到管理中心了,可以用来控制 provider 的升权、降权,还可以控制 consumer 的屏蔽和容错;

下载

image-20201025123610306

解压:

image-20201025123810593

image-20201025123823027

打包

进入 dubbo-admin 目录,在地址了输入 cmd 并回车,打开 cmd 窗口,输入命令 mvn package 开始打包,过程大概几分钟;

image-20201025200449783

image-20201025200510819

image-20201025200605972

打包成功后,就能看到 dubbo-admin/target 文件夹下生成了 jar 文件;

image-20201025200637187

image-20201025123637026

启动

在 cmd 命令窗口里,进入 target 目录,输入命令启动 jar 文件:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

image-20201025123727649

用户名、密码默认都是 root;

image-20201025123429658

访问

在浏览器中访问:localhost:7001,就能看到配置首页了;

image-20201025124807184

可以看到服务的提供者有 1 个,消费者有 3 个;

image-20201025124853142

提供者

进入【提供者】

image-20201025145611863

可以看到有【提供者、消费者、。。。】

image-20201025145633043

消费者

image-20201025145730889

当消费者选择【屏蔽】以后,用户就会无法访问;

image-20201025145833055

未屏蔽、可访问的状态:

image-20201025150113969

已屏蔽、不能访问的状态:

image-20201025150023171

监控中心

名字:dubbo-monitor-simple

dubbo:
  monitor:
    protocol: registry # 配置监控中心,从注册中心查找监控中心的地址

进入文件夹:

image-20201025153843004

配置

image-20201025153913765

dubbo.registry.address: 配置注册中心 zookeeper 的地址,

dubbo.jetty.port: 配置浏览器访问用的端口号,因为 8080 端口已经占用了,所以这里改成 8081

image-20201025153935389

打包:mvn package

image-20201025153811220

image-20201025153758294

image-20201025153718643

运行 start.bat

进入 assembly.bin,双击 start.bat,

image-20201025153645247

运行效果

image-20201025153742572

访问

在浏览器中输入 localhost:8081,就能访问到监控中心了:

image-20201025150828469

这个时候去看 Dubbo 的管理中心, 就能看到多了一个 7070端口的提供者,这个 7070 就是监控中心 dubbo-monitor-simple;

image-20201025154442267

Services

在这里可以看到服务被调用的次数和时间,这里的次数和时间是 provider 和 consumer 每分钟像监控中心发送的;

image-20201025151018452

image-20201025151600054

看图表

image-20201025151638687

image-20201025151655591

负载均衡

创建多个 Provider,每个 Provider 的设置不同的提供服务的端口,或者一个 Provider 启动多次,每次启动之前改动端口号;

Consumer 只需要一台就可以了;

Provider

配置 provider 集群,要把每个 provider 的 tomcat 端口、dubbo 提供服务的端口都改成不一样的;

在 资源管理器 中给 dubbo-springboot-provider 建立 2 个副本,结尾分别写 2 和 3;

image-20201025155711303

application.yml

server:
  port: 8882		#  tomcat 的端口号,第二台 provider 改成 8882,第三台 8883

dubbo:
  application:
    name: dubbo-springboot-provider2 # 应用的名称,第二台 provider 结尾加个 2,第三台加 3
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: 9992		# dubbo 提供服务的端口号;,第二台 provider 改成 9992,第三台 9993
  monitor:
    protocol: registry  # 配置监控中心,从注册中心查找监控中心的地址

UserServiceImpl.java

然后在服务代码中做记号:

package com.xian.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Author: xian
 * Date: 2020-10-25 9:35
 * Description: 描述
 */
@Component
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User findById(int id) {

        System.out.println("UserServiceImpl.findById====================== 1"); // 这里做个标记,打印的时候就知道调用的是哪台 provider;第一台结尾写 1,第二台 2,第三台 3

        User user = new User();
        user.setId(id);
        user.setUsername("xian");
        user.setPassword("123");

        return user;
    }
}

pom.xml

把 artifactId 和 name 修改成和项目名一致;

image-20201025155603620

导入 module

把 dubbo-springboot-provider2 和 dubbo-springboot-provider3 导入到工程里:

image-20201025160242643

image-20201025160255567 image-20201025160330957

后面的全是默认,最后【Finish】就可以了,两个 provider 导入后的效果:

image-20201025160454595

dubbo-springboot-consumer

消费者这边只需要有一个就可以了;

UserController.java

在 UserService 接口中设置负载均衡的策略:loadbalance = "roundrobin"

@Controller
@RequestMapping("/user")
public class UserController {
// 修改负载均衡策略
@Reference(loadbalance = "roundrobin")
private UserService userService;

application.yml

server:
  port: 7771  # tomcat 端口号改成 7771,因为原来的 8882 被 dubbo-springboot-provider2占用了

dubbo:
  application:
    name: dubbo-springboot-consumer
  registry:
    address: zookeeper://127.0.0.1:2181
  monitor:
    protocol: registry

spring:
  thymeleaf:
    cache: false

启动

分别启动 dubbo-springboot-provider、dubbo-springboot-provider2、dubbo-springboot-provider3,然后启动 dubbo-springboot-consumer;

Dubbo Admin

可以看到 3 个 dubbo 服务了:

image-20201025160711776

访问

多刷新几次:

image-20201025161848831

看打印记录可以知道是调用了哪个 provider 提供的服务,设置不同的负载均衡策略,则每个 provider 被调用的次数会有变化;

dubbo-springboot-provider 的控制台打印:

image-20201025161834072

dubbo-springboot-provider2 的控制台打印:

image-20201025161916829

dubbo-springboot-provider3 的控制台打印:

image-20201025161931625

服务降级

服务器压力过大的时候,需要把非核心业务暂停处理,就需要用到服务降级,服务降级分为两种:

  1. consumer 不调用 provider 提供的服务,直接给客户端返回 null;
  2. consumer 调用 provider 失败后, 再给客户端返回 null;

核心的服务例如:支付、下订单;

非核心的服务例如:广告、评论、确认收货;

服务降级是在【消费者】consumer 里面设置的,配合 Dubbo Admin 管理中心来设置;

直接返回 null

这种方式是通过点击 consumer 的【屏蔽】按钮来操作的:

image-20201025162849974

UserController.java

package com.xian.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Author: xian
 * Date: 2020-10-25 9:53
 * Description: 描述
 */
@Controller
@RequestMapping("/user")
public class UserController {

    @Reference(loadbalance = "random")
    private UserService userService;

    @RequestMapping("/findUser")
    public String findUser(int id, Model model) {

        User user = userService.findById(id);

        System.out.println("服务降级=========" + user);	// 做个记号:这里将会打印 null

        if (user != null) {
            model.addAttribute("user", user);
            return "success";
        } else {
            return "degraded";	// 服务降级的时候,就返回 degraded 页面
        }
    }

}

degraded.html

在 resources/templates 下添加 degraded.html 页面,当服务降级的时候访问;

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>Title</title>
</head>
<body>
    服务降级,就调用这个界面
</body>
</html>

访问

未降级,可访问的状态:

image-20201025163043862

已降级、不可访问的状态:

image-20201025163922770

后台打印

可以看到,consumer 直接 返回了 null 给客户端:

image-20201025163752055

consumer 调用服务失败的时候返回 null

Dubbo Admin 中设置 Consumer 为【已容错】,

设置 provider 服务中 水淼 3 秒再返回 user 对象,

因为 consumer 默认的超时时间是 1秒,所以当 1 秒之内还没有调用到 provider 服务,那么 consumer 就会默认重试 2 次,一共调用 3 次,3 次都失败以后,才返回一个 null 给客户端;

Dubbo Admin 设置服务容错

点击【容错】按钮,

image-20201025164439343

已容错:

image-20201025164630951

UserServiceImpl.java

package com.xian.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Author: xian
 * Date: 2020-10-25 9:35
 * Description: 描述
 */
@Component
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User findById(int id) {

        System.out.println("UserServiceImpl.findById====================== 1");

        // 设置睡眠 3 秒,实现服务调用失败的效果
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        User user = new User();
        user.setId(id);
        user.setUsername("xian");
        user.setPassword("123");

        return user;
    }
}

访问

已容错、provider 未睡眠 3 秒时,可以正常访问

image-20201025170112999

已容错、provider 睡眠 3 秒时,consumer 调用 provider 的服务失败,页面显示访问失败

image-20201025170204488

后台打印:

provider 的打印显示:consumer 在调用 provider 提供的服务失败了以后,又重试了 2 次,一共调用了 3 次:

image-20201025170304097

consumer 打印:显示调用失败了,consumer 返回了null 给客户端:

image-20201025170422048

至此,服务降级的效果就实现了;

posted @ 2020-10-25 11:03  xian19900116  阅读(885)  评论(0)    收藏  举报
顶部 底部