服务之间进行调用

RPC

什么是RPC

RPC是Remote Procedure Call的缩写,翻译为:远程过程调用

目标是为了实现两台(多台)计算机\服务器,相互调用方法\通信的解决方案

RPC只是显示远程调用的一套标准,该标准主要规定了两部分内容

  1. 序列化协议

  2. 通信协议

通信协议

通信协议指的就是远程调用的通信方式

实际上这个通知的方法可以有多种

例如:写信、闪送

序列化协议

序列化协议指通信内容的格式,双方都要理解这个格式

发送信息是序列化过程,接收信息需要反序列化

Dubbo

上面对RPC有基本认识之后,再学习Dubbo就简单了

Dubbo是一套RPC框架。既然是框架,我们可以再框架结构高度,定义Dubbo中使用的通信协议,使用的序列化协议框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。

可以说Dubbo就是RPC概念的实现

Dubbo是Spring Cloud Alibab提供的框架,能够实现微服务之间相互调用的功能

Dubbo的发展历程

最早是阿里巴巴内部的框架技术

2011年,阿里巴巴把dubbo对外开放

2012年底,阿里巴巴对dubbo停止更新维护

2017年9月,阿里巴巴继续对dubbo提供更新维护

2019年,阿里巴巴将dubbo2.7 以上版本共享给apache(apache.org)

至今,apache dubbo由apache维护更新,阿里巴巴一直更新到2.7

早期的Dubbo是和Spring Cloud平行的,也是微服务

我们学习的Dubbo指的都是2.7之后的版本,也是能够和Spring Cloud Alibaba配置使用的

dubbo对协议的支持

RPC框架分通信协议和序列化协议

Dubbo框架支持多种通信协议和序列化协议,可以通过配置文件进行修改

Dubbo支持的通信协议

dubbo协议(默认),阿里自己写的通信协议

http协议

rmi协议

hessian协议

webservice

Dubbo支持的序列化协议

hessian2(默认)

java 序列化

fastjson

kryo

dubbo

Dubbo默认情况下,支持的协议有如以下特征:

采用NIO单一长链接

优秀的并发性能,但是处理大型文件的能力差

Dubbo支持高并发和高性能

Dubbo服务的注册与发现

在Dubbo的调用过程中,必须包含注册中心的支持

注册中心推荐使用阿里自己的Nacos,兼容性好,能够发挥最大性能

但是Dubbo也支持其他软件作为注册中心,例如:redis,zookeeper等

服务发现,既消费端自动发现服务地址列表的能力,是微服务框架需要具备的关键额能力,借助于自动化的服务发现,微服务之间可以在无需感知对服务提供者端的部署位置与ip地址的情况下实现通信
image
consumer 服务的消费者,指服务的调用者

provider 服务的提供者,指的是服务的拥有者

在Dubbo中,远程调用依据是服务的提供者在Nacos中注册的服务名称

一个服务名称,可能有多个运行的实例,任何一个空闲的实例都可以提供服务

常见面试题:Dubbo的注册发现流程

  1. 首先服务的提供者启动服务时,将自己的具备的服务注册到注册中心,其中包含当前提供者的ip地址和端口号等信息,Dubbo会同时注册该项目提供的远程调用的方法
  2. 消费者启动项目,也注册到注册中心,同时从注册中心中获得当前项目具备的所有服务列表
  3. 当注册中心中有新的服务出现时,会通知已经定阅发现的消费者,消费者会更新所有服务列表
  4. RPC调用,消费者需要调用远程方法时,根据注册中心服务列表的信息,只需服务名称,不需要ip地址和端口号等信息,就可以利用Dubbo调用远程方法了

我们当前csmall项目的远程调用关系,如下图所示
image

Dubbo实现微服务调用

首先需要确定调用关系
image

修改csmall-stock模块

当前项目时Dubbo中的服务的提供者

需要将提供给别的服务调用的方法单独编写在一个项目中,这个项目中有的方法,别人才能调用

创建csmall-stock-service项目

删除test、删除resources、删除主启动类

csmall-stock升级为父项目,pom文件添加以下内容:

1
2
3
4
<packaging>pom</packaging>
<modules>
   <module>csmall-stock-service</module>
</modules>

