服务之间进行调用

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数据库中不存在的商品成功了!!但是这样肯定不符合常理,这就是数据的不一致

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