为什么要使用dubbo
- 服务治理框架
- 服务的监控
- 服务的注册发现
- 服务的通信
- 服务的容错
- 服务的负载均衡
Dubbo整合spring来使用
首先我们创建两个module,一个是spring-dubbo和dubbo-api , dubbo-api主要是存放需要发布出去的接口,spring-dubbo包含了:
- spring-dubbo-client #消费者
- spring-dubbo-server-nacos #把自己的服务注册到(nacos)注册中心中。
整体的pom.xml依赖引入
spring-dubbo的pom.xml如下,因为是spring-dubbo-client, spring-dubbo-server-nacos的父类,所以这两个模块也是引入了这些依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.8</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 注册中心nacos的相关配置-->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.8</version>
</dependency>
<!-- dubbo的相关配置-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<!-- 接口model的引入-->
<dependency>
<groupId>com.onion</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
接口模块
首先先看一下dubbo-api模块,主要是提供了两个接口是IUserService,IOrderService
/**
* 配合注册中心使用的一个接口
* @Author: gyc
* @Date: 2020/10/10 17:38
*/
public interface IUserService {
String getUser(String userId);
String loginUser(String userId);
}
提供者模块
在spring-dubbo-server-nacos中,实现IUserService的接口
/**
* @Author: gyc
* @Date: 2020/10/10 17:47
*/
public class UserService implements IUserService {
public String getUser(String userId) {
return "getUser: " + userId;
}
public String loginUser(String userId) {
return "loginUser: " + userId;
}
}
测试demo类:
/**
* @Author: gyc
* @Date: 2020/10/12 9:10
*/
public class App {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
context.start();
System.in.read(); // 按任意键退出
}
}
dubbo提供者的配置文件
<?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-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="spring-dubbo-server-nacos" />
<!-- 可以使用N/A来直连-->
<!--<dubbo:registry address="N/A" />-->
<!-- 使用nacos作为注册中心 -->
<dubbo:registry address="nacos://127.0.0.1:8848" />
<!-- 用dubbo协议,port为-1的时候,从20880开始递增1,直到找到没有冲突的端口 -->
<dubbo:protocol name="dubbo" port="-1" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.onion.service.IUserService" ref="userService" />
<!-- 和本地bean一样实现服务 -->
<bean id="userService" class="com.onion.service.UserService" />
</beans>
注意:如果提供者服务不注册到注册中心的话,是需要配置<dubbo:registry address="N/A" />,把注册中心的地址设为N/A,不然的话会报错
消费者模块
消费者的测试用例:
/**
* @author gyc
* @date 2020/10/11
*/
public class App {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"META-INF/spring/dubbo-demo-consumer.xml"});
context.start();
//利用nacos注册中心调用userService
IUserService userService = (IUserService)context.getBean("userService"); // 获取远程服务代理
String hello = userService.getUser("admn"); // 执行远程方法
System.out.println( hello ); // 显示调用结果
//利用直接连接暴露的服务
// IOrderService orderService = (IOrderService)context.getBean("orderService"); // 获取远程服务代理
// String orderId = orderService.getOrderById("orderId"); // 执行远程方法
// System.out.println( orderId ); // 显示调用结果
System.in.read(); // 按任意键退出
}
}
消费者的配置文件:
<?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-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="spring-dubbo-client" />
<!-- 以nacos为注册中心-->
<dubbo:registry address="nacos://127.0.0.1:8848" />
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="userService" interface="com.onion.service.IUserService" />
<!-- 可以通过url来直接指定服务提供者的地址 -->
<dubbo:reference id="orderService" interface="com.onion.service.IOrderService" url="dubbo://127.0.0.1:20881/com.onion.service.IOrderService" />
</beans>
这里我们可以看到服务提供者的地址格式为:
dubbo://127.0.0.1:20881/com.onion.service.IOrderService
http : // 服务器的IP: 容器的端口/ mapping
总结
dubbo依赖主要是:
- dubbo
- nacos-client
Dubbo整合springboot
首先我们创建两个module,一个是spring-boot-dubbo和dubbo-api ,跟上面Dubbo整合spring的一样,spring-boot-dubbo包含了:
- spring-boot-dubbo-consumer
- spring-boot-dubbo-provider
整体的pom.xml引入
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
<dubbo.version>2.7.8</dubbo.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.onion</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
接口模块
接口模块,跟上面Dubbo整合spring的一样
服务提供者模块
spring-boot-dubbo-provider 的实现类如下:
@DubboService //提供IUserService这个接口的服务
public class UserService implements IUserService {
public String getUser(String userId) {
return "spring boot getUserId:" + userId;
}
public String loginUser(String userId) {
return "spring boot loginUser:" + userId;
}
}
测试类启动:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
application.yml的配置:
dubbo:
application:
name: spring-boot-dubbo-provider
protocol:
name: dubbo
port: -1
registry:
address: nacos://127.0.0.1:8848
scan:
base-packages: com.onion.service
服务消费者模块
我们使用web方式来测试,引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
constroller:
/**
* @author gyc
* @date 2020/10/12
*/
@RestController
@RequestMapping("/user")
public class UserController {
@DubboReference //注入服务提供者
private IUserService userService;
@GetMapping("/getUser")
String getUser(@RequestParam("userId") String userId){
return userService.getUser(userId);
}
@PostMapping("/loginUser")
String loginUser(@RequestParam("userId") String userId){
return userService.loginUser(userId);
}
}
application.yml
dubbo:
application:
name: spring-boot-dubbo-consumer
protocol:
name: dubbo
port: -1
registry:
address: nacos://127.0.0.1:8848
server:
port: 9001
总结
依赖的话主要是:
- dubbo-spring-boot-starter
- nacos-client
配置的参数跟dubbo整合spring一样的,只不过这里是使用application来配置而已
Dubbo整合springcloud
首先我们创建两个module,一个是spring-cloud-dubbo和dubbo-api ,dubbo-api跟上面Dubbo整合spring的一样,spring-cloud-dubbo包含了:
- spring-cloud-dubbo-consumer
- spring-cloud-dubbo-provider
整体的pom.xml引入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.onion</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
如果报httpclient依赖问题可以加上一下依赖: (因为我有一台电脑测试的时候就,出现了说缺少httpclient依赖)
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
</dependency>
接口模块
接口模块,跟上面Dubbo整合spring的一样
服务提供者模块
spring-cloud-dubbo-provider 的实现类如下:
/**
* @author gyc
* @date 2020/10/13
*/
@DubboService
public class UserService implements IUserService {
public String getUser(String userId) {
return "spring cloud getUserId:" + userId;
}
public String loginUser(String userId) {
return "spring cloud loginUser:" + userId;
}
}
启动类配置:
/**
* @Author: gyc
* @Date: 2020/10/12 15:28
*/
@SpringBootApplication
@EnableDiscoveryClient //注册到注册中心的开关
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class,args);
}
}
applicaiton.yml配置文件:
spring:
cloud:
nacos:
discovery:
register-enabled: true
server-addr: 127.0.0.1:8848
application:
name: spring-cloud-dubbo-provider
dubbo:
application:
name: spring-cloud-dubbo-provider
protocol:
name: dubbo #协议名字
port: -1
scan:
base-packages: com.onion.service
dubbo.scan.base-packages
: 指定 Dubbo 服务实现类的扫描基准包dubbo.protocol
: Dubbo 服务暴露的协议配置,其中子属性name
为协议名称,port
为协议端口( -1 表示自增端口,从 20880 开始)spring.cloud.nacos.discovery
: Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
服务消费者模块
我们使用web方式来测试,引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
controller类:
@RestController
@RequestMapping("/user")
public class UserController {
@DubboReference
private IUserService userService;
@GetMapping("/getUser")
String getUser(@RequestParam("userId") String userId){
return userService.getUser(userId);
}
@PostMapping("/loginUser")
String loginUser(@RequestParam("userId") String userId){
return userService.loginUser(userId);
}
}
启动类:
/**
* @Author: gyc
* @Date: 2020/10/13 10:45
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
application.yml配置文件
spring:
application:
name: spring-cloud-dubbo-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
dubbo:
application:
name: spring-cloud-dubbo-consumer
protocol:
name: dubbo
port: -1
server:
port: 9101
总结
- 对于依赖的话,dubbo和nacos的主要依赖如下:
- spring-cloud-starter-dubbo
- spring-cloud-starter-alibaba-nacos-discovery
- 启动类需要,添加@EnableDiscoveryClient注解
- application.yml 使用spring.cloud.nacos.discovery指定注册中心的地址, 因为和spring-cloud整合了,所以还是通过spring-cloud注册到注册中心的步骤来做。
Dubbo支持多传输协议
使用Rest协议(tomcat作为嵌入式的servlet)和Dubbo协议共用。这个案例是结合spring-boot来的
这里主要有三个模块
- dubbo-rest-provider 服务的提供者
- dubbo-rest-api 提供rest-api接口 接口类为
ISayHelloService
- dubbo-api 提供dubbo接口,接口类为
UserService
依赖引入:
Rest(servlet嵌入式的依赖)
<!-- dubbo和spring boot和nacos 所需要的依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.onion</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.2</version>
</dependency>
<!-- rest 所需要的依赖 -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>${resteasy_version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>${resteasy_version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation_version}</version>
</dependency>
<!-- tomcat作为servlet的依赖 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.31</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>8.5.2</version>
</dependency>
注意: dubbo如果使用rest协议,默认使用jetty的。想用jetty作为嵌入式的servlet的话需要加入以下的依赖
<!-- jetty作为servlet的依赖 -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
接口模块:
定义一个restApi的接口
@Path("/")
public interface ISayHelloService {
@GET
@Path("/say")
public String say();
}
服务提供者模块
application.yml配置文件
dubbo:
application:
name: spring-boot-dubbo-provider
registry:
address: nacos://127.0.0.1:8848
scan:
base-packages: com.onion.service
protocols:
rest:
name: rest
port: 5566
server: tomcat #如果是jetty的话,这里填jetty
dubbo:
name: dubbo
port: -1
- dubbo.protocols 可以配置多个协议
实现类:
@DubboService(protocol = "rest")
public class SayHelloService implements ISayHelloService {
public String say() {
return "say ge hi";
}
}
@DubboService(protocol = "dubbo")
public class UserService implements IUserService {
public String getUser(String userId) {java
return "spring boot getUserId:" + userId;
}
public String loginUser(String userId) {
return "spring boot loginUser:" + userId;
}
}
这里可以通过@DubboService(protocol = "")
来指定传输协议。
现象展示
可以使用浏览器调用rest协议的信息,地址是:http://127.0.0.1:5566/say
查看nacos中注册上来的信息
dubbo:
rest:
可以看到protocol是不同的一个是dubbo,一个是rest,而且rest协议还是告诉你用的sever是什么,上图就是server=tomcat
参考文档:
Dubbo的负载均衡(引用官网的解析)
引用官网的地址是:
http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html
RandomLoadBalance(加权随机算法)
RandomLoadBalance 是加权随机算法的具体实现,它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被选中的次数比例接近其权重比例。比如,经过一万次选择后,服务器 A 被选中的次数大约为5000次,服务器 B 被选中的次数约为3000次,服务器 C 被选中的次数约为2000次。
缺点:
当然 RandomLoadBalance 也存在一定的缺点,当调用次数比较少时,Random 产生的随机数可能会比较集中,此时多数请求会落到同一台服务器上。这个缺点并不是很严重,多数情况下可以忽略。RandomLoadBalance 是一个简单,高效的负载均衡实现,因此 Dubbo 选择它作为缺省实现。
LeastActiveLoadBalance(最小活跃数负载均衡)
LeastActiveLoadBalance 翻译过来是最小活跃数负载均衡。活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。在具体实现中,每个服务提供者对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最小活跃数负载均衡算法的基本思想。除了最小活跃数,LeastActiveLoadBalance 在实现上还引入了权重值。所以准确的来说,LeastActiveLoadBalance 是基于加权最小活跃数算法实现的。举个例子说明一下,在一个服务提供者集群中,有两个性能优异的服务提供者。某一时刻它们的活跃数相同,此时 Dubbo 会根据它们的权重去分配请求,权重越大,获取到新请求的概率就越大。如果两个服务提供者权重相同,此时随机选择一个即可
ConsistentHashLoadBalance(一致性 hash 算法)
一致性 hash 算法由麻省理工学院的 Karger 及其合作者于1997年提出的,算法提出之初是用于大规模缓存系统的负载均衡。它的工作过程是这样的,首先根据 ip 或者其他的信息为缓存节点生成一个 hash,并将这个 hash 投射到 [0, 232 - 1] 的圆环上。当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。大致效果如下图所示,每个缓存节点在圆环上占据一个位置。如果缓存项的 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项。比如下面绿色点对应的缓存项将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项最终会存储到 cache-4 节点中。
下面来看看一致性 hash 在 Dubbo 中的应用。我们把上图的缓存节点替换成 Dubbo 的服务提供者,于是得到了下图:
这里相同颜色的节点均属于同一个服务提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。比如:
如上,由于 Invoker-1 和 Invoker-2 在圆环上分布不均,导致系统中75%的请求都会落到 Invoker-1 上,只有 25% 的请求会落到 Invoker-2 上。解决这个问题办法是引入虚拟节点,通过虚拟节点均衡各个节点的请求量。
RoundRobinLoadBalance(加权轮询负载均衡)
本节,我们来看一下 Dubbo 中加权轮询负载均衡的实现 RoundRobinLoadBalance。在详细分析源码前,我们先来了解一下什么是加权轮询。这里从最简单的轮询开始讲起,所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。我们将第一个请求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给服务器 A。这个过程就叫做轮询。轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下。但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。
集群容错
简介
为了避免单点故障,现在的应用通常至少会部署在两台服务器上。对于一些负载比较高的服务,会部署更多的服务器。这样,在同一环境下的服务提供者数量会大于1。对于服务消费者来说,同一环境下出现了多个服务提供者。这时会出现一个问题,服务消费者需要决定选择哪个服务提供者进行调用。另外服务调用失败时的处理措施也是需要考虑的,是重试呢,还是抛出异常,亦或是只打印异常等。为了处理这些问题,Dubbo 定义了集群接口 Cluster 以及 Cluster Invoker。集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者。这样一来,服务消费者只需通过这个 Invoker 进行远程调用即可,至于具体调用哪个服务提供者,以及调用失败后如何处理等问题,现在都交给集群模块去处理。集群模块是服务提供者和服务消费者的中间层,为服务消费者屏蔽了服务提供者的情况,这样服务消费者就可以专心处理远程调用相关事宜。比如发请求,接受服务提供者返回的数据等。这就是集群的作用。
使用方式
@DubboService(cluster="failover")
cluster中填写相应的容错方式,就可以使用了。
容错方式
Dubbo 主要提供了这样几种容错方式:
-
Failover Cluster - 失败自动切换
@DubboService(cluster="failover",retires=2) //retires代表重试的次数
这个需要,服务端提供幂等性的操作,不然可能会出现重复调用
-
Failfast Cluster - 快速失败,立马报错
-
Failsafe Cluster - 失败安全, 出现异常,直接吞掉
-
Failback Cluster - 失败自动恢复,记录失败请求,定时重发
-
Forking Cluster - 并行调用多个服务提供者,只要其中一个成功返回,那么就直接返回结果.
服务降级
服务降级只需要在配置@DubboReference(mock = "")
就可以了。mock里面带上类的名字
@RestController
@RequestMapping("/user")
public class UserController {
@DubboReference(mock = "com.onion.fallback.UserFallback")
private IUserService userService;
...
}
public class UserFallback implements IUserService {
public String getUser(String userId) {
return "getUser::服务降级返回";
}
public String loginUser(String userId) {
return "loginUser::服务降级返回";
}
}
服务注册的地址问题
ServiceConfig类中的findConfigedHosts方法获取ip信息:
- DUBBO_DUBBO_IP_TO_BIND 环境变量
- 配置文件中:dubbo.protocol.host
- 配置文件中:dubbo.provider.host
- InetAddress.getLocalHost().getHostAddress()
- 通过socket连接注册到注册中心的URL,来得到IP
- 通过遍历本机各个网卡,得到合适的网卡IP
这上面得到hostToBind(本机绑定的ip),优先级是从上到下。
hostToRegistry(注册到注册中心的ip)的获取(优先级是从上到下)
- DUBBO_DUBBO_IP_TO_REGISTRY 环境变量
- hostToRegistry = hostToBind;
如果DUBBO_DUBBO_IP_TO_REGISTRY 环境变量为空则取hostToBind(本机绑定的ip)。
ServiceConfig的源码分析:
public class ServiceConfig<T> extends ServiceConfigBase<T> {
private String findConfigedHosts(ProtocolConfig protocolConfig, List<URL> registryURLs, Map<String, String> map) {
boolean anyhost = false;
//如果协议是dubbo的话,获取环境中的DUBBO_DUBBO_IP_TO_BIND
String hostToBind = this.getValueFromConfig(protocolConfig, "DUBBO_IP_TO_BIND");
if (hostToBind != null && hostToBind.length() > 0 && NetUtils.isInvalidLocalHost(hostToBind)) {
throw new IllegalArgumentException("Specified invalid bind ip from property:DUBBO_IP_TO_BIND, value:" + hostToBind);
} else {
if (StringUtils.isEmpty(hostToBind)) {
//获取配置文件中:dubbo.protocol.host
hostToBind = protocolConfig.getHost();
if (this.provider != null && StringUtils.isEmpty(hostToBind)) {
//获取配置文件中:dubbo.provider.host
hostToBind = this.provider.getHost();
}
if (NetUtils.isInvalidLocalHost(hostToBind)) {
anyhost = true;
try {
logger.info("No valid ip found from environment, try to find valid host from DNS.");
//利用Api来获取InetAddress.getLocalHost().getHostAddress()
hostToBind = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException var22) {
logger.warn(var22.getMessage(), var22);
}
if (NetUtils.isInvalidLocalHost(hostToBind)) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
Iterator var6 = registryURLs.iterator();
label185:
while(true) {
URL registryURL;
do {
if (!var6.hasNext()) {
break label185;
}
registryURL = (URL)var6.next();
} while("multicast".equalsIgnoreCase(registryURL.getParameter("registry")));
try {
Socket socket = new Socket();
Throwable var9 = null;
try {
//通过socket连接注册到注册中心的URL,来得到IP
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
hostToBind = socket.getLocalAddress().getHostAddress();
break;
} catch (Throwable var21) {
var9 = var21;
throw var21;
} finally {
...
} catch (Exception var24) {
logger.warn(var24.getMessage(), var24);
}
}
}
if (NetUtils.isInvalidLocalHost(hostToBind)) {
//通过遍历本机各个网卡,得到合适的网卡IP
hostToBind = NetUtils.getLocalHost();
}
}
}
}
map.put("bind.ip", hostToBind);
//获取DUBBO_DUBBO_IP_TO_REGISTRY 环境变量,
String hostToRegistry = this.getValueFromConfig(protocolConfig, "DUBBO_IP_TO_REGISTRY");
if (hostToRegistry != null && hostToRegistry.length() > 0 && NetUtils.isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:DUBBO_IP_TO_REGISTRY, value:" + hostToRegistry);
} else {
//如果DUBBO_DUBBO_IP_TO_REGISTRY 环境变量为空得话,那么就把hostToRegistry设为hostToBind
if (StringUtils.isEmpty(hostToRegistry)) {
hostToRegistry = hostToBind;
}
map.put("anyhost", String.valueOf(anyhost));
return hostToRegistry;
}
}
}
private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
String value = ConfigUtils.getSystemProperty(protocolPrefix + key);
if (StringUtils.isEmpty(value)) {
value = ConfigUtils.getSystemProperty(key);
}
return value;
}
}