csmall-stock-service的pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>cn.tedu</groupId>
       <artifactId>csmall-stock</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>cn.tedu</groupId>
   <artifactId>csmall-stock-service</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>csmall-stock-service</name>
   <description>Demo project for Spring Boot</description>
   <dependencies>
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-commons</artifactId>
           <version>0.0.1-SNAPSHOT</version>
       </dependency>
   </dependencies>
</project>

把csmall-stock模块中的IStockService接口复制到csmall-stock-service中
image

到此为止,csmall-stock-service修改结束

创建csmall-stock-webapi项目

父子相认

csmall-stock-webapi的pom文件(把父项目要的依赖剪切过来,还行添加dubbo和csmall-stock-service依赖)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>cn.tedu</groupId>
       <artifactId>csmall-stock</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>cn.tedu</groupId>
   <artifactId>csmall-stock-webapi</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>csmall-stock-webapi</name>
   <description>Demo project for Spring Boot</description>
   <dependencies>
       <!--web实例-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <!--mybatis整合springboot-->
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
       </dependency>
       <!--alibaba 数据源德鲁伊-->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid</artifactId>
       </dependency>
       <!--mysql驱动-->
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
       </dependency>
       <!--all-common依赖-->
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-commons</artifactId>
           <version>0.0.1-SNAPSHOT</version>
       </dependency>
       <!--在线api文档-->
       <dependency>
           <groupId>com.github.xiaoymin</groupId>
           <artifactId>knife4j-spring-boot-starter</artifactId>
       </dependency>
       <!--添加nacos依赖-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
       </dependency>
       <!--添加dubbo依赖-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-dubbo</artifactId>
       </dependency>
       <!--为了实现业务逻辑层接口,要把csmall-stock-service的依赖添加进来-->
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-stock-service</artifactId>
           <version>0.0.1-SNAPSHOT</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
           <exclusions>
               <exclusion>
                   <groupId>org.junit.vintage</groupId>
                   <artifactId>junit-vintage-engine</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
   </dependencies>

   <dependencyManagement>
       <dependencies>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-dependencies</artifactId>
               <version>${spring-boot.version}</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
       </dependencies>
   </dependencyManagement>
</project>
​```
csmall-stock的pom文件,最终是
```XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>cn.tedu</groupId>
       <artifactId>csmall</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>cn.tedu</groupId>
   <artifactId>csmall-stock</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>csmall-stock</name>
   <description>Demo project for Spring Boot</description>
   <packaging>pom</packaging>
   <modules>
       <module>csmall-stock-service</module>
       <module>csmall-stock-webapi</module>
   </modules>

</project>

把csmall-stock中的cn.tedu.csmall.stock和resources目录下的内容复制到csmall-stock-webapi中

csmall-stock中的src目录删除了

接下来是csmall-stock-webapi项目的修改

  1. 删除service中的接口
  2. StockServiceImpl中修改StockMapper的包路径
  3. 修改Knife4jConfiguration中的包路径和分组名称
  4. 修改MybatisConfiguration中扫描的包路径

关于dubbo的使用

添加dubbo的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
       <!--添加dubbo依赖-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-dubbo</artifactId>
       </dependency>
       <!--为了实现业务逻辑层接口,要把csmall-stock-service的依赖添加进来-->
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-stock-service</artifactId>
           <version>0.0.1-SNAPSHOT</version>
​```
在webapi的application-dev.yml中配置dubbo相关的信息
```yml
#配置dubbo信息
dubbo:
protocol:
   #port设置成-1是Dubbo框架支持的特殊写法,会自动动态生成可用的端口,规则是从20880开始,
   #如果被占用就递增使用
  port: -1
   #设置连接的名称,一般固定就叫dubbo
  name: dubbo
registry:
   #声明注册中心的软件类型和ip端口号
  address: nacos: //localhost:8848
consumer:
   #当项目启动时,作为消费者,是否要检查所有远程服务可用,false表示不检查
  check: false

下面就可以配置实现Dubbo方法提供的步骤了

将业务逻辑层实现类方法声明为Dubbo可调用的方法

在业务逻辑层类上添加@DubboService的注解,该注解描述的类中的所有的放啊都会注册到Nacos,其它服务在订阅时就会发现当前项目提供的业务逻辑层方法,以备Dubbo调用

1
2
3
4
5
6
@DubboService
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
  ......
}

