官网简介
Dubbo 官网为 dubbo.apache.org Dubbo 官网已做过了中英文国际化,用户可在中英文间任何切换。
什么是 PRC
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 协议假定某些传输协议的存在,如 TCP 或 UDP,为通信程序之间携带信息数据。在 OSI 网络通信模型(OSI 七层网络模型,OSI,Open System Interconnection,开放系统互联)中,RPC 跨越了传输层和应用层。RPC 使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC 采用客户机/服务器模式(即 C/S 模式)。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
当然,这里举一个最简单的例子来理解RPC。就比如我在深圳打电话给在北京的朋友,问他今天北京天气怎么样**(此时我是客户端(既服务消费者),我发出请求),朋友听到我的提问后把答案告诉我(此时朋友是服务端(既服务提供者),响应答案给我)**。 我没有去北京,而同过这一通电话,我知道了今天北京的天气。
Dubbo成长线路
- 2011 年开源,之后就迅速成为了国内该类开源项目的佼佼者。2011 年时,优秀的、可在生产环境使用的 RPC 框架很少,Dubbo 的出现迅速给人眼前一亮的感觉,迅速受到了开发者和各大企业的亲睐。
- 2012 年 10 月之后就基本停止了重要升级,改为阶段性维护。
- 2014 年 10 月 30 日发布 2.4.11 版本后,Dubbo 停止更新。
- 2017 年 10 月云栖大会上,阿里宣布 Dubbo 被列入集团重点维护开源项目,这也就意味着 Dubbo 起死回生。
- 2018 年 2 月 15 日阿里把Dubbo捐献给Apache基金会,进入Apache孵化器。
- 2018年7月Dubbo官网更新为Dubbo.apache.org,并开始使用新的Logo。
Dubbo四大组件
- Provider:服务提供者。
- Consumer:服务消费者。
- Registry:服务注册与发现的中心,提供目录服务。
- Monitor:服务监控,统计服务的调用次数、调用时间等信息的日志服务,并可以对服务设置权限、降级处理等,称为服务管控中心。
Dubbo特性
| 分类 | 特性 |
|---|---|
| 面向接口代理的高性能RPC调用 | 提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节 |
| 服务自动注册与发现 | 支持多种注册中心服务,服务实例上下线实时感知 |
| 运行期流量调度 | 内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布 |
| 负载均衡 | 内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量 |
| 高扩展 | 遵循微内核+插件的设计思想,所有核心能力如Protocol,Transport,Serialization被设计为扩展点,平等对待内置实现和第三方实现 |
| 服务治理 | 提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数 |
搭建Web版的Dubbo项目
Dubbo 的使用是基于 Spring 环境下的,即 Dubbo 是依赖于 Spring 框架的。Dubbo2.7.0依赖的Spring是4.3.16。所以,在Dubbo的开发过程中最好使用与该Spring版本相同的Spring,这样可以避免可能的版本冲突问题。
创建consumer-web工程
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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gc</groupId>
<artifactId>consumer-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- 自定义版本号 -->
<spring-version>4.3.16.RELEASE</spring-version>
</properties>
<dependencies>
<!-- zk客户端依赖:curator -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
<!-- dubbo依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- commons-logging依赖 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- Servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- JSP依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>consumer-web</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
定义业务接口
package com.gc.service;
public interface GcService {
String getGcInfo();
}
定义业务接口具体实现
package com.gc.service;
public class GcServiceImpl implements GcService {
@Override
public String getGcInfo() {
return "Name=GC";
}
}
定义控制器GcController
package com.gc.controller;
import com.gc.service.GcService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class GcController {
@Autowired
private GcService service;
@RequestMapping("/gc.do")
public String gcHandle() {
String result = service.getGcInfo();
System.out.println("消费者端接收到 = " + result);
return "/index.jsp";
}
}
配置spring-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 服务名称 -->
<dubbo:application name="consumer-web"/>
<!--指定服务注册中心:zk单机-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!--订阅服务-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService"/>
<!--注册处理器-->
<mvc:component-scan base-package="com.gc.controller"/>
</beans>
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--对于2.6.4版本,其Spring配置文件必须指定从<context-param>中加载-->
<!--<context-param>-->
<!--<param-name>contextConfigLocation</param-name>-->
<!--<param-value>classpath:spring-*.xml</param-value>-->
<!--</context-param>-->
<!--字符编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--注册中央调度器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--不能写/*,不建议写/,建议扩展名方式-->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
编写index.jsp
<html>
<body>
<h2>Hello World! - consumer</h2>
</body>
</html>
项目结构
创建provider-web工程
pom依赖
和consumer-web工程的pom一样
把项目名称改成
<groupId>com.gc</groupId>
<artifactId>provider-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
定义业务接口
package com.gc.service;
public interface GcService {
String getGcInfo();
}
定义业务接口具体实现
package com.gc.service.impl;
import com.gc.service.GcService;
public class GcServiceImpl implements GcService {
@Override
public String getGcInfo() {
System.out.println("执行提供者的getGcInfo()");
return "执行提供者的getGcInfo()";
}
}
配置spring-provider.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="provider-web"/>
<!--指定服务注册中心:zk单机-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!--注册服务执行对象-->
<bean id="gcService" class="com.gc.service.impl.GcServiceImpl"/>
<!--服务暴露-->
<dubbo:service interface="com.gc.service.GcService" ref="gcService" />
</beans>
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--注册Spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</context-param>
<!--注册ServletContext监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
项目结构
配置tomcat容器
运行访问
Dubbo高级配置
基本的使用已经搭建过了,现在来看一下dubbo中有那些高级配置。
关闭服务检查
<dubbo:reference id="gcService" check="false" interface="com.gc.service.GcService"/>
作用及说明
默认情况下,若服务消费者先于服务提供者启动,则消费者端会报错。因为默认情况下消费者会在启动时查检其要消费的服务的提供者是否已经注册,若未注册则抛出异常。可以在消费者端的 spring 配置文件中添加 check=”false”属性,则可关闭服务检查功能。
只要注意启动顺序,该属性看似可以不使用。但在循环消费场景下是必须要使用的。即A 消费 B 服务,B 消费 C 服务,而 C 消费 A 服务。这是典型的循环消费。在该场景下必须至少有一方要关闭服务检查功能,否则将无法启动任何一方。
多版本控制
当系统进行升级时,一般都是采用“灰度发布(又称为金丝雀发布)”过程。即在低压力时段,让部分消费者先调用新的提供者实现类,其余的仍然调用老的实现类,在新的实现类运行没有问题的情况下,逐步让所有消费者全部调用成新的实现类。多版本控制就是实现灰度发布的。
服务提供者
<!--注册服务执行对象-->
<bean id="gcService" class="com.gc.service.impl.GcServiceImpl"/>
<bean id="newGcService" class="com.gc.service.impl.NewGcServiceImpl"/>
<!--服务暴露-->
<!-- 旧版本 -->
<dubbo:service interface="com.gc.service.GcService" ref="gcService" version="0.0.1" />
<!-- 新版本 -->
<dubbo:service interface="com.gc.service.GcService" ref="newGcService" version="0.0.2" />
服务消费者
<!--订阅服务 -->
<!-- 消费者端指定使用0.0.2 最新版本 -->
<dubbo:reference id="gcService" check="false" interface="com.gc.service.GcService" version="0.0.2"/>
服务分组
服务分组与多版本控制的使用方式几乎是相同的,只要将 version 替换为 group 即可。但使用目的不同。使用版本控制的目的是为了升级,将原有老版本替换掉,将来不再提供老版本的服务,所以不同版本间不能出现相互调用。
而分组的目的则不同,其也是针对相同接口,给出了多种实现类。但不同的是,这些不同实现并没有谁替换掉谁的意思,是针对不同需求,或针对不同功能模块所给出的不同实现。这些实现所提供的服务是并存的,所以它们间可以出现相互调用关系。例如,对于支付服务的实现,可以有微信支付实现与支付宝支付实现等。
服务提供者
<!--注册Service实现类-->
<bean id="weixinService" class="WeixinServiceImpl"/>
<bean id="alipayService" class="AlipayServiceImpl"/>
<!--暴露服务-->
<dubbo:service interface="com.gc.service.WeixinService" ref="weixinService" group="pay.weixin"/>
<dubbo:service interface="com.gc.service.AlipayService" ref="alipayService" group="pay.alipay"/>
服务消费者
<!--注册Service实现类-->
<bean id="weixinService" class="WeixinServiceImpl"/>
<bean id="alipayService" class="AlipayServiceImpl"/>
<!--暴露服务-->
<!--使用微信支付分组的Service实现类-->
<dubbo:service interface="com.gc.service.PayService" ref="weixinService" group="pay.weixin"/>
<!--使用支付宝支付分组的Service实现类-->
<dubbo:service interface="com.gc.service.PayService" ref="alipayService" group="pay.alipay"/>
多协议支持
dubbo 协议 (默认协议)
-
连接个数:单连接
-
连接方式:长连接
-
传输协议:TCP
-
传输方式:NIO 异步传输
-
适用范围:传入传出参数数据包较小(建议小于 100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。
rmi 协议
-
连接个数:多连接
-
连接方式:短连接
-
传输协议:TCP
-
传输方式:BIO 同步传输
-
适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。
hession 协议
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:HTTP
- 传输方式:BIO 同步传输
- 适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者抗压能力较大,可传文件。
http 协议
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:HTTP
- 传输方式:BIO 同步传输
- 适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或 URL 传入参数,暂不支持传文件。
webService 协议
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:HTTP
- 传输方式:BIO 同步传输
- 适用场景:系统集成,跨语言调用
thrift 协议
Thrift 是 Facebook 捐给 Apache 的一个 RPC 框架,其消息传递采用的协议即为 thrift协议。当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展。Thrift 协议不支持 null值的传递。
memcached 协议与 redis 协议
缓存服务器。它们会对传输的数据使用相应的技术进行缓存。
rest 协议
若需要开发具有 RESTful 风格的服务,则需要使用该协议。
服务提供者
<!--声明要使用的协议,如果不配置协议的端口号,它将使用默认的端口号-->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!--注册服务执行对象-->
<bean id="gcService" class="com.gc.service.impl.GcServiceImpl"/>
<bean id="newGcService" class="com.gc.service.impl.NewGcServiceImpl"/>
<!--服务暴露-->
<!-- 旧版本 -->
<dubbo:service interface="com.gc.service.GcService" ref="gcService" version="0.0.1" />
<!-- 新版本 -->
<dubbo:service interface="com.gc.service.GcService" ref="newGcService" version="0.0.2" protocol="dubbo,rmi"/>
服务消费者
<!--订阅服务 -->
<!-- 消费者端指定使用0.0.2 最新版本 -->
<dubbo:reference id="gcService" check="false" interface="com.gc.service.GcService" protocol="rmi" version="0.0.2"/>
使用场景
同一服务支持多种协议
系统在使用过程中其使用场景逐渐发生了变化,例如,由原来的消费者数量多于提供者数量,变为了消费者数量与提供者数量差不多了,并且原来系统不用传输文件,现在的系统需要传输文件了。此时就将将原来默认的 dubbo协议更换为 rmi 协议。目的是为了兼容老工程,扩展新功能。
不同服务使用不同协议
同一个系统中不同的业务具有不同的特点,所以它们的传输协议就应该根据它们的特点选择不同的协议。例如对于前面使用服务分组实现的“微信支付”与“支付宝支付”,就可以针对不同的支付方式,使用不同的协议。
负载均衡
若消费者与提供者均设置了负载均衡策略,消费者端设置的优先级高。若消费者端没有显式的设置,但提供者端显式的设置了,且同一个服务(接口名、版本号、分组都相同)的负载均衡策略相同。消费者调用时会按照提供者设置的策略调用。
若多个提供者端设置的不相同,则最后一个注册的会将前面注册的信息覆盖。
Dubbo 内置的负载均衡算法
-
random:随机算法,是 Dubbo 默认的负载均衡算法。存在服务堆积问题。
-
roundrobin:轮询算法。按照设定好的权重依次进行调度。
-
leastactive:最少活跃度调度算法。即被调度的次数越少,其优选级就越高,被调度到的机率就越高。
-
consistenthash:一致性 hash 算法。对于相同参数的请求,其会被路由到相同的提供者。
指定负载均衡算法
<!--设置调用指定服务的负载均衡算法类型-->
<dubbo:reference id="gcService" check="false" loadbalance="random" interface="com.gc.service.GcService" protocol="rmi" version="0.0.2"/>
<!--如果其它方法没有指定算法,则使用服务中指定的random随机算法-->
<!--指定一个服务中的某一个方法的负载均衡算法-->
<dubbo:reference id="gcService" check="false" loadbalance="random" interface="com.gc.service.GcService" protocol="rmi" version="0.0.2">
<!--指定getGcInfo方法使用随机负载均衡算法-->
<dubbo:method name="getGcInfo" loadbalance="random" />
<!--指定getGcAge方法使用负载均衡轮询算法-->
<dubbo:method name="getGcAge" loadbalance="roundrobin" />
<!--指定getGcName方法使用一致性哈希算法-->
<dubbo:method name="getGcName" loadbalance="consistenthash"/>
</dubbo:reference>
集群容错
集群容错指的是,当消费者调用提供者集群时发生异常的处理方案。
Dubbo 内置的容错策略
- Failover:故障转移策略。当消费者调用提供者集群中的某个服务器失败时,其会自动尝试着调用其它服务器。该策略通常用于读操作,例如,消费者要通过提供者从 DB 中读取某数据。但重试会带来服务延迟。
- Failfast:快速失败策略。消费者端只发起一次调用,若失败则立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe:失败安全策略。当消费者调用提供者出现异常时,直接忽略本次消费操作。该策略通常用于执行相对不太重要的服务,例如,写入审计日志等操作。
- Failback:失败自动恢复策略。消费者调用提供者失败后,Dubbo 会记录下该失败请求,然后定时自动重新发送该请求。该策略通常用于实时性要求不太高的服务,例如消息通知操作。
- Forking:并行策略。消费者对于同一服务并行调用多个提供者服务器,只要一个成功即调用结束并返回结果。通常用于实时性要求较高的读操作,但其会浪费较多服务器资源。
- Broadcast:广播策略。广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
配置集群容错策略
容错策略可以设置在消费者端,也可以设置在提供者端。若消费者与提供者均做了设置,则消费者端的优先级更高。
<!--指定该服务的集群容错策略为: 快速失败-->
<dubbo:reference id="gcService" check="false" loadbalance="random" interface="com.gc.service.GcService" protocol="rmi" version="0.0.2" cluster="failfast">
<!--指定在调用该方法时,如果发送失败的情况,设置重试次数为2次(2次不包括第一次调用)-->
<dubbo:method name="getGcInfo" loadbalance="random" retries="2"/>
</dubbo:reference>
服务降级
什么是服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略的降低服务级别,以释放服务器资源,保证核心任务的正常运行。例如,双 11 时 0 点-2 点期间淘宝用户不能修改收货地址,不能查看历史订单,就是典型的服务降级。
服务降级方式
能够实现服务降级方式很多,常见的有如下几种情况:
- 部分服务暂停:页面能够访问,但是部分服务暂停服务,不能访问。
- 部分服务延迟:页面可以访问,当用户提交某些请求时系统会提示该操作已成功提交给了服务器,由于当前服务器繁忙,此操作随后会执行。在等待了若干时间后最终用户可以看到正确的执行结果。
- 全部服务暂停:系统入口页面就不能访问,提示由于服务繁忙此服务暂停。跳转到一个预先设定好的静态页面。
- 随机拒绝服务:服务器会按照预先设定好的比例,随机挑选用户,对其拒绝服务。作为用户,其看到的就是请重试。可能再重试就可获得服务。
系统的服务降级埋点
服务降级与 Mock 机制
Dubbo的服务降级采用的是mock机制。其具有两种降级处理方式:Mock Null降级处理,与 Class Mock 降级处理。
方法降级
<!--方法降级-->
<!--如果方法有返回值则返回null,如果方法没有返回值则无任何显示-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService" mock="return null" />
类降级
package com.gc.service;
/**
* 在业务接口所在的包中,本例为 com.gc.service 包,定义一个类,该类的命名需要满足以下规则:业务接口简单类名 + Mock。
* @description: 服务降级之类降级
* @author: GC
* @create: 2020-12-10 14:01
**/
public class GcServiceMoke implements GcService{
@Override
public String getGcInfo() {
return "当前访问人数较多,请您稍后重试。";
}
}
<!--类降级-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService" mock="true" />
服务限流
为了防止某个消费者的 QPS 或是所有消费者的 QPS 总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
Dubbo 中能够实现服务限流的方式较多,可以划分为两类:直接限流与间接限流。
- 直接限流:通过对连接的数量直接限制来达到限流的目的。超过限制则会让再来的请求等待,直到等待超时,或获取到相应服务(官方方案)。
- 间接限流:通过一些非连接数量设置的间接手段来达到限流的目的(个人经验)。
直接限流
executes 限流—仅提供者端
该属性仅能设置在提供者端。可以设置为接口级别,也可以设置为方法级别。对指定服务(方法)的连接数量进行限制。
<!--设置该服务中的方法并发数不能超过10-->
<dubbo:service interface="com.gc.service.GcService" ref="gcService" executes="10">
<!--设置单一方法的并发数不能超过5 (方法设置优先于类设置)-->
<dubbo:method name="getGcInfo" executes="5"/>
</dubbo:service>
accepts 限流—仅提供者端
<!--设置使用dubbo协议的服务提供者最多不能超过10个消费者连接-->
<dubbo:protocol name="dubbo" port="20880" accepts="10"/>
actives 限流—两端均可
提供者端限流
根据客户端与服务端建立的连接是长连接还是短连接,其意义不同:
- 长连接:当前这个服务上的一个长连接最多能够处理的请求个数。对长连接数量没有限制。
- 短连接:当前这个服务上可以同时处理的短连接数量。
消费者端限流
- 长连接:当前这个消费者的一个长连接最多能够提交的请求个数。对长连接数量没有限制。
- 短连接:当前这个消费者可以同时提交的短连接数量。
<!--以消费者端为例-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService" actives="10"/>
<!--以消费者端为例-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService">
<dubbo:method name="getGcInfo" loadbalance="random" actives="9"/>
</dubbo:reference>
connections 限流—两端均可
可以设置在提供者端,也可以设置在消费者端。限定连接的个数。
一般情况下,我们使用的都是默认的服务暴露协议 Dubbo,所以,一般会让 connections与 actives 联用。connections 限制长连接的数量,而 actives 限制每个长连接上的请求数量。
<!--以消费者端为例-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService" connections="8" actives="10" />
<!--以消费者端为例-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService">
<dubbo:method name="getGcInfo" loadbalance="random" connections="8" actives="10" />
</dubbo:reference>
间接限流
延迟连接
只要消费者真正调用提供者方法时才创建长连接。
仅可设置在消费者端,且不能设置为方法级别。仅作用于 Dubbo 服务暴露协议。用于减少长连接数量。
<dubbo:reference id="gcService" interface="com.gc.service.GcService" layer="true" />
<!--设置当前整个消费者中的所有类都为延迟连接-->
<dubbo:consumer layer="true" />
粘连连接
粘连连接是指,让所有客户端要访问的同一接口的同一方法,尽可能是的由同一Inovker 提供服务。其用于限定流向。
粘连连接仅能设置在消费者端,其可以设置为接口级别,也可以设置为方法级别。方法级别是,仅该方法启用粘连连接。接口级别则是指,接口中每一个方法均启用了粘连连接,不用再逐个方法设置了。
仅作用于 Dubbo 服务暴露协议。用于减少长连接数量。粘连连接的开启将自动开启延迟连接。
<dubbo:reference id="gcService" interface="com.gc.service.GcService" sticky="true" />
<dubbo:reference id="gcService" interface="com.gc.service.GcService">
<dubbo:method name="getGcInfo" sticky="true" />
</dubbo:reference>
负载均衡
可以设置在消费者端,亦可设置在提供者端;可以设置在接口级别,亦可设置在方法级别。其可以限定流向,但其没有限制了流量。
<!--设置负载均衡散发为,最小活跃度。 既使用最少的服务节点来执行请求处理-->
<dubbo:reference id="gcService" interface="com.gc.service.GcService" loadbalance="leastactive"/>
声明式缓存
为了进一步提高消费者对用户的响应速度,减轻提供者的压力,Dubbo 提供了基于结果的声明式缓存。该缓存是基于消费者端的,所以使用很简单,只需修改消费者配置文件,与提供者无关。该缓存是缓存在消费者端内存中的,一旦缓存创建,即使提供者宕机也不会影响消费者端的缓存。
注:这种声明式缓存在常规业务场景或一般业务场景中使用的概率不高。其默认是缓存1000个结果数据,当超出1000条数据后,将采用LUR淘汰策略进行淘汰且不能修改淘汰策略。
<dubbo:reference id="gcService" interface="com.gc.service.GcService" cache="true"/>
<dubbo:reference id="gcService" interface="com.gc.service.GcService" >
<dubbo:method name="getGcInfo" cache="true"/>
</dubbo:reference>
多注册中心
<!--声明注册中心-->
<dubbo:registry id="bj" address="zookeeper://127.0.0.1:2181"/> <!--北京中心-->
<dubbo:registry id="sh" address="zookeeper://127.0.0.1:2181"/> <!--上海中心-->
<dubbo:registry id="sz" address="zookeeper://127.0.0.1:2181"/> <!--深圳中心-->
<dubbo:registry id="hz" address="zookeeper://127.0.0.1:2181"/> <!--杭州中心-->
<!--给服务设置指定的注册中心-->
<dubbo:service interface="com.gc.service.GcService" ref="gcService" register="bj,sh" />
<dubbo:service interface="com.gc.service.GcService" ref="newGcService" register="hz" />
服务暴露延迟
如果我们的服务启动过程需要预热,就可以使用 delay 进行服务延迟暴露。只需在服务提供者的标签中添加 delay 属性。其值可以有三类:
- 正数:单位为毫秒,表示在提供者对象创建完毕后的指定时间后再发布服务。
- 0:默认值,表示当前提供者创建完毕后马上向注册中心暴露服务。
- -1:表示在 Spring 容器初始化完毕后再向注册中心暴露服务。
<dubbo:service interface="com.gc.service.GcService" ref="gcService" delay="5000" />
属性配置优先级
Dubbo 配置文件中各个标签属性配置的优先级总结:
- 方法级优先,接口级(服务级)次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
- dubbo:consumer/设置在消费者端,用于设置消费者端的默认配置,即消费者端的全局设置。
- dubbo:provider/设置在提供者端,用于设置提供者端的默认配置,即提供者端的默认配置。