SpringCloud Alibaba使用Dubbo实现RPC远程通信(四)

3 篇文章 0 订阅

目录

1.Dubbo介绍

1.1工作原理

2.集成Dubbo

2.1基础用法

1)公共接口工程

2)服务提供者

3)服务消费者

3.服务提供者和消费者的配置规则

3.1.负载均衡

3.2.集群容错

3.3.服务降级

3.4.重试机制

3.5.限流


1.Dubbo介绍

Apache Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

Dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有Dubbo这样的分布式服务框架的需求。

1.1工作原理

0:start >>>项目启动,将服务提供者(Prvider)提供的服务加载到容器(Container).中.Container就是spring容器,这个学过spring的应该都知道。

1:register >>>将容器中的服务注册到注册中心(Registry)去。

2:subscribe >>>消费者(Consumer)订阅注册中心(Registry)。

3:notify >>> 注册中心(Registry)告诉消费者(Consumer)我有那些服务。

4:invoke >>> 消费者(Consumer)向发起服务提供者(Prvider)调用。

5:count>>>监听器(Monitor)会监听所有的的执行过程。

2.集成Dubbo

2.1基础用法

1)公共接口工程

1、新建module,名为alibaba-dubbo-common

2、新建公共接口及实体类

public interface OrderDubboService {

    List<OrderInfo> findAllOrder();

    OrderInfo findOrderByUserId(Long userId);

    String getInfo();
}

OrderInfo.java

public class OrderInfo implements Serializable {
    private Long orderId;
    private Long userId;

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }
}

2)服务提供者

1、新建module,模块名称为alibaba-cloud-order

2、引入相关的依赖

<!--boot web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos注册中心 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- nacos配置中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- dubbo RPC调用,注意是spring-cloud-starter-dubbo-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

<!-- 公共依赖 -->
<dependency>
    <groupId>com.harvey</groupId>
    <artifactId>alibaba-dubbo-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

3、bootstrap.yml配置

server:
  port: 8888

spring:
  application:
    name: alibaba-cloud-order
  cloud:
    nacos:
      #服务注册
      discovery:
        enabled: true
        #nacos地址
        server-addr: http://localhost:8848
        #当前应用是否注册到注册中心,默认是true
        register-enabled: true
      #配置中心
      config:
        enabled: true
        #nacos地址
        server-addr: http://localhost:8848
        #配合文件后缀
        file-extension: yaml

# dubbo配置
dubbo:
  application:
    name: ${spring.application.name}
  #扫描java包,多个包用逗号分割
  scan:
    base-packages: com.harvey.demo
  #注册中心
  registry:
    #注册中心地址列表,同一集群内的多个地址用逗号分隔
    address: nacos://127.0.0.1:8848
    #是否向此注册中心订阅服务
    subscribe: true
    #注册中心会话超时时间(毫秒)
    session: 6000
  #服务提供者
  provider:
    register: true
  #服务消费者
  consumer:
    #远程服务调用重试次数,不包括第一次调用,不需要重试请设为0
    retries: 2
  protocol:
    #随机端口,默认是20880
    port: -1
    name: dubbo

4、启动类

@SpringBootApplication
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class, args);
    }
}

5、Dubbo接口的实现

@DubboService
public class OrderDubboServiceImpl implements OrderDubboService {

    private static Map<Long, OrderInfo> orderInfoMap = new HashMap();

    static {
        orderInfoMap.put(10001L, new OrderInfo(1L, 10001L));
        orderInfoMap.put(10002L, new OrderInfo(2L, 10002L));
        orderInfoMap.put(10003L, new OrderInfo(3L, 10003L));
    }

    @Override
    public List<OrderInfo> findAllOrder() {
        return new ArrayList(orderInfoMap.values());
    }

    @Override
    public OrderInfo findOrderByUserId(Long userId) {
        return orderInfoMap.get(userId);
    }

