Dubbo拾遗(一)——负载均衡实例,服务降级与集群容错
前言
关于dubbo之前总结过基本的使用,但也只是简单总结了其入门使用——dubbo 简单实例。这篇博客打算总结一下dubbo中给我们提供的其他常见的一些功能。完整的内容可以参看dubbo官网——dubbo官网。
我们知道dubbo本身是一个微服务的治理框架,既然是面向微服务的,那么dubbo自然为我们提供了一套微服务治理所需的服务注册中心,负载均衡,集群容错,服务降级以及分布式链路追踪等一系列强大的组件和功能。这篇博客就简单从负载均衡说开去。
准备工作(springboot集成dubbo)
为了对负载均衡有一个直观的感受,这里先通过springboot集成dubbo,然后记录一下全部搭建过程。springboot集成dubbo比较简单,无非就是引入dubbo对应的starter支持动态化配置,然后引入dubbo的引用jar包,支持类的动态注入。
服务提供端
服务提供端搭建一个多模块的mvn应用,该应用基于springboot构建,包含一个api和一个service的简单模块
整体的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进入
2、点击添加,然后选择springboot应用(因为我们的应用是基于springboot)的
3、设置启动类和启动参数
4、启动两个应用配置
这样就相当于我们服务端同一套代码在同一个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-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的代码,按照如下步骤操作即可
启动之后,在浏览器中输入localhost:8080可以进入如下页面。
给指定应用增加配置
选择右侧的配置管理,然后在点击主界面的创建,在弹出的对话框中输入应用名以及配置内容,注意,这里的应用名需要与dubbo.application.name配置的一致。
启动项目,会发现zookeeper中关于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中常用的几个实例,在官网中都有指定的参考,只是一个简单的个人总结