如果当前项目是服务的提供者,还需要再SpringBoot启动类上添加@EnableDubbo的注解,才让让Dubbo真正生效
如果作为提供者,不添加该注解,其他服务是无法调用该项目的方法

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableDubbo
public class CsmallStockWebapiApplication {

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

}

先启动nacos,再启动StockWebapi项目,观察结果
image

修改csmall-cart模块

步骤和stock差不多,参考stock拆分cart

修改csmall-order模块

创建csmall-order-service项目

csmall-order-service和cart以及stock是一样的

创建csmall-order-webapi

需要添加的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
       <!--添加dubbo依赖-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-dubbo</artifactId>
       </dependency>
       <!--order需要调用cart和stock模块,所以要把csmall-order-service、csmall-cart-service、csmall-stock-service的依赖添加进来-->
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-order-service</artifactId>
           <version>0.0.1-SNAPSHOT</version>
       </dependency>
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-stock-service</artifactId>
           <version>0.0.1-SNAPSHOT</version>
       </dependency>
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-cart-service</artifactId>
           <version>0.0.1-SNAPSHOT</version>
       </dependency>

yaml文件和cart的一样,代码是正常的从父项目中复制过来

config包下类的修改也是和之前一样

作为dubbo的提供者的配置还是和之前一样

作为dubbo的调用者

service.impl中的修改不一样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@DubboService
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
   @Autowired
   private OrderMapper orderMapper;
   //当前order模块消费stock模块的减少库存的方法
   //因为stock模块减少库存的方法已经注册到Nacos,所以当前order模块可以利用dubbo调用
   //要向调用必须使用@DubboReference,才能获得业务逻辑层实现类对象
   @DubboReference
   private IStockService stockService;
   //还需要cart模块的服务,有的公司要求dubbo的业务层变量名使用dubbo开头
   @DubboReference
   private ICartService dubboCartService;

   @Override
   public void orderAdd(OrderAddDTO orderAddDTO) {
       OrderTbl orderTbl = new OrderTbl();
       BeanUtils.copyProperties(orderAddDTO,orderTbl);
       //执行新增订单之前,需要先减少库存和删除购物车商品
       //1.减少订单商品的库存数(利用dubbo调用stock模块)
       //参数需要的是StockReduceCountDTO类型的参数,
       // 这个参数里面的属性是commodityCode和reduceCount,恰好OrderAddDTO有
       //,直接获取赋值即可
       StockReduceCountDTO stockReduceCountDTO = new StockReduceCountDTO();
       stockReduceCountDTO.setCommodityCode(orderAddDTO.getCommodityCode());
       stockReduceCountDTO.setReduceCount(orderAddDTO.getCount());
       stockService.reduceCommodityCount(stockReduceCountDTO);
       //2.删除购物车商品(利用dubbo调用cart模块)
       dubboCartService.deleteUserCart(orderAddDTO.getUserId(),orderAddDTO.getCommodityCode());
       //3.新增订单
       orderMapper.insertOrder(orderTbl);
       log.info("新增订单的信息:{}",orderTbl);
  }
}

测试dubbo是否可以使用

首先保证Na操启动

我们项目的启动顺序,尽量保证先启动生产者

再启动消费者

先启动stock、cart,再启动order

