本篇一句话总结:Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了面向接口的远程方法调用、集群容错和负载均衡、以及服务自动注册和发现的功能。
正文开始:
- 什么是Dubbo?
- 为什么选择Dubbo?
- 怎么用Dubbo?
上面这几个问题,是每个刚接触 Dubbo的人都想知道的。下面小兵综合自己的理解和使用情况,在分布式专题里总结一篇关于 Dubbo的内容。全文看完,我们对 Dubbo也有一定的了解了。
什么是Dubbo?
总结一句话,就是:
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了面向接口的远程方法调用、集群容错和负载均衡、以及服务自动注册和发现的功能。
从这句话我们可以知道 Dubbo的基本定位是RPC框架,另外Dubbo也作为一个分布式服务治理框架,除了能够提供通用RPC框架的远程方法调用功能外,还能提供负载均衡、服务注册与发现等功能。
为什么选择Dubbo?
RPC(Remote Procedure Call):远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。
RPC是一种思想,不是指某种具体的技术。 它的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。常见 RPC 技术和框架有:RMI、Spring RMI、WebService、HTTP、Hessian、Dubbo等。RMI、HTTP、Hessian我们都比较熟悉了,他们用起来也比较简单。实际上在大规模服务化之前,应用可能只是通过RMI或者Hessian等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,我们也能快速地实现RPC。
但是如果只是简单地配置URL来处理应用与应用之间的远程调用,当远程调用交互越来越多时,URL配置会变得非常庞大,不利于管理。比如某台服务上的应用需要移动了,那么相应的URL配置就得跟着修改,否则就会调用失败。这时候我们将配置URL这一部分功能抽出来作为一个服务注册中心,每次调用服务时服务的地址都从注册中心取,这时候无论你哪台机器移动了,只要注册中心还在,只要注册中心能够返回可用的服务地址给我,那我就不用改变。服务注册中心的一个功能,就是能够自动理清应用间的依赖关系。通过在接收方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对F5硬件负载均衡器的依赖,也能减少部分成本。
另外,不同的服务、同一个服务在不同的时间需要的机器支持可能都是不同的,这个服务需要多少机器支撑?什么时候该加机
器?如何监控各个服务的调用量、响应时间,是需要考虑的。对这些有了清晰的了解之后,才可以更有效率的利用硬件设备,节省成本。
以12306系统为例,平日里的访问量和春运时的访问量是完全不同的。硬件设备容量不足,必然导致春运时系统崩溃,而保证了春运时的硬件设备,在平日里访问量剧减,又浪费了成本。这些都需要一个解决方案来动态的、流动性的调度各个服务。
dubbo就是其中一种解决方案,也是dubbo出现的原因。
Dubbo架构和工作流程
我们先看一下Dubbo的架构图:
节点角色说明:
- Provider: 暴露服务的服务提供方。
- Consumer: 调用远程服务的服务消费方。
- Registry: 服务注册与发现的注册中心。
- Monitor: 统计服务的调用次调和调用时间的监控中心。
- Container: 服务运行容器。
调用过程说明:
- 0. 服务容器负责启动,加载,运行服务提供者。
- 1. 服务提供者在启动时,向注册中心注册自己提供的服务。
- 2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
那么,我们究竟是在什么地方使用到的 Dubbo 呢?大家请看下面的流程图:
简单来说,用户发送的请求转交给 Nginx,然后 Nginx 决定将请求发送那个服务器(此处为 Tomcat),然后 Tomcat 将请求发送给 Dubbo,由它来决定继续调用哪个 service 层去数据库读取数据。
相信大家对于 Dubbo 作用于何处应该有个大体的了解了。(好像所有RPC框架都是这个流程。。)
Dubbo实现远程方法调用
下面我们一起来用 Dubbo来实现下面的小目标。
(终于要回到我们熟悉的代码部分了,Code Time Begin !)
小目标:对基于远程通信技术(Dubbo技术)的编程有初步的了解。具体需求如下:
1)从消费端(Consumer)把“Hello, I am xxx. Here is Dubbo Consumer.”作为参数调用服务提供端的远程方法;
2)从服务提供端(Provider)读取该参数,并返回消息:“Hello, xxx, nice to meet you! Here is Dubbo Provider.”
我们可以按照以下步骤实现上述需求:
一、定义统一的Api接口
首先我们创建一个公用项目dubbo-api,此项目为Consumer、Provider项目提供接口,保证生产者和消费者所使用的接口统一。我们在 dubbo-api 项目中创建包 com.jvxb.test.dubbo.api,并添加 HelloDubboService 接口。
public interface HelloDubboService {
String helloDubbo(String requestMsg);
}
二、服务端实现接口
import com.jvxb.test.dubbo.api.HelloDubboService;
import org.springframework.stereotype.Service;
@Service("helloDubboService")
public class HelloDubboServiceImpl implements HelloDubboService {
@Override
public String helloDubbo(String requestMsg) {
System.out.println("服务端收到客户端信息:" + requestMsg);
return "Hello, jvxb, nice to meet you! Here is Dubbo Server.";
}
}
三、将服务端实现的接口注册到服务中心
接口实现类添加后,我们需要将其注册到注册中心,只有这样,其他应用才有可能请求到,接下来我们在 src / main / resources 下创建 provider.xml 文件,用于配置dubbo服务端注册,代码如下:
3.1 此时服务端pom.xml中要先引入dubbo 和 zookeeper的依赖。
<dependency>
<groupId>com.jvxb.test</groupId>
<artifactId>dubbo-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
3.2 在provider.xml中注册提供的服务
3.3 在启动类DubboProviderApplication上添加注解
四、消费端通过注册中心引用接口
4.1 此时消费端pom.xml中要先引入dubbo 和 zookeeper的依赖。(同服务端,此处略)
4.2 在 consumer.xml中获取注册的服务
4.3 在启动类DubboConsumerApplication上添加注解
关于dubbo的配置文件其它参数,可参考:dubbo 配置文件详解
五、测试
5.1 在客户端添加controller,使用从注册中心获取的服务
@RestController
public class ConsumerControllerTest {
@Autowired
private HelloDubboService helloDubboService;
@RequestMapping("helloDubbo")
public String helloDubbo(){
String response = helloDubboService.helloDubbo("Hello, I am jvxb. Here is Dubbo Client.");
System.out.println(response);
return response;
}
}
5.2 启动服务端(可改为8081端口),启动客户端(还是默认的8080端口)
启动过程有两个点值得关注一下:主机绑定、服务注册与发现
- 主机绑定:在发布一个dubbo服务的时候,会生成一个dubbo://ip:port的协议地址。这就是主机绑定过程,那么这个IP是如何生成的呢?大家可以通过ServiceConfig.java 中的 doExportUrlForlProtocol 方法中找到对应代码块。大致的逻辑是,首先从配置文件中获取,如果失败再尝试从本地网卡中获取host,如果这个也失败,会继续执行,直到找到合适的IP地址。
- 启动服务端和客户端时,将进行服务自动注册和发现,参看:Dubbo服务注册与动态发现机制的原理与实现细节
5.3 访问客户端的测试接口(helloDubbo),查看结果。
通过以上的实例可以看到,通过简单的配置,dubbo就能够实现远程方法调用,即完成了它作为RPC框架的基本功能。并且通过zookeeper,dubbo可以在服务端启动时,就自动将服务端暴露的服务注册到注册中心(服务注册),在服务消费者在启动后就可以向注册中心订阅想要的服务(服务发现)。所以我们说“Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了面向接口的远程方法调用、集群容错和负载均衡、以及服务自动注册和发现的功能”,其中的“是一款RPC框架”、“提供面向接口的远程方法调用”、“服务自动注册和发现”也就不难理解了。
Dubbo集群容错
在分析dubbo的集群容错前,先了解什么是容错机制?容错机制指的是某种系统控制在一 定范围内的一种允许或包容犯错情况的发生,举个简单例 子,我们在电脑上运行一个程序,有时候会出现无响应的情况,然后系统会弹出一个提示框让我们选择,是立即结 束还是继续等待,然后根据我们的选择执行对应的操作, 这就是“容错”。
在分布式架构下,网络、硬件、应用都可能发生故障,由 于各个服务之间可能存在依赖关系,如果一条链路中的其 中一个节点出现故障,将会导致雪崩效应。为了减少某一 个节点故障的影响范围,所以我们才需要去构建容错服务(这里要说明一点,容错机制仅仅是处理节点故障的一种机制), 来优雅的处理这种中断的响应结果 。
Dubbo提供了6种容错机制,分别如下
- 1. failsafe 失败安全,可以认为是把错误吞掉(记录日 志)
- 2. failover(默认) 重试其他服务器; retries(2) ,缺省的重试次数,不包含第一次
- 3. failfast 快速失败, 失败以后立马报错
- 4. failback 失败后自动恢复。
- 5. forking forks. 设置并行数
- 6. broadcast 广播,任意一台报错,则执行的方法报错
配置方式如下,通过指定cluster方式,配置指定的容错方案
<!--声明需要暴露的服务接口,指定版本号-->
<dubbo:reference id="helloDubboService" interface="com.jvxb.test.dubbo.api.HelloDubboService" version="1.0.0"
cluster="failover" retries="3"/>
配置的优先级别
客户端会优于服务端,这里还可以细化,可以细化到方法级别
1. 方法级优先,接口级次之,全局配置再次之。
2. 如果级别一样,则消费方优先,提供方次之
其中,服务提供方配置,通过URL经由注册中心传递给消费方
什么应该配置在客户端,什么应该配置在服务端,retires、loadBlance、cluster(客户端)、timeout(服务端)
以 timeout 为例,建议由服务提供方设置超时,因为一个方法需要执行多长 时间,服务提供方更清楚,如果一个消费方同时引用多个 服务,就不需要关心每个服务的超时设置。
我们说“Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了面向接口的远程方法调用、集群容错和负载均衡、以及服务自动注册和发现的功能”,其中的“集群容错”也就不难理解了。
Dubbo实现负载均衡
我们说“Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了面向接口的远程方法调用、集群容错和负载均衡、以及服务自动注册和发现的功能”,其中的“是一款RPC框架”、“提供面向接口的远程方法调用”、“服务自动注册和发现”通过上面的例子我们已经解释了,下面我们看看Dubbo是如何实现负载均衡的。
Dubbo内置了4种负载均衡策略:
- RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。
- RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。
- LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
- ConsistentHashLoadBalance:一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。
负载均衡配置
如果不指定负载均衡,默认使用随机负载均衡。我们也可以根据自己的需要,显式指定一个负载均衡。 可以在多个地方类来配置负载均衡,比如 Provider 端,Consumer端,服务级别,方法级别等。
服务端服务级别
<dubbo:service interface="..." loadbalance="roundrobin" />
该服务的所有方法都使用roundrobin负载均衡。
客户端服务级别
<dubbo:reference interface="..." loadbalance="roundrobin" />
该服务的所有方法都使用roundrobin负载均衡。
服务端方法级别
<dubbo:service interface="...">
<dubbo:method name="hello" loadbalance="roundrobin"/>
</dubbo:service>
只有该服务的hello方法使用roundrobin负载均衡。
客户端方法级别
<dubbo:reference interface="...">
<dubbo:method name="hello" loadbalance="roundrobin"/>
</dubbo:reference>
只有该服务的hello方法使用roundrobin负载均衡。
和Dubbo其他的配置类似,多个配置是有覆盖关系的:
-
方法级优先,接口级次之,全局配置再次之。
-
如果级别一样,则消费方优先,提供方次之。
所以,上面4种配置的优先级是:
-
客户端方法级别配置。
-
客户端接口级别配置。
-
服务端方法级别配置。
-
服务端接口级别配置。
Dubbo的4种负载均衡的实现,大多数情况下能满足要求。有时候,因为业务的需要,我们也可以实现自己的负载均衡策略。此处不再扩展。
负载均衡实例
1.复制我们的dubbo-provider项目,改名为dubbo-provider2,并修改其中的server-port为8082和dubbo协议端口号为20881。(模拟两个服务)
<dubbo:protocol name="dubbo" port="20881" accesslog="true" />
2.修改两个provider项目HelloDubboServiceImpl的实现类,标识是20880还是20881的服务器。
public String helloDubbo(String requestMsg) {
System.out.println("服务端(20881)收到客户端信息:" + requestMsg);
return "Hello, jvxb, nice to meet you! Here is Dubbo Server(20881).";
}
3.在客户端的 consumer.xml下,引用服务时使用轮询算法,即reference的loadbalance为轮询roundrobin,来使消费机轮询调用不同的服务机,使程序在单点崩溃的情况下能做合理的负载均衡。
<dubbo:reference id="helloDubboService" interface="com.jvxb.test.dubbo.api.HelloDubboService" version="1.0.0"
retries="3" loadbalance="roundrobin"/>
4.测试:连续访问客户端的测试接口(helloDubbo),查看结果。
我们说“Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了面向接口的远程方法调用、集群容错和负载均衡、以及服务自动注册和发现的功能”,其中的“负载均衡”也就不难理解了。
Dubbo实现服务降级
降级的目的是为了保证核心服务可用。
降级可以有几个层面的分类: 自动降级和人工降级; 按 照功能可以分为:读服务降级和写服务降级;
- 对一些非核心服务进行人工降级,在大促之前通过降级 开关关闭哪些推荐内容、评价等对主流程没有影响的功 能
- 故障降级,比如调用的远程服务挂了,网络故障、或者 RPC服务返回异常。 那么可以直接降级,降级的方案比 如设置默认值、采用兜底数据(系统推荐的行为广告挂 了,可以提前准备静态页面做返回)等等
- 限流降级,在秒杀这种流量比较集中并且流量特别大的 情况下,因为突发访问量特别大可能会导致系统支撑不 了。这个时候可以采用限流来限制访问量。当达到阀值 时,后续的请求被降级,比如进入排队页面,比如跳转 到错误页(活动太火爆,稍后重试等)
dubbo的降级方式: Mock 机制
首先我们在客户端添加一个TestMock类,实现HelloDubboService接口,配置文件主要内容如上,可以看到我这里的配置相对第一点客户端的配置添加了几个参数 timeout、failover、mock。这里主要是看mock和timeout配置,因为我们是要去验证mock机制,failover是集群容错的配置,配置好之后,先发布服务端的服务,然后运行客户端的App.java。在控制台我们可以看到打印结果,系统繁忙,这个结果说明,我们客户端远程调用服务超时等报错时,是走到了Mock里,此时我们可以做其它处理。
实现步骤如下:
1、在client端创建一个TestMock类,实现对应HelloDubboService的接口(需要对哪个接口进行 mock,就实现哪个), 名称必须以Mock结尾。
public class TestMock implements HelloDubboService {
@Override
public String helloDubbo(String requestMsg) {
return "系统繁忙!";
}
}
2、在client端的consumer.xml配置文件中,添加如下配置,增加一 个mock属性指向创建的TestMock,并模拟错误(设置 timeout),模拟超时异常(timeout设为1,)
<!--声明需要引用的服务接口,指定版本号-->
<dubbo:reference id="helloDubbo" interface="com.jvxb.test.dubbo.api.HelloDubboService"
version="1.0.0" cluster="failover" timeout="1"
mock="com.jvxb.test.dubboconsumer.consumer.TestMock"/>
3、运行测试代码即可访问到TestMock这个类。
4、然后我们再验证,将超时间加大,设置为100,再运行,此时就不会报错,
<!--声明需要引用的服务接口,指定版本号-->
<dubbo:reference id="helloDubbo" interface="com.jvxb.test.dubbo.api.HelloDubboService"
version="1.0.0" cluster="failover" timeout="100"
mock="com.jvxb.test.dubboconsumer.consumer.TestMock"/>
5、就会正常输出服务的返回值,输出结果如下
即dubbo通过Mock机制也是很方便地实现了服务降级。