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

前言

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

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

准备工作(springboot集成dubbo)

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

服务提供端

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

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错_第1张图片

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


<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.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.3.RELEASEversion>
        <relativePath/> 
    parent>

    <modelVersion>4.0.0modelVersion>
    <packaging>pompackaging>
    <groupId>com.learngroupId>
    <artifactId>dubbo-providerartifactId>
    <version>1.0-SNAPSHOTversion>
    <description>dubbo服务提供者入门实例description>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
    dependencies>

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

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

            
            <dependency>
                <groupId>org.apache.zookeepergroupId>
                <artifactId>zookeeperartifactId>
                <version>${zookeeper.version}version>
                <exclusions>
                    <exclusion>
                        <artifactId>slf4j-log4j12artifactId>
                        <groupId>org.slf4jgroupId>
                    exclusion>
                exclusions>
            dependency>
            <dependency>
                <groupId>org.apache.curatorgroupId>
                <artifactId>curator-frameworkartifactId>
                <version>${curator.version}version>
            dependency>
            <dependency>
                <groupId>org.apache.curatorgroupId>
                <artifactId>curator-recipesartifactId>
                <version>${curator.recipes.version}version>
            dependency>
        dependencies>
    dependencyManagement>

    <modules>
        <module>dubbo-provider-apimodule>
        <module>dubbo-provider-servicemodule>
    modules>
project>

服务端的service模块

pom文件如下:


<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-providerartifactId>
        <groupId>com.learngroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <groupId>com.learngroupId>
    <artifactId>dubbo-provider-serviceartifactId>


    <dependencies>
    	
        <dependency>
            <groupId>com.learngroupId>
            <artifactId>dubbo-provider-apiartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>

        <dependency>
            <groupId>org.apache.dubbogroupId>
            <artifactId>dubbo-spring-boot-starterartifactId>
        dependency>
        <dependency>
            <groupId>org.apache.dubbogroupId>
            <artifactId>dubboartifactId>
        dependency>
        <dependency>
            <groupId>org.apache.zookeepergroupId>
            <artifactId>zookeeperartifactId>
        dependency>
        <dependency>
            <groupId>org.apache.curatorgroupId>
            <artifactId>curator-frameworkartifactId>
        dependency>
        <dependency>
            <groupId>org.apache.curatorgroupId>
            <artifactId>curator-recipesartifactId>
        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张图片

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

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错_第3张图片

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

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错_第4张图片

4、启动两个应用配置

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错_第5张图片

这样就相当于我们服务端同一套代码在同一个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拾遗(一)——负载均衡实例,服务降级与集群容错_第6张图片

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拾遗(一)——负载均衡实例,服务降级与集群容错_第7张图片

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

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错_第8张图片

给指定应用增加配置

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

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错_第9张图片

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

Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错_第10张图片

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

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中常用的几个实例,在官网中都有指定的参考,只是一个简单的个人总结

你可能感兴趣的:(#,dubbo,#,RPC,java,分布式)