(访问路径:http://localhost:20002/doc.html) 运行测试

注意:运行前数据库的数据状态和运行后比较一下

配置business

添加依赖

1
2
3
4
5
6
7
8
9
10
11
        <!--添加dubbo依赖-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-dubbo</artifactId>
       </dependency>
      <!--business需要调用order模块,所以要把csmall-order-service的依赖添加进来-->
       <dependency>
           <groupId>cn.tedu</groupId>
           <artifactId>csmall-order-service</artifactId>
           <version>0.0.1-SNAPSHOT</version>
       </dependency>

application-dev.yml和其他项目一样,需要添加dubbo信息

business是单纯的调用者,只需要再service层添加@DubboReference然后调用方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
@Slf4j //提供log对象,可以用于打印日志
public class BusinessServiceImpl implements IBusinessService {

   //需要利用dubbo调用order模块中的新增订单方法
   @DubboReference
   private IOrderService dubboOrderService;
   @Override
   public void buy() {
       //模拟购买业务
       //创建OrderAddDTO类,并赋值
       OrderAddDTO orderAddDTO = new OrderAddDTO();
       orderAddDTO.setUserId("UU100");
       orderAddDTO.setCommodityCode("PC100");
       orderAddDTO.setCount(5);
       orderAddDTO.setMoney(100);
       //调用order模块的方法
       dubboOrderService.orderAdd(orderAddDTO);
       //目前现在控制台输出
       log.info("新增订单的信息为:{}",orderAddDTO);
  }
}

测试,观察数据状态

Dubbo生产者消费者配置小结

Dubbo生产者消费者相同的配置

pom文件添加dubbo依赖,yml文件配置dubbo信息

不同配置

  • 生产者
  1. 要有service接口项目
  2. 提供服务的业务逻辑层实现类要添加@DubboService
  3. SpringBoot启动类要添加@EnableDubbo注解
  • 消费者
  1. pom文件添加消费者模块的service依赖
  2. 业务逻辑层远程调用前,模块使用@DubboReference注解获取业务逻辑层实现类对象

配置stock集群的高可用

仍然使用20003、20004、20005端口

按照之前笔记配置即可

为了方便观察运行的哪个实例,我们获取端口输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@DubboService
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
   @Autowired
   private StockMapper stockMapper;

   @Value("${server.port}")
   private int port;

   @Override
   public void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO) {
       //参数1:商品编号
       //参数2:减少的库存数
       stockMapper.updateStockByCommodityCode(
               stockReduceCountDTO.getCommodityCode(), stockReduceCountDTO.getReduceCount());
       log.info("库存减少完成!端口是:{}",port);
  }
}

我们多次访问business模块进行测试后,可以发现stock模块的三台服务器都可以处理请求,像这样的一个过程是集群中的负载均衡策略

负载均衡

什么是负载均衡

在实际开发中,一个服务基本都是集群模式的,也就是多个功能相同的项目在运行,这样才能承受更高的并发

这时一个请求到这个服务,就需要确定访问哪个服务器
image
Dubbo框架内部支持负载均衡算法,能够尽可能的让请求在相对空闲的服务器上运行

Dubbo内部默认支持负载均衡算法

在不同的项目中给,可能选用不同的负载均衡策略,以达最好效果

Dubbo内置负载均衡策略

Dubbo内置4种负载均衡算法

  • Random LoadBalance:随机分配策略(默认)

  • Round Robin LoadBalance:权重平滑分配

  • LeastActive LoadBalance:活跃度自动感知分配

  • ConsistantHash LoadBalance:一致性hash算法

实际运行过程中,每个服务器性能不同的

在负载均衡时,会有性能权重,这些策略都考虑权重问题

随机分配策略

假设有三台服务器,经过测试它们的性能权重比为: 5:3:1

下面可以生成一个权重模型
image
随机生成随机数,在哪个范围内就让哪个服务器运行

优点
算法简单,效率高,长时间运行下,任务分配比例准确

缺点
偶然性高,如果连续的几个随机请求发送到性能弱的服务器,会导致异常甚至宕机

权重平滑策略

如果几个服务器权重一致,那么就是依次运行

但是服务器性能权重一致的可能性很小

所以我们需要权重平滑分配

一个优秀的权重分配算,应该是让每个服务器都有机会运行

如果一个集群性能比为: 5:3:1

1->A 2->A 3->A 4->A 5->A 6->B 7->B 8->B 9->C

上面这种安排,连续几个请求到一个服务器中处理,显然是不合理的,我们希望的是所有服务器穿插在一起运行

Dubbo 2.7 之后更新了这个算法,这个算法使用“权重加权算法”优化权重平均分配策略
image

活跃度自动感知

记录每个服务器处理一次请求的时间(会额外消耗资源)

按着时间比例来分配任务数,运行一次需要时间多的分配的请求数较少

一致性hash算法

根据请求的参数进行hash运算

以后每次相同参数的请求都会访问固定的服务器

因为根据参数选择服务器,不能平均分配到每台服务器,而且如果服务器宕机了,请求也不会被分配到其他的服务器
使用的很少

我们在business中添加了一个在stock数据库中不存在的商品成功了!!但是这样肯定不符合常理,这就是数据的不一致

数据一致相关的知识点我们之间学习过事务