不多BB,开门见山
这是阿里巴巴的官方中文文档:https://dubbo.gitbooks.io/dubbo-user-book/content/preface/background.html
节点说明
image.png调用关系说明
1、Container负责启动,加载,运行Provider。
2、Provider在启动时,向注册中心注册自己提供的服务。
3、Consumer在启动时,向注册中心订阅自己所需的服务。
4、注册中心返回服务提供者地址列表给Consumer,如果有变更,注册中心将基于长连接推送变更数据给Consumer。
5、Consumer,从Provider地址列表中,基于软负载均衡算法,选一台Provider进行调用,如果调用失败,再选另一台调用。
6、Consumer和Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到Monitor。
Dubbo的特性
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。
连通性
- 注册中心负责服务地址的注册与查找,相当于目录服务,Provider和Consumer只在启动时与注册中心交互,注册中心不转发请求,压力较小
- 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
- Provider向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
- Consumer向注册中心获取Provider地址列表,并根据负载算法直接调用Provider,同时汇报调用时间到监控中心,此时间包含网络开销
- 注册中心,Provider,Consumer三者之间均为长连接,监控中心除外
- 注册中心通过长连接感知Provider的存在,Provider宕机,注册中心将立即推送事件通知Consumer
- 注册中心和监控中心全部宕机,不影响已运行的Provider和Consumer,Consumer在本地缓存了Provider列表
- 注册中心和监控中心都是可选的,Consumer可以直连Provider
健状性
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- Provider无状态,任意一台宕掉后,不影响使用
- Provider全部宕掉后,Consumer应用将无法使用,并无限次重连等待Provider恢复
伸缩性
- 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
- Provider无状态,可动态增加机器部署实例,注册中心将推送新的Provider信息给Consumer
演示:
Spring版本的配置
image.pngProvider
image.png<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 使用zookeeper进行注册中心化 -->
<!--
protocol:协议,这里是zookeeper协议
-->
<dubbo:registry protocol="zookeeper"
address="192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181"/>
<!-- 和本地bean一样实现服务 -->
<bean id="quickStartService" class="com.learn.test.dubbo.quickstart.QuickStartServiceImpl" />
<!-- 声明需要暴露的服务接口 -->
<!-- 声明需要暴露的服务接口 写操作可以设置
retries :被调用接口超时时重复调用次数,默认是三次
interface:接口
ref:实现类
group:当接口多实现时,可区分具体是哪个实现类
loadbalance:负载均衡算法
async:异步调用
register:需注册到哪一个注册中心,如果缺省,则默认向所有注册中心注册
-->
<dubbo:service
timeout="10000"
async="true"
interface="com.learn.test.dubbo.ServiceAPI"
ref="quickStartService"/>
<!-- N/A代表不需要注册中,采用直连方式 -->
<!-- registry="N/A" -->
</beans>
Consumer
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="consumer-of-helloworld-app" />
<!-- 使用zookeeper进行注册中心化 -->
<dubbo:registry protocol="zookeeper"
address="192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181"/>
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference
id="consumerService"
interface="com.learn.test.dubbo.ServiceAPI"/>
</beans>
调用
public class ConsumerClient {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-hello-consumer.xml");
context.start();
while (true){
Scanner scanner = new Scanner(System.in);
String message = scanner.next();
// 获取接口
ServiceAPI serviceAPI = (ServiceAPI)context.getBean("consumerService");
// 测试异步调用
long beginTime = System.currentTimeMillis();
serviceAPI.sendMessage(message);
}
}
}
SpringBoot版本的配置
Provider
image.pngspring:
application:
name: dubbo-spring-boot-starter
dubbo:
server: true
registry:
protocol: zookeeper
address: 192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181
提供的接口
import com.alibaba.dubbo.config.annotation.Service;
@Component
@Service(interfaceClass = ServiceAPI.class)
public class QuickstartServiceImpl implements ServiceAPI {
@Override
public String sendMessage(String message) {
System.out.println(message);
return "quickstart-provider-message="+message;
}
}
启动
@SpringBootApplication
@EnableDubboConfiguration
public class PrivoderApplication {
public static void main(String[] args) {
SpringApplication.run(PrivoderApplication.class, args);
}
}
Consumer
image.pngspring:
application:
name: dubbo-spring-boot-starter
dubbo:
server: true
registry:
protocol: zookeeper
address: 192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181
调用的接口
@Component
public class QuickstartConsumer {
@Reference(interfaceClass = ServiceAPI.class)
ServiceAPI serviceAPI;
public void sendMessage(String message){
System.out.println(serviceAPI.sendMessage(message));
}
}
启动
@SpringBootApplication
@EnableDubboConfiguration
public class ConsumerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run =
SpringApplication.run(ConsumerApplication.class, args);
QuickstartConsumer quickstartConsumer = (QuickstartConsumer)run.getBean("quickstartConsumer");
quickstartConsumer.sendMessage("dubbo是好东私");
}
}
启动项目就可以看到注册中心相应的节点了
image.png启动检查check
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动的情况下可以关闭了。
另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。
spring
<dubbo:reference check="false"
id="consumerService"
interface="com.learn.test.dubbo.ServiceAPI"/>
或者是springboot
@Reference(interfaceClass = ServiceAPI.class,check = false)
ServiceAPI serviceAPI;
负载均衡策略
在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 Random 随机调用。
Random
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin
轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash
一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
配置
配置Spring版的:
Provider
<dubbo:service interface="..." loadbalance="roundrobin" />
Consumer
<dubbo:reference interface="..." loadbalance="roundrobin" />
Provider
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
Consumer
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
配置SpringBoot版
Consumer
@Reference(interfaceClass = ServiceAPI.class,loadbalance = "roundrobin")
ServiceAPI serviceAPI;
协议
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。
SpringBoot版
配置
spring:
application:
name: dubbo-spring-boot-starter
dubbo:
server: true
registry:
protocol: rmi/*协议在这里改*/
address: 192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181
本地存根
远程服务后,客户端通常只是调用一下生产端的接口,而接口实现全都在生产者端,但生产者端有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后服务降级等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
本地存根需要Stub类的有参构造函数,把Provider端提供的接口传入,本地存根的原理就是强制让Consumer先走Stub类对应的实现接口进行逻辑处理
image.png演示
Provider
创建Stub类
public class Mystub implements ServiceAPI {
private ServiceAPI serviceAPI;
public Mystub(ServiceAPI serviceAPI) {
this.serviceAPI = serviceAPI;
}
@Override
public String sendMessage(String message) {
if ("tzb".equals(message)){
return this.serviceAPI.sendMessage(message);
}else {
System.out.println("符合规则,降级");
return "符合规则,降级";
}
}
}
public class QuickStartServiceImpl implements ServiceAPI {
@Override
public String sendMessage(String message) {
System.out.println("message="+message);
return "quickstart-provider-message="+message;
}
}
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20883" />
<!-- 使用zookeeper进行注册中心化 -->
<dubbo:registry protocol="zookeeper"
address="192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181"/>
<!-- 和本地bean一样实现服务 -->
<bean id="quickStartService" class="com.learn.test.dubbo.quickstart.QuickStartServiceImpl" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service
stub="com.learn.test.dubbo.stub.Mystub"
interface="com.learn.test.dubbo.ServiceAPI"
ref="quickStartService"
/>
Consumer
在消费者端需要从生产者端拷贝一份Stub类,然后修改一下逻辑,其他不变
public class Mystub implements ServiceAPI {
private ServiceAPI serviceAPI;
public Mystub(ServiceAPI serviceAPI) {
this.serviceAPI = serviceAPI;
}
@Override
public String sendMessage(String message) {
if ("tzb".equals(message)){
System.out.println("这里是Consumer端");
return this.serviceAPI.sendMessage(message);
}else {
System.out.println("这里是Consumer端");
System.out.println("符合规则,降级或者是参数验证");
}
return null;
}
}
我们看到本地存根,虽然是远程调用dubbo的接口,但是先走的是消费者的Stub类,通过Stub类进行一些逻辑处理在调用远程调用dubbo的接口,起到了
consumer image.png本地伪装(服务降级神器)
这个是本地存根的子集,调用这个需要Mock类的空参构造函数,如果Provider的接口出现异常就会走Consumer端的Mock类,本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
本地伪装的原理就是在发生Provider的接口发生异常时,强制使Consumer端的走Mock类对应的实现接口,从而达到服务器的降级
本地伪装和本地存根不同的地方就在于,本地存根是会拦截所有的对应接口对应的方法,而本地伪装只是在异常时才去拦截对应接口对应的方法
演示
Provider
mock类(说白了就是服务降级的处理类)
//关于ServiceAPI的服务降级
public class Mymock implements ServiceAPI {
//ServiceAPI类的sendMessage方法异常或者超时就触发服务降级
@Override
public String sendMessage(String message) {
System.out.println("服务降级被触发了");
return "mock服务降级";
}
@Override
public String sendMessage02(String message) {
return null;
}
}
我给被调用的方法设置一个超时,2秒肯定超时了的,看看超时会不会触发降级
public class QuickStartServiceImpl implements ServiceAPI {
@Override
public String sendMessage(String message) {
System.out.println("message="+message);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "quickstart-provider-message="+message;
}
........
}
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20883" />
<!-- 使用zookeeper进行注册中心化 -->
<dubbo:registry protocol="zookeeper"
address="192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181"/>
<!-- 和本地bean一样实现服务 -->
<bean id="quickStartService" class="com.learn.test.dubbo.quickstart.QuickStartServiceImpl" />
<!-- 配置服务降级 -->
<dubbo:service
mock="com.learn.test.dubbo.mock.Mymock"
interface="com.learn.test.dubbo.ServiceAPI"
ref="quickStartService"
/>
Consumer
在消费者端需要从生产者端拷贝一份Mock类
public class Mymock implements ServiceAPI {
@Override
public String sendMessage(String message) {
System.out.println("这里是Consumer端");
System.out.println("Provider端出现异常,降级!!!");
return message;
}
}
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="consumer-of-helloworld-app" />
<!-- 使用zookeeper进行注册中心化 -->
<dubbo:registry protocol="zookeeper"
address="192.168.200.131:2181,192.168.200.130:2181,192.168.200.132:2181"/>
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference
id="consumerService"
interface="com.learn.test.dubbo.ServiceAPI"
mock="com.learn.test.dubbo.mock.Mymock"
/>
image.png
网友评论