Dubbo
dubbo是一个高性能的RPC(远程过程调用)框架
架构演变及dubbo基本使用
1 架构演变
1.1 单体架构
1.2 垂直架构
根据业务分类,相互之间不影响,耦合度低
1.3 分布式架构(SOA)
SOA即面向服务的架构
1.4 微服务架构
微服务架构是一种将单个应用程序作为独立服务开发的方法,可以通过自动部署机制进行独立部署。业务需要彻底的组件化和服务化
2 Dubbo架构与实战
2.1 Dubbo特点
参见 dubbo官网
优点
-
远程接口方法调用 (屏蔽方法具体实现)
-
智能容错,负载均衡
-
服务自动注册与发现(注册中心,可以自动感知服务上下线)
-
数据隔离,权限回收
缺点
-
粒度控制复杂
-
接口数量可能太多。建议以业务场景划分
-
调用链路长,服务质量不易监控
2.2 Dubbo服务治理
2.3 Dubbo处理流程
节点说明
节点 | 角色名称 |
---|---|
Provider | 服务提供方 |
Consumer | 远程调用服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务次数的监控中心 |
Container | 服务运行容器 负责启动 加载 运行服务提供者 |
调用关系说明:
-
虚线 代表异步调用 实线代表同步访问
-
蓝色虚线 是在启动时完成的功能
-
红色虚线 是程序运行中执行的功能
调用流程:
-
服务提供者在服务容器启动时 向注册中心 注册自己提供的服务
-
服务消费者在启动时 向注册中心订阅自己所需的服务
-
注册中心返回服务提供者地址列表给消费者 如果有变更 注册中心会基于长连接推送变更数据给消费者
-
服务消费者 从提供者地址列表中 基于软负载均衡算法 选一台提供者进行调用 如果调用失败 则重新选择一台
-
服务提供者和消费者 在内存中的调用次数 和 调用时间 定时每分钟发送给监控中心
2.4 Dubbo配置
2.4.1 maven 引入
2.4.1.1 用于一个工程
如filter
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
</dependencies>
2.4.1.2 用于父子工程
父工程:
<modelVersion>4.0.0</modelVersion>
<groupId>com.limeng</groupId>
<artifactId>dubbo2_project</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<dubbo.version>2.7.5</dubbo.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 日志配置 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
子工程
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
</dependency>
</dependencies>
2.4.1.3 和springboot整合
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.limeng</groupId>
<artifactId>dubbo1_web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dubbo1_web</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.4.2 注解方式
启动客户端时可能报错:
qos-server can not bind localhost:22222
需要在客户端配置文件中加入:
dubbo.application.qosEnable=true
dubbo.application.qosPort=33333
2, 创建公共接口
package com.limeng.service;
public interface HelloService {
public String sayHello(String name);
}
Provider
1, 实现接口方法
2, 编写Provider 属性配置文件dubbo-provider.properties
dubbo.application.name=provider_annotation
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=rmi
dubbo.protocol.port=20880
3,编写Config配置类
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@EnableDubbo(scanBasePackages = "com.limeng.service")
@PropertySource("classpath:/dubbo-provider.properties")
public class DubboProviderConfiguration {
}
4,启动引导类
public class DubboProviderMain {
public static void main(String[] args) throws IOException {
ApplicationContext context = new AnnotationConfigApplicationContext(DubboProviderConfiguration.class);
context.start();//可以没有
System.in.read();//防止程序停止
}
}
Consumer
1,Consumer配置文件 dubbo-consumer.properties
dubbo.application.name=consumer_annotation
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.consumer.timeout=3000
dubbo.application.qosEnable=true
dubbo.application.qosPort=33333
2,Consumer端代理类
import com.limeng.service.HelloService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Component;
@Component
public class AnnotationAction {
@Reference
private HelloService helloService;
public String doSayHello(String name){
return helloService.sayHello(name);
}
}
3,Consumer Config配置类
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@EnableDubbo(scanBasePackages = "com.limeng.config")
@PropertySource("classpath:/dubbo-consumer.properties")
@ComponentScan(value = {"com.limeng.config"})
public class DubboConsumerConfiguration {
}
4,Consumer启动引导类
import com.limeng.config.AnnotationAction;
import com.limeng.config.DubboConsumerConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.io.IOException;
public class DubboConsumerMain {
public static void main(String[] args) throws IOException {
ApplicationContext context = new AnnotationConfigApplicationContext(DubboConsumerConfiguration.class);
AnnotationAction action = (AnnotationAction) context.getBean("annotationAction");
while(true){//一直可以调用
System.in.read();
String msg = action.doSayHello("World");
System.out.println(msg+"");
}
}
}
2.4.3 XML方式
Provider
1,实现接口方法
2,编写Provider配置文件 provider.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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="dubbo_demo_provider_xml"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="dubbo" port="20890"/>
<bean id="helloService" class="com.limeng.dubbo_demo_xml.service.HelloXmlServiceImpl"/>
<dubbo:service interface="com.limeng.service.HelloXMLService" ref="helloService"/>
</beans>
3,Provider启动引导类
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("classpath:/provider.xml");
System.in.read(); //防止程序停止
}
Consumer
1,编写Consumer配置文件 consumer.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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="dubbo_demo_consumer_xml"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="10000"/>
<dubbo:reference id="helloService" interface="com.limeng.service.HelloXMLService" timeout="4000" retries="2" />
</beans>
2,Consumer启动引导类
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("classpath:/consumer.xml");
HelloXMLService service = (HelloXMLService) context.getBean("helloService");
String msg = service.sayHelloForXML("World");
System.out.println(msg);
}
2.5 Dubbo管理控制台
2.5.1 作用
主要包含:服务管理 、 路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能如我们在开发时,需要知道Zookeeper注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我们可以通过部署一个管理中心来实现。其实管理中心就是一个web应用,原来是war(2.6版本以前)包需要部署到tomcat即可。现在是jar包可以直接通过java命令运行。
2.5.2 控制台安装
-
从git 上下载项目 https://github.com/apache/dubbo-admin , 找master分支的
-
修改项目下的dubbo.properties文件
注意dubbo.registry.address对应的值需要对应当前使用的Zookeeper的ip地址和端口号
dubbo.registry.address=zookeeper://zk所在机器ip:zk端口
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
-
切换到项目所在的路径 使用mvn 打包
mvn package
--mvn clean package -Dmaven.test.skip=true
-
ava 命令运行
java -jar 对应的jar包
2.6 Dubbo配置项
2.6.1 dubbo:application
对应 org.apache.dubbo.config.ApplicationConfig, 代表当前应用的信息
-
name: 当前应用程序的名称,在dubbo-admin中我们也可以看到,这个代表这个应用名称。我们在真正时是时也会根据这个参数来进行聚合应用请求。
-
owner: 当前应用程序的负责人,可以通过这个负责人找到其相关的应用列表,用于快速定位到责任人。
-
qosEnable : 是否启动QoS 默认true
-
qosPort : 启动QoS绑定的端口 默认22222
-
qosAcceptForeignIp: 是否允许远程访问 默认是false
2.6.2 dubbo:registry
org.apache.dubbo.config.RegistryConfig, 代表该模块所使用的注册中心。一个模块中的服务可以将其注册到多个注册中心上,也可以注册到一个上。后面再service和reference也会引入这个注册中心。
-
id : 当前服务中provider或者consumer中存在多个注册中心时,则使用需要增加该配置。在一些公司,会通过业务线的不同选择不同的注册中心,所以一般都会配置该值。
-
address : 当前注册中心的访问地址。
-
protocol : 当前注册中心所使用的协议是什么。也可以直接在 address 中写入,比如使用zookeeper,就可以写成 zookeeper://xx.xx.xx.xx:2181
-
timeout : 当与注册中心不再同一个机房时,大多会把该参数延长。
2.6.3 dubbo:protocol
org.apache.dubbo.config.ProtocolConfig, 指定服务在进行数据传输所使用的协议。
-
id : 在大公司,可能因为各个部门技术栈不同,所以可能会选择使用不同的协议进行交互。这里 在多个协议使用时,需要指定。
-
name : 指定协议名称。默认使用 dubbo 。
2.6.4 dubbo:service
org.apache.dubbo.config.ServiceConfig, 用于指定当前需要对外暴露的服务信息,后面也会具体讲解。和 dubbo:reference 大致相同。
-
interface : 指定当前需要进行对外暴露的接口是什么。
-
ref : 具体实现对象的引用,一般我们在生产级别都是使用Spring去进行Bean托管的,所以这里面一般也指的是Spring中的BeanId。
-
version : 对外暴露的版本号。不同的版本号,消费者在消费的时候只会根据固定的版本号进行消费
2.6.5 dubbo:reference
org.apache.dubbo.config.ReferenceConfig, 消费者的配置,这里只做简单说明,后面会具体讲解。
-
id : 指定该Bean在注册到Spring中的id。
-
interface: 服务接口名
-
version : 指定当前服务版本,与服务提供者的版本一致。
-
registry : 指定所具体使用的注册中心地址。这里面也就是使用上面在 dubbo:registry 中所声明的id
2.6.6 dubbo:service和dubbo:reference详解
这两个在dubbo中是我们最为常用的部分,其中有一些我们必然会接触到的属性。并且这里会讲到一些设置上的使用方案。
-
mock: 用于在方法调用出现错误时,当做服务降级来统一对外返回结果,后面我们也会对这个方法做更多的介绍。
-
timeout: 用于指定当前方法或者接口中所有方法的超时时间。我们一般都会根据提供者的时长来具体规定。比如我们在进行第三方服务依赖时可能会对接口的时长做放宽,防止第三方服务不稳定导致服务受损。
-
check: 用于在启动时,检查生产者是否有该服务。我们一般都会将这个值设置为false,不让其进行检查。因为如果出现模块之间循环引用的话,那么则可能会出现相互依赖,都进行check的话,那么这两个服务永远也启动不起来。
retries: 用于指定当前服务在执行时出现错误或者超时时的重试机制。
-
注意提供者是否有幂等,否则可能出现数据一致性问题
-
注意提供者是否有类似缓存机制,如出现大面积错误时,可能因为不停重试导致雪崩
executes: 用于在提供者做配置,来确保最大的并行度。
-
可能导致集群功能无法充分利用或者堵塞
-
但是也可以启动部分对应用的保护功能
-
可以不做配置,结合后面的熔断限流使用
2.6.7 其它配置 参考官网
Dubbo高级实战
1 SPI
1.1 概念
是一种提供发现机制,目的是为了解耦。使第三方服务的装配控制逻辑与调用者的业务分开。
1.2 Dubbo SPI 使用 & 1.3 Adaptive
dubbo中大量使用了SPI
-
编写接口
package com.lagou.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("human")// 默认是调用human的实现类,如果4调用初URL不指定的话
public interface HelloService {
String sayHello();
@Adaptive
String sayHello(URL url);
}
-
实现类
package com.lagou.service.impl;
import com.lagou.service.HelloService;
import org.apache.dubbo.common.URL;
public class DogHelloService implements HelloService{
@Override
public String sayHello() {
return "wang wang";
}
@Override
public String sayHello(URL url) {
return "wang url";
}
}
package com.lagou.service.impl;
import com.lagou.service.HelloService;
import org.apache.dubbo.common.URL;
public class HumanHelloService implements HelloService{
@Override
public String sayHello() {
return "hello 你好";
}
@Override
public String sayHello(URL url) {
return "hello url";
}
}
-
SPI配置文件 com.lagou.service.HelloService, 在META-INF/dubbo 下
human=com.lagou.service.impl.HumanHelloService
dog=com.lagou.service.impl.DogHelloService
-
调用
1)
import com.lagou.service.HelloService;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.util.Set;
public class DubboSpiMain {
public static void main(String[] args) {
// 获取扩展加载器
ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
// **遍历所有的支持的扩展点** META-INF.dubbo
Set<String> extensions = extensionLoader.getSupportedExtensions();
for (String extension : extensions){
String result = extensionLoader.getExtension(extension).sayHello();
System.out.println(result);
}
}
}
结果:
2)Adaptive调用
import com.lagou.service.HelloService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class DubboAdaptiveMain {
public static void main(String[] args) {
URL url = URL.valueOf("test://localhost/hello**?hello.service=dog**");
HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class)
.getAdaptiveExtension();
String msg = adaptiveExtension.sayHello(url);//根据url自动找到扩展点
System.out.println(msg);
}
}
结果:
1.4 Dubbo Filter使用
-
编写接口
public interface HelloServiceB {
public String callB(String name);
}
-
编写实现类
import com.limeng.dubbo1.service.HelloServiceB;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.RpcContext;
@Service
public class HelloServiceBImpl implements HelloServiceB {
@Override
public String callB(String name) {
String ip = RpcContext.*getContext*().getAttachment("ip");
System.*out*.println("===============>>>这是HelloServiceB, ip是"+ip);
return null;
}
}
-
在META-INF/dubbo下创建接口全限定名的文件 org.apache.dubbo.rpc.Filter
transportIPFilter=com.limeng.dubbo1.filter.TransportIPFilter
package com.limeng.dubbo1.filter;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
@Activate(group = {CommonConstants.*CONSUMER*})
public class TransportIPFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String ip = invoker.getUrl().getIp();
RpcContext.*getContext*().setAttachment("ip", ip);
return invoker.invoke(invocation);
}
}
-
调用该接口
import com.limeng.dubbo1.service.HelloServiceB;
import com.limeng.dubbo1.service.HelloServiceC;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
@Service
public class HelloServiceImpl implements HelloService , ApplicationContextAware {
private ApplicationContext context;
@Override
public String hello(String name) {
HelloServiceB serviceB = (HelloServiceB)context.getBean("helloServiceB");
serviceB.callB(name);
return "hello, " +name;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = new ClassPathXmlApplicationContext("classpath:/consumer.xml");
}
}
1.5 Dubbo调用时拦截操作
Dubbo的Filter机制,是专门为服务提供方和服务消费方调用过程进行拦截设计的,每次远程方法执行,该拦截都会被执行。这样就为开发者提供了非常方便的扩展性,比如为dubbo接口实现ip白名单功能、监控功能 、日志记录等。
-
实现 org.apache.dubbo.rpc.Filter 接口
-
使用 org.apache.dubbo.common.extension.Activate 接口进行对类进行注册 通过group 可以指定生产端 消费端 如:
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
@Activate(group = {CommonConstants.CONSUMER)
-
Filter中业务代码的实现
-
在 META-INF.dubbo 中新建 org.apache.dubbo.rpc.Filter 文件,并将当前类的全名写入
timerFilter=包名.过滤器的名字
-
注意:一般类似于这样的功能都是单独开发依赖的(单独一个工程,引入dubbo的依赖,见2.4.1.1),所以在使用的项目中只需要引入依赖,在调用接口时,该方法便会自动拦截。 详见1.4 。
2 负载均衡策略
2.1 负载均衡策略
详见 > dubbo官网负载均衡
-
随机
-
轮询
-
最小活跃数
-
一致性Hash
2.2 基本配置
服务端服务级别
<dubbo:service interface="aaa" loadbalance="roundrobin" />
服务端方法级别
<dubbo:service interface="aa">
<dubbo:method name="aa" loadbalance="roundrobin"/>
</dubbo:service>
2.3 自定义负载均衡器
使用Dubbo SPI机制实现,实现 org.apache.dubbo.rpc.cluster.LoadBalance
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import java.util.List;
public class OnlyFirstLoadbalancer implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> list, URL url, Invocation invocation) throws RpcException {
// 所有的服务提供者 按照IP + 端口排序 选择第一个
return list.stream().sorted((i1,i2)->{
final int ipCompare = i1.getUrl().getIp().compareTo(i2.getUrl().getIp());
if(ipCompare == 0){
return Integer.*compare*(i1.getUrl().getPort(),i2.getUrl().getPort());
}
return ipCompare;
}).findFirst().get();
}
}
3 异步调用
Dubbo不只提供了堵塞式的的同步调用,同时提供了异步调用的方式。这种方式主要应用于提供者接口响应耗时明显,消费者端可以利用调用接口的时间去做一些其他的接口调用,利用 Future 模式来异步等待和获取结果即可。这种方式可以大大的提升消费者端的利用率。 目前这种方式可以通过XML的方式进行引入。
3.1 实现
-
可以通过改变消费端的超时时间 通过timeout属性设置即可单位毫秒。async=true
<dubbo:reference id="helloService" interface="com.lagou.service.HelloService"> <dubbo:method name="sayHello" async="true" /> </dubbo:reference>
-
测试,我们休眠100毫秒,然后再去进行获取结果。方法在同步调用时的返回值是空,我们可以通过 RpcContext.getContext().getFuture() 来进行获取Future对象来进行后续的结果等待操作。
String hello = service.sayHello("World", 100);//休眠100ms再执行方法 Future<Object> future = RpcContext.getContext().getFuture();//从RpcContext中拿到结果 System.out.println(future.get());
3.2 特殊说明
需要特别说明的是,该方式的使用,请确保dubbo的版本在2.5.4及以后的版本使用。 原因在于在2.5.3及之前的版本使用的时候,会出现异步状态传递问题。 比如我们的服务调用关系是 A -> B -> C , 这时候如果A向B发起了异步请求,在错误的版本时,B向C发起的请求也会连带的产生异步请求。这是因为在底层实现层面,他是通过 RPCContext 中的attachment 实现的。在A向B发起异步请求时,会在 attachment 中增加一个异步标示字段来表明异步等待结果。B在接受到A中的请求时,会通过该字段来判断是否是异步处理。但是由于值传递问题,B向C发起时同样会将该值进行传递,导致C误以为需要异步结果,导致返回空。这个问题在2.5.4及以后的版本进行了修正。
4.线程池
4.1 Dubbo已有线程池
dubbo在使用时,都是通过创建真实的业务线程池进行操作的。目前已知的线程池模型有两个和java中的相互对应:
-
fix: 表示创建固定大小的线程池。也是Dubbo默认的使用方式,默认创建的执行线程数为200,并 且是没有任何等待队列的。所以再极端的情况下可能会存在问题,比如某个操作大量执行时,可能 存在堵塞的情况。后面也会讲相关的处理办法。
-
cache: 创建非固定大小的线程池,当线程不足时,会自动创建新的线程。但是使用这种的时候需 要注意,如果突然有高TPS的请求过来,方法没有及时完成,则会造成大量的线程创建,对系统的 CPU和负载都是压力,执行越多反而会拖慢整个系统。
4.2 自定义线程池
在真实的使用过程中可能会因为使用fix模式的线程池,导致具体某些业务场景因为线程池中的线程数量不足而产生错误,而很多业务研发是对这些无感知的,只有当出现错误的时候才会去查看告警或者通过客户反馈出现严重的问题才去查看,结果发现是线程池满了。所以可以在创建线程池的时,通过某些手段对这个线程进行监控,这样就可以进行及时的扩缩容机器或者告警。下面的这个程序就是这样子的,会在创建线程池后进行对其监控,并且及时作出相应处理。
(1)线程池实现, 这里主要是基于对 FixedThreadPool 中的实现做扩展出线程监控的部分
public class WatchingThreadPool extends FixedThreadPool implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(WatchingThreadPool.class);
private static final double ALARM_PERCENT = 0.90;
private final Map<URL, ThreadPoolExecutor> THREAD_POOLS = newConcurrentHashMap<>();
public WatchingThreadPool() {
// 每隔3秒打印线程使用情况
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(this, 1,3, TimeUnit.SECONDS);
}
@Override
public Executor getExecutor(URL url) {
// 从父类中创建线程池
final Executor executor = super.getExecutor(url);
if (executor instanceof ThreadPoolExecutor) {
THREAD_POOLS.put(url, ((ThreadPoolExecutor) executor));
}
return executor;
}
@Override
public void run() {
// 遍历线程池,如果超出指定的部分,进行操作,比如接入公司的告警系统或者短信平台
for (Map.Entry<URL, ThreadPoolExecutor> entry : THREAD_POOLS.entrySet()){
final URL url = entry.getKey();
final ThreadPoolExecutor executor = entry.getValue();
// 当前执行中的线程数
final int activeCount = executor.getActiveCount();
// 总计线程数
final int poolSize = executor.getCorePoolSize();
double used = (double)activeCount / poolSize;
final int usedNum = (int) (used * 100);
LOGGER.info("线程池执行状态:[{}/{}]:{}%", activeCount, poolSize,usedNum);
if (used >= ALARM_PERCENT) {
LOGGER.error("超出警戒值!host:{}, 当前已使用量:{}%, URL:{}",url.getIp(), usedNum, url);
}
}
}
}
(2)SPI声明,创建文件 META-INF/dubbo/org.apache.dubbo.common.threadpool.ThreadPool
watching=包名.线程池名
(3)在服务提供方项目引入该依赖 (4)在服务提供方项目中设置使用该线程池生成器 (5)接下来需要做的就是模拟整个流程,因为该线程当前是每1秒抓一次数据,所以我们需要对该方法的提供者超过1秒的时间(比如这里用休眠 Thread.sleep ),消费者则需要启动多个线程来并行执行,来模拟整个并发情况。 (6)在调用方则尝试简单通过for循环启动多个线程来执行 查看服务提供方的监控情况
5 路由
路由是决定一次请求中需要发往目标机器的重要判断,通过对其控制可以决定请求的目标机器。我们可以通过创建这样的规则来决定一个请求会交给哪些服务器去处理。
5.1 路由规则
(1)提供两个提供者(一台本机作为提供者,一台为其他的服务器),每个提供者会在调用时可以返回不同的信息 以区分提供者。 (2)针对于消费者,我们这里通过一个死循环,每次等待用户输入,再进行调用,来模拟真实的请求情况。通过调用的返回值 确认具体的提供者。 (3)我们通过ipconfig来查询到我们的IP地址,并且单独启动一个客户端,来进行如下配置(这里假设我们希望隔离掉本机的请求,都发送到另外一台机器上)。
public class DubboRouterMain {
public static void main(String[] args) {
RegistryFactory registryFactory =ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181"));
registry.register(URL.valueOf("condition://0.0.0.0/com.lagou.service.HelloService?category=routers&force=true&dynamic=true&rule=" + URL.encode("=> host != 你的机器ip不能是127.0.0.1")));
}
}
(4)通过这个程序执行后,我们就通过消费端不停的发起请求,看到真实的请求都发到了除去本机以 外的另外一台机器上。
5.2 路由规则详解
通过上面的程序,我们实际本质上就是通过在zookeeper中保存一个节点数据,来记录路由规则。消费者会通过监听这个服务的路径,来感知整个服务的路由规则配置,然后进行适配。这里主要介绍路由配置的参数。具体请见
[参考文档] http://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule-deprecated.html
-
route:// 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
-
0.0.0.0 表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填。
-
com.lagou.service.HelloService 表示只对指定服务生效,必填。
-
category=routers 表示该数据为动态配置类型,必填。
-
dynamic : 是否为持久数据,当指定服务重启时是否继续生效。必填。
-
runtime : 是否在设置规则时自动缓存规则,如果设置为true则会影响部分性能。
-
rule : 是整个路由最关键的配置,用于配置路由规则。 ... => ... 在这里 => 前面的就是表示消费者方的匹配规则,可以不填(代表全部)。 => 后方则必须填写,表示当请求过来时,如果选择提供者的配置。官方这块儿也给出了详细的示例,可以按照那里来讲。 其中使用最多的便是 host 参数。 必填。
6、服务动态降级
6.1 什么是服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略的降低服务级别,以释放服务器资源,保证核心任务的正常运行。
6.2 为什么要服务降级
而为什么要使用服务降级,这是防止分布式服务发生雪崩效应,什么是雪崩?就是蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪崩。
6.3 dubbo 服务降级实现方式
第一种 在 dubbo 管理控制台配置服务降级
屏蔽和容错
-
mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可
用时对调用方的影响。
-
mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳 定时对调用方的影响。
第二种 指定返回简单值或者null
<dubbo:reference id="xxService" check="false" interface="com.xx.XxService" timeout="3000" mock="return null" />
<dubbo:reference id="xxService2" check="false" interface="com.xx.XxService2" timeout="3000" mock="return 1234" />
如果是标注 则使用@Reference(mock="return null") @Reference(mock="return 简单值") 也支持 @Reference(mock="force:return null")
第三种 使用java代码 动态写入配置中心
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://IP:端口"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService? category=configurators&dynamic=false&application=foo&mock=force:return+null"));