dubbo入门笔记

为什么要使用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

image-20201018172031620

查看nacos中注册上来的信息

dubbo:

image-20201018171826580

rest:

image-20201018171753463

​ 可以看到protocol是不同的一个是dubbo,一个是rest,而且rest协议还是告诉你用的sever是什么,上图就是server=tomcat

参考文档:

http://dubbo.apache.org/zh-cn/blog/dubbo-rest.html

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 节点中。

img

下面来看看一致性 hash 在 Dubbo 中的应用。我们把上图的缓存节点替换成 Dubbo 的服务提供者,于是得到了下图:

img

这里相同颜色的节点均属于同一个服务提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。比如:

consistent-hash-data-incline

如上,由于 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;
    }

}
原文地址:https://www.cnblogs.com/dabenxiang/p/13898031.html