欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

程序员文章站 2024-03-16 20:58:34
...

前言

关于dubbo之前总结过基本的使用,但也只是简单总结了其入门使用——dubbo 简单实例。这篇博客打算总结一下dubbo中给我们提供的其他常见的一些功能。完整的内容可以参看dubbo官网——dubbo官网

我们知道dubbo本身是一个微服务的治理框架,既然是面向微服务的,那么dubbo自然为我们提供了一套微服务治理所需的服务注册中心,负载均衡,集群容错,服务降级以及分布式链路追踪等一系列强大的组件和功能。这篇博客就简单从负载均衡说开去。

准备工作(springboot集成dubbo)

为了对负载均衡有一个直观的感受,这里先通过springboot集成dubbo,然后记录一下全部搭建过程。springboot集成dubbo比较简单,无非就是引入dubbo对应的starter支持动态化配置,然后引入dubbo的引用jar包,支持类的动态注入。

服务提供端

服务提供端搭建一个多模块的mvn应用,该应用基于springboot构建,包含一个api和一个service的简单模块

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

整体的pom文件如下,这里针对一些依赖用到了dependencyManagement,使得子模块在使用对应依赖的时候,无需指定版本号。

<?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">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <groupId>com.learn</groupId>
    <artifactId>dubbo-provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>dubbo服务提供者入门实例</description>

    <properties>
        <dubbo.starter.version>2.7.6</dubbo.starter.version>
        <dubbo.version>2.7.6</dubbo.version>
        <zookeeper.version>3.6.0</zookeeper.version>
        <curator.version>4.0.0</curator.version>
        <curator.recipes.version>4.0.0</curator.recipes.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- dubbo -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.starter.version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
            </dependency>

            <!-- zookeeper start -->
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>${zookeeper.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>slf4j-log4j12</artifactId>
                        <groupId>org.slf4j</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.curator</groupId>
                <artifactId>curator-framework</artifactId>
                <version>${curator.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.curator</groupId>
                <artifactId>curator-recipes</artifactId>
                <version>${curator.recipes.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <modules>
        <module>dubbo-provider-api</module>
        <module>dubbo-provider-service</module>
    </modules>
</project>

服务端的service模块

pom文件如下:

<?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">
    <parent>
        <artifactId>dubbo-provider</artifactId>
        <groupId>com.learn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.learn</groupId>
    <artifactId>dubbo-provider-service</artifactId>


    <dependencies>
    	<!--这里依赖api-->
        <dependency>
            <groupId>com.learn</groupId>
            <artifactId>dubbo-provider-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
        </dependency>
    </dependencies>

</project>

配置文件中加入如下几项

dubbo.scan.base-packages=com.learn.springboot.dubbo.provider
dubbo.application.name=dubbo-springboot-provider
## 这个必须加上,否则进行负载均衡实验的时候,会抛错
dubbo.application.id=liman-springboot-dubbo-provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo

对外提供一个HelloService的服务

import org.apache.dubbo.config.annotation.Service;
/**
 * autor:liman
 * createtime:2020/8/25
 * comment:
 */
//这里不是spring的service注解,而是dubbo的service注解
@Service
public class HelloServiceImpl implements IHelloService {
    @Override
    public String sayHello(String name) {
        System.out.printf("dubbo service is invoked param:%s\n",name);
        return "hello this is dubbo-sprintboot provider"+name;
    }
}

服务提供端api

api就没啥好说的了,就一个接口

/**
 * autor:liman
 * createtime:2020/8/25
 * comment:
 */
public interface IHelloService {
    public String sayHello(String name);
}

消费端

也基于springboot构建一个应用,目录结构比较单一,可以不用多模块的。pom依赖于服务端差不多,简单编写一个controller方便我们测试

/**
 * autor:liman
 * createtime:2020/8/25
 * comment:
 */
@RestController
public class HelloController {

    //Reference是dubbo的注解,这里的check=false,是让客户端启动的时候不去检测服务引用依赖。
    @Reference(check = false)
    IHelloService iHelloService;

    @RequestMapping(value="dubbohello",method=RequestMethod.GET)
    public String dubboHello(@RequestParam("name") String name){
        return iHelloService.sayHello(name);
    }

}

同时配置文件中配置如下几项

server.port=9909
dubbo.scan.base-packages=com.learn.dubboconsumer
dubbo.application.name=dubbo-springboot-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181

服务端启动的配置

我们为了试验负载均衡,服务端需要同一套代码在不同端口提供dubbo的服务。这就需要服务端存在两套启动的配置,在idea中可以通过配置启动参数完成。这里需要记录一下,同一套代码如果根据不同的启动参数启动多个应用。

1、进入启动编辑页面,idea中点击如下Edit Configurations进入

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

2、点击添加,然后选择springboot应用(因为我们的应用是基于springboot)的

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

3、设置启动类和启动参数

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

4、启动两个应用配置

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

这样就相当于我们服务端同一套代码在同一个IDE中,分别在20880和20881两个端口提供dubbo的应用服务。

看见服务正常启动,表示准备工作完成。

负载均衡

dubbo通过集成zookeeper之后解决了服务注册以及服务动态感知的问题。但是其实实际中,我们服务端可能不止一个,以我所在的公司为例(分为灾备和生产两个环境)每个环境中至少需要两台服务器提供服务,避免出现单点的情况,从一定程度上确保了服务的高可用。同时数据中心还支持多地双活。扯远了。

当服务端存在多个应用提供服务的时候,对于客户端而言,需要一种机制能实现目标服务的均衡调用,通过负载均衡,能让服务端处理的请求均衡分配。dubbo官网中关于这部分的介绍不太多——dubbo官网负载均衡。但也能看到dubbo提供大致以下几种负载均衡策略,同时还支持我们自行扩展dubbo的负载均衡策略。

Random LoadBalance

随机算法。这个是dubbo的默认负载均衡策略,即使我们不配置什么,默认的客户端调用就是采用的这个策略完成。其底层原理就是类似产生一个随机数,根据这个随机数所在的区间,决定最终请求被分发至何处。dubbo中的配置值:random

RoundRobin LoadBalance

加权轮询算法。还是通过实例来说明吧,官网上寥寥数笔,确实介绍的不多。简单的轮询比较容易理解,比如我们有三台服务器——A,B,C。第一个请求发送给A,第二个请求发送给B,第三个请求发送给C,第四个请求发送给A…这就是轮询,轮询其实很简单,就是一个比较公平的请求转发。但是实际情况中,可能存在A,B,C三个服务器性能存在差异,如果将等量的请求分发给性能较差的服务器,这显然是不合理的,因此我们需要给每个服务器配置一定的权重比例,根据这个权重比例来进行请求的分发——这就是加权轮询算法。dubbo中的配置值:roundrobin

LeastActive LoadBalance

**最小活跃调用数算法。**这个是一个比较科学的负载均衡算法,活跃调用数越小,表明这个服务端提供服务的能力越大,因此每次处理活跃调用数较小的服务端越有可能接收到客户端请求。dubbo中的配置值:leastactive

ConsistentHash LoadBalance

一致性hash算法。从名字也能看出来,这个就是和一致性hash类似了,dubbo根据客户端的调用参数等一系列内容,生成一个hash值,根据这个hash值决定调用的服务端。关于一致性hash算法可以参看其他大牛的介绍,这里不赘述。dubbo中的配置值:consistenthash

实例

在前面启动了两个基础的应用的前提下,我们启动消费端的应用程序,通过调用其中的controller,查看负载均衡的日志实例。

1、在服务提供端的@Service注解中配置loadbalance,或者在服务消费端的@Reference中配置loadbalance策略

@Service(loadbalance="roundrobin")
@Reference(check = false,loadbalance = "roundrobin")

客户端的loadbalance配置优先级高于服务端的配置

通过 http://localhost:9909/dubbohello?name=loadblancetest 不断发送该url请求,然后看看对应控制台的输出。

集群容错

所谓集群容错,其实也不是什么高端的操作,只是在微服务化之后,服务与服务之间的调用会出现第三种状态,调用超时或者出错,在实际开发中,有一些第三方的查询接口,其实本身不影响业务,但是我们不能因为在调用第三方接口出现问题时,直接让应用程序中断,这是不合理的,需要有一定的容错机制。dubbo也为我们提供了相关的容错策略,这个在客户端@Reference中的cluster配置。dubbo官网集群容错

Failover Cluster

失败自动切换,这个是默认的配置,当调用出现失败,则自动切换至其他提供服务的服务器。通常用于读操作,但重试会带来更长延迟。默认的重试次数配置是retries="2"这个不包含第一次,因此调用次数其实是3,如果超过三次,这会抛出服务调用超时(实际开发中已经碰到过N多次这种了)

Failfast Cluster

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

Failsafe Cluster

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

Failback Cluster

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

Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

Broadcast Cluster

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

配置如下:

@Reference(check = false, loadbalance = "roundrobin", cluster = "failover", retries = 5)
IHelloService iHelloService;

服务降级

当某个非关键的服务出现异常,可以通过降级该服务来临时屏蔽这个功能,按照不同维度可以分为人工降级和自动降级,按照功能可以分为读服务降级和写服务降级。dubbo官网——服务降级示例

常见的有故障降级和限流降级。故障降级——比如某个远程服务出现了网络异常,那么可以直接通过返回兜底的数据进行降级操作,限流降级——在突发访问量很大的情况下,可以通过设置相关阈值,当请求数达到阈值的时候,可以通过跳转到错误页面或者排队页面的方式进行降级。

Dubbo中提供了一个Mock的属性配置,可以通过Mock的方式来实现服务降级。

实例

在上述实例的基础上,服务端修改成如下代码

@Service(loadbalance="random",timeout = 10000)
public class HelloServiceImpl implements IHelloService {
    @Override
    public String sayHello(String name) {
    	//加入线程等待5秒的操作
        try {
            Thread.sleep(50_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.printf("dubbo service is invoked param:%s\n",name);
        return "hello this is dubbo-sprintboot provider"+name;
    }
}

在消费端加入一个Mock的接口

/**
 * autor:liman
 * createtime:2020/8/25
 * comment:客户端针对IHelloService调用的Mock操作。
 */
public class MockIHelloServiceImpl implements IHelloService {
    @Override
    public String sayHello(String name) {
        String errorMessage = "服务繁忙 , "+name+" 请稍后重试";
        return errorMessage;
    }
}

真正消费接口的配置

@RestController
public class HelloController {

	//这里指定Mock的配置,mock指定为我们上面新增的mock的类
    @Reference(check = false, loadbalance = "roundrobin", cluster = "failfast", timeout = 100
            , mock = "com.learn.dubboconsumer.controller.MockIHelloServiceImpl"
            , retries = 5)
    IHelloService iHelloService;

    @RequestMapping(value = "dubbohello", method = RequestMethod.GET)
    public String dubboHello(@RequestParam("name") String name) {
        return iHelloService.sayHello(name);
    }
}

运行结果,用postman操作如下

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

dubbo-admin的一些功能

关于dubbo-admin的一些功能在官网中也有介绍——dubbo-admin官网。主要有标签路由,应用级别的服务治理,配置管理以及元数据和服务测试等功能,这里简单介绍一下配置管理和元数据的功能。

dubbo-admin的GitHub地址——dubbo-admin GitHub地址

配置中心

目前dubbo支持的配置中心有很多,Apollo,zookeeper,nacos等,所谓配置中心,其实就是将每一个应用的配置信息管理起来,在工作中我接触的是Apollo配置中心,这里我们采用zookeeper来作为dubbo应用的配置中心。

准备工作

zookeeper启动的时候,如果没有配置服务端端口,则zookeeper的服务端端口为8080。这个端口与dubbo-admin的端口冲突,因此需要更改zookeeper服务端的端口配置,在zoo.cfg中加入如下配置

#zookeeper启动默认会占用8080端口,与dubbo-admin冲突
admin.serverPort=9999

编译启动dubbo-admin

其实在dubbo-admin的GitHub文档中都已经写明了,clone下dubbo-admin的代码,按照如下步骤操作即可

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

启动之后,在浏览器中输入localhost:8080可以进入如下页面。

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

给指定应用增加配置

选择右侧的配置管理,然后在点击主界面的创建,在弹出的对话框中输入应用名以及配置内容,注意,这里的应用名需要与dubbo.application.name配置的一致。

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

启动项目,会发现zookeeper中关于dubbo的配置,多了一个节点,如下图所示

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错

应用中还是需要指定原来的配置,作为一个兜底策略,但是需要新增如下两行配置

dubbo.config-center.address=zookeeper://127.0.0.1:2181
dubbo.config-center.app-name=dubbo-springboot-provider
## 配置信息的优先级配置,配置为true,表示外部配置信息的优先级更高(可选)
dubbo.config-center.highest-priority=true

元数据中心

dubbo本身是根据url驱动的一个RPC框架,本身关于服务端调用的一些配置信息都写在url里头,我们通过查看zookeeper的服务端节点会发现如下信息

dubbo%3A%2F%2F192.168.25.1%3A20880%2Fcom.learn.springboot.dubbo.provider.api.IHelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-springboot-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.learn.springboot.dubbo.provider.api.IHelloService%26methods%3DsayHello%26pid%3D5344%26release%3D2.7.6%26side%3Dprovider%26timeout%3D10000%26timestamp%3D1598412406232

这只是一个服务,一个接口的配置信息,如果有多个服务多个接口,zookeeper将这些信息推送给消费端都是一个很占用网络资源的操作。为此,dubbo给我们提供了元数据的功能。就是将这些配置信息共有的部分提出去来,作为一个元数据进行配置,使得接口级别的配置信息减少。dubbo的元数据可以采用zookeeper和Redis实现,官网推荐Redis。

我们需要在消费端配置如下属性,这里我们为了简便,不再单独启用Redis,如果需要使用Redis,可以直接将dubbo.metadata-report.address设置成指定的Redis地址即可。

dubbo.metadata-report.address=zookeeper://127.0.0.1:2181
dubbo.registry.simplified=true

配置之后的服务消费方地址

dubbo%3A%2F%2F192.168.25.1%3A20880%2Fcom.learn.springboot.dubbo.provider.api.IHelloService%3Fapplication%3Ddubbo-springboot-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26release%3D2.7.6%26timeout%3D10000%26timestamp%3D1598412769476

可以明显的看到,调用地址的长度明显减少(其中的GBK的编码,没有转换)

总结

本篇博客简单总结了一下dubbo中常用的几个实例,在官网中都有指定的参考,只是一个简单的个人总结