1. Dubbo的历史
2011年10月27日,阿里巴巴开源了自己的SOA服务化治理方案的核心框架Dubbo,服务治理和SOA的设计理念开始逐渐在国内软件行业中落地,并被广泛应用。Dubbo主要有两种使用方式:
- 早期版本的dubbo遵循SOA的思想,是面向服务架构的重要组件。
- 如今版本的Dubbo作为Spring Cloud的二进制通信方案来发挥Dubbo的性能优势
2. Dubbo快速入门
2.1 Dubbo的基本架构
节点角色说明:
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方。 |
Consumer | 调用远程服务的服务消费方。 |
Registry | 服务注册与发现的注册中心。 |
Monitor | 统计服务的调用次数和调用时间的监控中心。 |
Dubbo的作用和Feign是一样的,但是Feign是应用层的协议进行远程通讯,而Dubbo是位于传输层的,因此Dubbo更加接近底层一些,因此在高并发的情况下,Dubbo的效率要更高一些,因此我们使用Dubbo来带Feign
不同角色的说明:
- 服务容器负责启动,加载运行服务的提供者
- 服务提供者在启动的时候就将自己注册到注册中心中
- 消费者在启动的时候,向注册中心订阅自己需要的服务
- 服务消费者从提供者的地址列表中,基于负载均衡算法,选择一个提供者进行调用,如果调用失败,则选择另一台调用
- 监控中心会监控消费者和提供者的调用次数和时间。
2.2 Nacos注册中心
Nacos是阿里巴巴的产品,是一个集服务发现,配置管理的平台,在国内受欢迎程度较高。
安装步骤:
- 解压Nacos到没有中文和特殊符号的目录中
- 进入
bin
目录中,然后运行下面的指令
#进入bin目录
cd bin
#启动
startup.cmd -m standalone
- 在浏览器访问http://127.0.0.1:8848/nacos
2.3 管理后台
DubboAdmin是阿里巴巴管理提供的管理控制台,可以实现服务查询,详情展示,服务测试等功能。借由DubboAdmin可以更好的帮助开发人员对服务进行管理和监控。
安装步骤如下:
#1、下载代码:
git clone https://github.com/apache/dubbo-admin.git
#2、在 dubbo-admin-server/src/main/resources/application.properties中指定注册中心地址
#3、构建
mvn clean package -D maven.test.skip=true
#4、启动
mvn --projects dubbo-admin-server spring-boot:run
#或者
cd dubbo-admin-distribution/target; java -jar dubbo-admin-0.1.jar
#5、访问 http://localhost:8080
注意:管理后台只在仅仅使用Dubbo的时候使用,后续将Dubbo和Spring Cloud整合后可以直接通过Nacos进行管理
2.4 入门案例
需求:使用Dubbo构建分布式架构,完成根据用户id查询用户
2.4.1 服务提供者
创建user-provider模块导入依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--dubbo的起步依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.8</version>
</dependency>
</dependencies>
编写引导类
package cn.itcast.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("cn.itcast.user.mapper")
@SpringBootApplication
public class UserProviderApplication {
public static void main(String[] args) {
SpringApplication.run(UserProviderApplication.class, args);
}
}
UserService接口
package cn.itcast.user.service;
import cn.itcast.user.domain.User;
public interface UserService {
User queryById(Long id);
}
UserServiceImpl
package cn.itcast.user.service;
import cn.itcast.user.api.UserService;
import cn.itcast.user.domain.User;
import cn.itcast.user.mapper.UserMapper;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
@DubboService
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
//根据id查询用户名称
public String queryUsername(Long id) {
return userMapper.findById(id).getUsername();
}
}
因为是远程调用,因此之前的
@Service
注解需要替换成@DubboService
,并且之后在yml的配置文件中需要指定包扫描,让Dubbo扫描到
Application.yml
需要配置的内容
- 协议:端口,协议名称
- 注册中心地址
- 包扫描位置
server:
port: 18081
spring:
datasource:
url: jdbc:mysql://localhost:3306/dubbo-demo?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
application:
name: user-provider
logging:
level:
cn.itcast: debug
pattern:
dateformat: HH:mm:ss:SSS
dubbo:
protocol:
name: dubbo
port: 20881
registry:
address: nacos://127.0.0.1:8848
scan:
base-packages: cn.itcast.user.service
2.4.2 服务消费者
创建user-consumer模块导入依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--dubbo的起步依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.8</version>
</dependency>
</dependencies>
配置启动类
package cn.itcast.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerApplication.class, args);
}
}
UserService接口
package cn.itcast.user.service;
import cn.itcast.user.domain.User;
public interface UserService {
User queryById(Long id);
}
编写Controller
因为现在是远程调动,因此之前是有@Autowired
注解就不可以使用了,因为目前容器中没有userService对象,要使用远程调用的@DubboReference
package cn.itcast.user.controller;
import cn.itcast.user.api.UserService;
import cn.itcast.user.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
//引用远程服务
@DubboReference
private UserService userService;
@GetMapping("/username/{id}")
public String findUserName(@PathVariable("id") Long id) {
return userService.queryUsername(id);
}
}
Application.yml
消费者端的配置文件中仅仅需要配置注册中心地址
server:
port: 18080
spring:
application:
name: user-consumer
logging:
level:
cn.itcast: debug
pattern:
dateformat: HH:mm:ss:SSS
dubbo:
registry:
address: nacos://127.0.0.1:8848
此时访问http://localhost:18080/user/username/1
就可以访问当相关的内容了
注意,在消费者和服务者两个服务中都需要有UserService接口,并且两个接口的名字和里面的方法定义都需要一模一样在才可以实现远程方法调用
2.5 代码优化
通过上面的代码我们看到,在消费者和提供者两个模块中都需要UserService的接口,在编写代码的时候,我们很可能只修改了一处的代码,而忘记修改另一处导致远程方法调用失败。实际上,我们可以将这些公共的代码,比如实体类和接口等抽取到一个独立的模块user-api
,其他模块需要的话只需要引入user-api
的Maven依赖即可。
将接口抽取为独立模块,并且把接口有关的domain都放到这个模块中
- 创建user-api模块引入依赖
- 将UserService接口和User对象导入user-api模块下
- User对象实现序列化接口
实现序列化的原因是因为数据在网络上传输是以二进制的方式进行传输的
创建一个新模块user-api
,将实体类对象和UserService接口拷贝到这个模块下。目录结构如下
在消费者和提供者的pom文件中添加对user-api
的依赖
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>user-apis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
删除消费者和提供者中的实体类和UserService
接口相关代码
3. Dubbo高级特性
3.1 超时重试
消费者在调用提供者的时候发生了阻塞,等待的情况,这个时候消费者会一直等待下去。如果此时请求比较多,那么在消费者这里积攒的请求就越来越多,导致请求大量堆积,引发雪崩现象。Dubbo使用超时机制来解决这个问题,在配置文件中设置Timeout属性,默认值为1秒
此外,Dubbo还提供了重试机制,当一次请求超时时,会重新发送请求。默认情况下,一旦超时,Dubbo会重新发送两次请求到提供者。也可以通过配置文件取消重试机制
user-consumer模块中添加配置信息
dubbo:
registry:
address: nacos://127.0.0.1:8848
consumer:
timeout: 3000
retries: 0
一般情况下我们不使用重试机制,原因是我们进行查询数据的时候其实是可以使用重试机制的。但是一旦涉及到插入数据,那么一旦超时,Dubbo会再次发送两次请求,导致最后本来应该插入一条数据,实际上插入了三条数据。
3.2 启动检查
为了保障服务的正常可用,Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常
在正式环境这是很有必要的一项配置,可以保证整个调用链路的平稳运行
在开发时,往往会存在没有提供者的情况。由于启动检查的原因,可能导致开发测试出现问题
可以通过check=false
关闭
user-consumer模块中添加配置信息
dubbo:
registry:
address: nacos://127.0.0.1:8848
consumer:
check: false
3.4、多版本
灰度发布:当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。Dubbo提供了提供者多版本的支持,平滑处理项目功能升级部署
user-provider定义新的服务实现类UserServiceImpl2
,指定版本
@DubboService(version = “2.0.0”)
public class UserServiceImpl2 implements UserService {
…………
}
消费者在调用服务的时候指定版本
@RestController
@RequestMapping("/user")
public class UserController {
//引用远程服务
@DubboReference(version = "2.0.0")
private UserService userService;
………
}
3.5 负载均衡
在集群部署时,Dubbo提供了4种负载均衡策略,帮助消费者找到最优提供者并调用
-
Random :按权重随机,默认值。按权重设置随机概率。
-
RoundRobin :按权重轮询
随机轮询和按权重轮询的思想是,首先将所有的权重求和,如上图权重之和为10。然后每一个提供者根据权重有一个对应的区间,如001为[1,2],002为[3,4,5],003为[6,7,8,9,10]。
随机轮询是从[1,10]中随机产生整数,根据整数位于的区间来选择调用哪一个提供者
按权重轮询是从1开始到10,前两次为001提供者,后面3次为002提供者,后面5次为003提供者
-
LeastActive:最少活跃调用数,相同活跃数的随机。
这种方式每次都选择调用量最小的提供者服务 -
ConsistentHash:一致性 Hash,相同参数的请求总是发到同一提供者。
3.6 小结
- 启动检查:消费者在启动的时候检查提供者是否可用,如果不可用则抛出异常
- 多版本:Dubbo支持灰度发布,通过在消费者和提供者代码中指定版本号来实现
- 超时和重试:为了防止雪崩,Dubbo默认1秒钟超时;默认情况下超时后Dubbo会再次向提供者发送两次请求
- 负载均衡:四种策略
4. Dubbo整合Spring Cloud
4.1 为什么需要Dubbo
- Feign基于HTTP协议 ,在高并发场景下性能不够理想,容易成为性能瓶颈
- Dubbo基于RPC协议,属于传输层协议
- Dubbo默认使用Netty构造TCP长连接方式通信,性能较高
4.2 整合架构
将Dubbo集成至SpringCloud主要是替换Ribbo或者Feign实现远程调用。加入Dubbo后,整体的架构如下:
4.3 入门案例
需求:根据订单Id查询订单的同时将订单所属用户信息一并查出
模块分析
4.3.1 Dubbo-api模块
抽取Dubbo-api模块,添加相关依赖和接口
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>dubbo-domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
package cn.itcast.dubbo.api;
import cn.itcast.dubbo.domain.User;
public interface UserService {
User queryById(Long id);
}
4.3.2 Dubbo-domain模块
在该模块中保存实体类对象
@Data
public class Order implements Serializable {
private Long id;
private Long price;
private String name;
private Integer num;
private Long userId;
private User user;
}
@Data
public class User implements Serializable {
private Long id;
private String username;
private String address;
}
4.3.2 订单服务模块
引入依赖
<!--nacos注册中心的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--springcloud alibaba dubbo依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
修改Controller中方法
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@DubboReference
private UserService userService;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
Order order = orderService.queryOrderById(orderId);
Long userId = order.getUserId();
User user = userService.queryById(userId);
order.setUser(user);
return order;
}
}
4.3.3 用户服务模块
引入依赖
<!--nacos注册中心的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--springcloud alibaba dubbo依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
修改Service实现类
@DubboService
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return userMapper.findById(id);
}
}
至此,代码改造完毕。
注意:在application.yml文件中一定要指定服务名称,否则会报错