    @Override
    public String getInfo() {
        return "alibaba-cloud-order-01";
    }
}

6、启动项目,结果报错:

***************************
APPLICATION FAILED TO START
***************************

Description:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.<init>(ReferenceAnnotationBeanPostProcessor.java:106)

The following method did not exist:

    org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.setClassValuesAsString(Z)V

The method's class, org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor, is available from the following locations:

    jar:file:/E:/englishPath/selfrespority/org/apache/dubbo/dubbo/2.7.13/dubbo-2.7.13.jar!/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.class

The class hierarchy was loaded from the following locations:

    org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor: file:/E:/englishPath/selfrespority/org/apache/dubbo/dubbo/2.7.13/dubbo-2.7.13.jar
    com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor: file:/E:/englishPath/selfrespority/com/alibaba/spring/spring-context-support/1.0.10/spring-context-support-1.0.10.jar
    org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter: file:/E:/englishPath/selfrespority/org/springframework/spring-beans/5.2.8.RELEASE/spring-beans-5.2.8.RELEASE.jar


Action:

Correct the classpath of your application so that it contains a single, compatible version of org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor

一直以为是依赖冲突问题。改了无数个版本都不行,最后终于在github,spring-cloud-alibaba issues中找到了答案:升级 2.2.7 报错 · Issue #2310 · alibaba/spring-cloud-alibaba · GitHub

单独添加依赖:

<dependency>
    <groupId>com.alibaba.spring</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>1.0.11</version>
</dependency>

重新启动,控制台没有报错,查看nacos服务列表:

实际开发中,常用namespace来隔离应用和Dubbo服务的注册。

例如我们创建两个命名空间dev和dubbo,应用注册到dev,dubbo服务注册到命名空间dubbo,修改bootstrap.yml:

spring:
  cloud:
    nacos:
      #服务注册
      discovery:
        namespace: 2f7930c8-ccae-4577-bdcd-0cf874cf1297

# dubbo配置
dubbo:
  #注册中心
  registry:
    address: nacos://127.0.0.1:8848?namespace=fab88f57-87a7-4e9b-a3a5-fcdee1605282

最终效果:

3)服务消费者

1、新建module,名为alibaba-cloud-user

2、引入相关的依赖

<!--boot web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos注册中心 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- nacos配置中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- dubbo RPC调用,注意是spring-cloud-starter-dubbo-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.spring</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>1.0.11</version>
</dependency>

<!-- 公共依赖 -->
<dependency>
    <groupId>com.harvey</groupId>
    <artifactId>alibaba-dubbo-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

3、bootstrap.yml

server:
  port: 9999

spring:
  application:
    name: alibaba-cloud-user
  cloud:
    nacos:
      #服务注册
      discovery:
        enabled: true
        #nacos地址
        server-addr: http://localhost:8848
        #当前应用是否注册到注册中心,默认是true
        register-enabled: true
        namespace: 2f7930c8-ccae-4577-bdcd-0cf874cf1297
      #配置中心
      config:
        enabled: true
        #nacos地址
        server-addr: http://localhost:8848
        #配合文件后缀
        file-extension: yaml

# dubbo配置
dubbo:
  application:
    name: ${spring.application.name}
  #扫描java包,多个包用逗号分割
  scan:
    base-packages: com.harvey.demo
  #注册中心
  registry:
    #注册中心地址列表,同一集群内的多个地址用逗号分隔
    address: nacos://127.0.0.1:8848?namespace=fab88f57-87a7-4e9b-a3a5-fcdee1605282
    #是否向此注册中心订阅服务
    subscribe: true
    #注册中心会话超时时间(毫秒)
    session: 6000
  #服务提供者
  provider:
    register: true
  #服务消费者
  consumer:
    #远程服务调用重试次数,不包括第一次调用,不需要重试请设为0
    retries: 2
  protocol:
    #随机端口,默认是20880
    port: -1
    name: dubbo

4、启动类

@SpringBootApplication
public class CloudUserApp {
    public static void main(String[] args) {
        SpringApplication.run(CloudUserApp.class, args);
    }
}

5、新建UserService、UserController完成调用

UserService.java

public interface UserService {

    Object findAllOrders();

    Object getOrderInfo(Long userId);

    Object getInfo();

}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService{

    @DubboReference
    private OrderDubboService orderDubboService;

    @Override
    public Object findAllOrders() {
        return orderDubboService.findAllOrder();
    }

    @Override
    public Object getOrderInfo(Long userId) {
        return orderDubboService.findOrderByUserId(userId);
    }

    @Override
    public Object getInfo(){
        return orderDubboService.getInfo();
    }
}

UserController.java

@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/findAllOrders")
    public Object findAllOrders() {
        return userService.findAllOrders();
    }

    @RequestMapping("/getOrderInfo")
    public Object getOrderInfo(@RequestParam("userId") Long userId) {
        return userService.getOrderInfo(userId);
    }

    @RequestMapping("/getInfo")
    public Object getInfo() {
        return userService.getInfo();
    }
}

6、测试验证

启动项目,访问 http://127.0.0.1:9999/user/findAllOrders

3.服务提供者和消费者的配置规则

dubbo推荐在Provider上尽量多配置Consumer端属性:

作为服务的提供者,⽐服务⽅更清楚服务性能的参数,如调⽤时间,合理的重试次数等,所以这些参数应尽量配置在服务的提供者⽅;

在provider配置后,Consumer不配置则会使⽤provider的配置值,即provider的配置会作为consumer配置的缺省值。如果使⽤consumer的全局配置,这对于provider是不可控的,并且是不合理的。

配置优先顺序为:消费端⽅法级 > 服务端⽅法级 > 消费端接⼝级 > 服务端接⼝级 > 消费端全局 > 服务端全局

3.1.负载均衡

我们与alibaba-cloud-order一样,创建一个新的module,alibaba-cloud-order2,将bootstrap.yml中的server.port改为7777,OrderDubboServiceImpl的getInfo方法改为:

@Override
public String getInfo() {
    return "alibaba-cloud-order-02";
}

启动项目后,我们在nacos可以看到alibaba-cloud-order有两个实例:

Dubbo主要提供了以下几种负载均衡策略:

  • RandomLoadBalance (随机-random):默认策略,实际上是考虑了权重之后的随机选择,如果每个服务提供者的权重都一致,那么就使用 java 的随机函数去选择一个。
  • RoundRobinLoadBalance (轮询-roundrobin):轮询负载均衡策略,本质上也是考虑了权重之后的轮循。如果 A 服务提供者的权重是 B 服务提供者的两倍,那么理论上 A 被轮循到的次数就会是 B 的两倍。
  • LeastActiveLoadBalance (最少活跃-leastactive):最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
  • ConsistentHashLoadBalance (一致性 hash-consistenthash):使用一致性 Hash 算法,让相同参数的请求总是发到同一 Provider。 当某一台 Provider 崩溃时,原本发往该 Provider 的请求,基于虚拟节点,平摊到其它 Provider,不会引起剧烈变动。
  • ShortestResponseLoadBalance (最短反馈):根据响应时间和当前服务的请求量去获得一个最优解。如果存在多个最优解,则考虑权重,如果仅有一个则权重无效。

1、修改服务消费者alibaba-cloud-user的配置,负载均衡策略改为轮询

dubbo:
  #服务消费者
  consumer:
    #负载均衡
    loadbalance: roundrobin #轮询

2、重启项目,访问 http://127.0.0.1:9999/user/getInfo,我们发现,alibaba-cloud-order-01 和 alibaba-cloud-order-02 是轮着展示的,说明配置成功了。

3、上面我们讲到轮询也是基于权重后的轮询,我们在nacos上修改alibaba-cloud-order的两个dubbo服务提供者实例的权重,看看效果怎样(本人测试貌似没起作用)。

编辑其中一个实例的权重,点击"确认"时报错:

caused: errCode: 500, errMsg: do metadata operation failed ;
caused: com.alibaba.nacos.consistency.exception.ConsistencyException: com.alibaba.nacos.core.distributed.raft.exception.NoLeaderException: The Raft Group [naming_instance_metadata] did not find the Leader node;
caused: com.alibaba.nacos.core.distributed.raft.exception.NoLeaderException: The Raft Group [naming_instance_metadata] did not find the Leader node;

Nacos 采用 raft 算法来计算 Leader,并且会记录前一次启动的集群地址,所以当我们自己的服务器 IP 改变时(这里特指自己学习时,在本地启动的同学,因为有时候我们的网络环境会变的 … WIFI,所以 IP 地址也经常变化),会导致 raft 记录的集群地址失效,导致选 Leader 出现问题,只要删除 Nacos 根目录下 data 文件夹下的 protocol 文件夹即可。

注意:该错误的解决方案可能不适用所有人,大家还是找下度娘,尝试多种方案解决吧。

3.2.集群容错

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。

  • FailoverCluster(failover):失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
  • FailfastCluster(failfast):快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
  • FailsafeCluster(failsafe):失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
  • FailbackCluster(failback):失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  • ForkingCluster(forking):并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
  • AvailableCluster(available):调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。

集群容错这样配置:

dubbo:
  #服务消费者
  consumer:
    # 集群容错
    cluster: failover

3.3.服务降级

可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。

dubbo官方文档上使用一个mock配置,实现服务降级。mock只在出现非业务异常(比如超时,网络异常等)时执行。

mock的配置支持两种:

  • 配置"return null",可以很简单的忽略掉异常。
  • 为boolean值,默认的为false。如果配置为true,则缺省使用mock类名,即类名+Mock后缀;

1、模拟超时场景

1)alibaba-cloud-user配置timeout:

dubbo:
  consumer:
    timeout: 2000

2)alibaba-cloud-order和alibaba-cloud-order2休眠3s

@Override
public String getInfo() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "alibaba-cloud-order-01";
}
@Override
public String getInfo() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "alibaba-cloud-order-02";
}

3)重启项目后,访问 http://127.0.0.1:9999/user/getInfo

2、直接return null 忽略异常

服务调用方配置

dubbo:
  consumer:
    mock: "return null"

此时控制台还会报错,但是页面没有显示任何报错信息了。

3、使用mock类名,即类名+Mock后缀

业务接口所在的包中,定义一个类,该类的命名需要满足以下规则:业务接口简单类名 + Mock同时实现mock接口,类名要注意命名规范:接口名+Mock后缀。此时如果调用失败会调用Mock实现。mock实现需要保证有无参的构造方法。

1)服务调用方配置

dubbo:
  consumer:
    mock: true

2)服务调用方提供Mock实现(注意规范)

/**
 * OrderDubboServiceMock
 * 1)实现的接口是服务提供方提供的Dubbo接口OrderDubboService,不是我们的业务接口
 * 2)实现类的命名规范:Dubbo接口名 + Mock
 * 3)实现类与Dubbo接口必须在同一个包路径下
 * 以上条件缺一不可,否则会找不到对应的Mock类
 */
public class OrderDubboServiceMock implements OrderDubboService {

    @Override
    public List<OrderInfo> findAllOrder() {
        List<OrderInfo> orderInfoList = new ArrayList();
        orderInfoList.add(new OrderInfo(-1L, -1L));
        return orderInfoList;
    }

    @Override
    public OrderInfo findOrderByUserId(Long userId) {
        return new OrderInfo(-1L, -1L);
    }

    @Override
    public String getInfo() {
        return "getInfo调用失败了";
    }
}

3)测试验证,访问 http://127.0.0.1:9999/user/getInfo ,控制报错了,但是浏览器显示如下:

说明我们的服务降级Mock生效了。

3.4.重试机制

当消费者请求⼀个服务时出现错误,会重试连接其他的服务器,但重试会带来更多的延迟。重试次数可以使⽤【retries=重试次数】来设置。

Dubbo的超时重试机制为服务容错、服务稳定提供了⽐较好的框架⽀持 dubbo在调⽤服务不成功时,默认会重试2次。Dubbo的路由机制,会把超时的请求路由到其他机器上,⽽不是本机尝试,所以 dubbo的重试机器也能⼀定程度的保证服务的质量。,但是在⼀些⽐较特殊的⽹络环境下(⽹络传输慢,并发多)可能由于服务响应慢,Dubbo⾃⾝的超时重试机制(服务端的处理时间超过了设定的超时时间时,就会有重复请求)可能会带来⼀些⿇烦。

常见的应⽤场景故障:

  • 发送邮件(重复) ;
  • 账户注册(重复)

解决方案:

1)对于核⼼的服务中⼼,去除dubbo超时重试机制,并重新评估设置超时时间。

retries设置为0,表示不重试。

2)业务处理代码必须放在服务端,客户端只做参数验证和服务调⽤,不涉及业务流程处理。

3.5.限流

为了防止某个消费者的QPS或是所有消费者的QPS总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。

Dubbo中能够实现服务限流的方式较多,可以划分为两类:直接限流间接限流

  • 直接限流:通过对连接数量直接进行限制来达到限流的目的。
  • 间接限流:通过一些非连接数量设置来达到限制流量的目的。

1、executes直接限流– 仅提供者端

该属性仅能设置在提供者端。可以设置为接口级别,也可以设置为方法级别。限制的是服务(方法)并发执行数量。

服务提供者:

dubbo:
  provider:
    executes: 10

接口级别:

@DubboService(executes = 10)

方法级别:

@DubboService(methods = {
        @Method(name = "getInfo", executes = 10)
})

2、accepts限流 – 仅提供者端

用于对指定协议的连接数量进行限制。

3、actives限流 – 两端

该限流方式与前两种不同的是,其可以设置在提供者端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别

1)提供者端限流

根据消费者与提供者间建立的连接类型的不同,其意义也不同。

  • 长连接:表示当前长连接最多可以处理的请求个数。与长连接的数量没有关系
  • 短连接:表示当前服务可以同时处理的短连接数量

2)消费者端限流

根据消费者与提供者间建立的连接类型的不同,其意义也不同:

  • 长连接:表示当前消费者所发出的长连接中最多可以提交的请求个数。与长连接的数量没有关系。
  • 短连接:表示当前消费者可以提交的短连接数量

4、connections限流

可以设置在提供者端,也可以设置在消费者端。限定连接的个数。对于短连接,该属性效果与actives相同。但对于长连接,其限制的是长连接的个数。

一般情况下,会使connectons与actives联用,让connections限制长连接个数,让actives限制一个长连接中可以处理的请求个数。联用前提:使用默认的Dubbo服务暴露协议。

5、间接限流

1)延迟连接 – 仅消费者端

仅可设置在消费者端,且不能设置为方法级别。仅作用于Dubbo服务暴露协议。

将长连接的建立推迟到消费者真正调用提供者时。

可以减少长连接的数量。

@DubboReference(lazy = true)

2)粘连连接 – 仅消费者

仅能设置在消费者端,其可以设置为接口级别,也可以设置为方法级别。仅作用于Dubbo服务暴露协议。

其会使客户端尽量向同一个提供者发起调用,除非该提供者挂了,其会连接另一台。只要启用了粘连连接,其就会自动启用延迟连接。

其限制的是流向,而非流量。

@DubboReference(sticky = true)

3)负载均衡

可以设置在消费者端,亦可设置在提供者端;可以设置在接口级别,亦可设置在方法级别。其限制的是流向,而非流量。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值