SpringBoot 整合 Dubbo
Dubbo的介绍
Dubbo是什么?
Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的
RPC
远程服务调用方案,以及SAO
服务治理方案。简单来说,dubbo就是一个微服务框架。其核心功能包含:
- 远程通讯:提供了多种基于长连接的
NIO
框架抽象封装,包括多线程模型,序列化,以及"请求-响应" 模式的信息交换方式。- 集群容错:提供了基于接口方法的透明远程调用,包含多协议支持、以及软负载均衡、失败容错、地址路由、动态配置等集群支持。
- 自动发现:基于注册中心目录服务,使服务消费方可以动态的查找服务提供方,使地址透明,使服务提供方可以平滑的增加或减少服务器。
Dubbo能做什么?
- 透明化的远程方法调用:通过将公共的接口抽取出来,实现向调用本地方法一样调用远程方法,只需要少量的配置,没有任何API侵入。
- 软负载均衡及容错机制:dubbo默认提供了4种负载均衡策略,Random LoadBalance、RoundRobin LoadBalance、LeastActive LoadBalance、ConsistentHash LoadBalance
- 服务的自动注册与发现:服务的提供方不在写死,而是在注册中心基于接口名查询到服务提供者的IP地址,调用获取到的IP的服务提供方的相应服务;并且能够平滑的添加或删除服务节点。
Dubbo架构图解
组件介绍:
Container:服务运行容器。
Provider:暴露服务的服务提供方。
Registry:服务注册与发现的注册中心。
Consumer:远程调用服务的服务消费方。
Monitor:统计服务调用情况的监控中心
流程说明:
- start:服务容器负责启动、加载、运行服务提供者。
- register:服务提供者启动时,向注册中心注册自己提供的服务。
- subsribe:服务消费者启动时,去注册中心订阅自己所需要的服务。
- notify:注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更信息给消费者。
- invoke:服务消费者从提供者地址列表中,基于负载均衡算法,选取一台服务提供者进行调用,若调用失败,则选取另一台。
- count:服务消费者和提供者,在内存中累计调用次数和调用时间,定时发送一次统计数据到监控中心。
其中蓝色虚线的
init
属于初始化流程;红色虚线的async
属于异步调用;红色实线的sync
属于同步调用
Dubbo的简单使用
本文采用Dubbo与Zookeeper、SpringBoot框架的整合
准备工作
SpringBoot为
2.3.0.RELEASE
版本dubbo为
2.7.7
版本zookeeper服务程序为
3.6.1
版本
项目结构:
主要分为三大模块:
gmall-interface:存放公共接口与实体类对象
boot-user-service-provider:服务提供者
boot-order-service-consumer:服务消费者
父工程依赖:
主要用于规范依赖版本 以及管理 各个功能模块
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.akieay.dubbo</groupId>
<artifactId>springboot-dubbo</artifactId>
<name>springboot-dubbo</name>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>gmall-interface</module>
<module>boot-user-service-provider</module>
<module>boot-order-service-consumer</module>
</modules>
<properties>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
<dubbo.version>2.7.7</dubbo.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Apache Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- 跳过单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
通用模块抽取
/**
* @author akieay
*/
public class UserAddress implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String userId;
private String address;
private String name;
public UserAddress(Integer id, String userId, String address, String name, String mobile) {
this.id = id;
this.userId = userId;
this.address = address;
this.name = name;
this.mobile = mobile;
}
private String mobile;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
import com.akieay.dubbo.gmall.entity.UserAddress;
import java.util.List;
/**
* @author akieay
*/
public interface OrderService {
List<UserAddress> initOrder(String userId);
}
import com.akieay.dubbo.gmall.entity.UserAddress;
import java.util.List;
/**
* @author akieay
*/
public interface UserService {
List<UserAddress> getUserAddressList(String userId);
}
服务提供者模块
依赖:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.akieay.dubbo</groupId>
<artifactId>springboot-dubbo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.akieay.dubbo</groupId>
<artifactId>boot-user-service-provider</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<!--dubbo客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--zookeeper-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>com.akieay.dubbo</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
配置文件:application.yml
server:
port: 8002
spring:
application:
name: user-service-provider
dubbo:
application:
id: user-service-provider
name: user-service-provider
registry:
id: zookeeper
address: zookeeper://192.168.80.128:2181
protocol:
id: dubbo
name: dubbo
port: -1
# monitor:
# protocol: registry
scan:
base-packages: com.akieay.dubbo.user.service
config-center:
timeout: 25000
debug: true
启动类配置
/**
* @author akieay
* @EnableDubbo: 开启基于注解的dubbo功能
*/
@SpringBootApplication
@EnableDubbo(scanBasePackages = {"com.akieay.dubbo.user.service"})
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
服务提供者:
import com.akieay.dubbo.gmall.entity.UserAddress;
import com.akieay.dubbo.gmall.service.UserService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* @author akieay
*/
@DubboService(interfaceClass = UserService.class)
@Service
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress userAddress1 = new UserAddress(1, "1", "测试地址一深圳宝安区", "小李", "0736-25347358");
UserAddress userAddress2 = new UserAddress(2, "1", "测试地址—深圳龙华区", "小王", "0736-54682535");
return Arrays.asList(userAddress1, userAddress2);
}
}
服务消费者模块
依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.akieay.dubbo</groupId>
<artifactId>springboot-dubbo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.akieay.dubbo</groupId>
<artifactId>boot-order-service-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<!--dubbo客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<!--zookeeper-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>com.akieay.dubbo</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
yml配置
server:
port: 8001
dubbo:
application:
id: order-service-consumer
name: order-service-consumer
registry:
id: zookeeper
address: zookeeper://192.168.80.128:2181
client: curator
protocol:
id: dubbo
name: dubbo
port: -1
# monitor:
# protocol: registry
scan:
base-packages: com.akieay.dubbo.order.service
config-center:
timeout: 25000
debug: true
启动类配置
/**
* @author akieay
*/
@EnableDubbo
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
服务消费者:
import com.akieay.dubbo.gmall.entity.UserAddress;
import com.akieay.dubbo.gmall.service.OrderService;
import com.akieay.dubbo.gmall.service.UserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author akieay
*/
@Service
public class OrderServiceImpl implements OrderService {
@DubboReference(check = false, retries = 0 )
private UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
List<UserAddress> addressList = userService.getUserAddressList(userId);
return addressList;
}
}
import com.akieay.dubbo.gmall.entity.UserAddress;
import com.akieay.dubbo.gmall.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @author akieay
*/
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/initOrder")
public List<UserAddress> initOrder(@RequestParam("uid") String userId){
return orderService.initOrder(userId);
}
}
测试
启动服务提供者模块,然后再启动服务消费者模块,调用服务消费者的测试接口,即可看到 消费者 通过rpc远程调用了 提供者的 UserServiceImpl 的 getUserAddressList 方法
扩展示例
配置来源
首先,从Dubbo支持的配置来源说起,默认有四种配置来源:
- JVM System Properties,-D参数
- Externalized Configuration,外部化配置
- ServiceConfig、ReferenceConfig等编程接口采集的配置
- 本地配置文件dubbo.properties
覆盖关系
启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止
Spring
初始化完成,以便上线时,能及时发现问题,默认check="true"
可以通过
check="false"
关闭检查,比如,测试时,有些服务不关心,或者出现循环依赖,必须有一方先启动。另外,如果你的
Spring
容器是懒加载的,或者通过API
编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果check="false"
,总是会返回引用,当服务恢复时,能自动连上。
示例:
关闭某个服务的启动时检查 (没有提供者时报错):
[spring配置文件方式]
<dubbo:reference interface="com.akieay.dubbo.gmall.service.UserService" check="false" />
[注解方式]
@DubboReference(check = false)
private UserService userService;
关闭所有服务的启动时检查 (没有提供者时报错):
<dubbo:consumer check="false" />
关闭注册中心启动时检查 (注册订阅失败时报错):
<dubbo:registry check="false" />
通过 dubbo.properties
dubbo.reference.com.foo.BarService.check=false
dubbo.reference.check=false
dubbo.consumer.check=false
dubbo.registry.check=false
通过 -D 参数
java -Ddubbo.reference.com.foo.BarService.check=false
java -Ddubbo.reference.check=false
java -Ddubbo.consumer.check=false
java -Ddubbo.registry.check=false
配置的含义
dubbo.reference.check=false
,强制改变所有 reference 的 check 值,就算配置中有声明,也会被覆盖。
dubbo.consumer.check=false
,是设置 check 的缺省值,如果配置中有显式的声明,如:<dubbo:reference check="true"/>
,不会受影响。
dubbo.registry.check=false
,前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试。
超时:timeout
远程服务调用超时时间(毫秒) 默认: 1000
<dubbo:reference= "com.akieay.dubbo.gmall.service.UserService" id="userService" timeout= "5000">
<dubbo:method name= "getUserAddressList" timeout="1000"></dubbo:method>
</dubbo:reference>
@DubboReference(check = false, timeout = 1000)
private UserService userService;
匹配规则:
- 精确优先:(方法级优先,接口次之,全局配置再次之)
- 消费者设置优先,(如果级别一样,则消费方优先,服务方次之)
重试次数: retries [不包含第一次调用]
多个服务提供者时,第一个服务提供者失败,可以重试其它的服务提供者
不包含第一次调用,0表示不重试
@DubboReference(check = false, timeout = 1000, retries = 0 )
private UserService userService;
幂等(设置重试次数)【查询、删除、修改】 非幂等(不能设置重试次数)【新增】
幂等:方法的多次执行结果 与 单次执行结果一致:即在条件相同的情况下,方法执行多次产生的结果 与 执行一次相同;
非幂等:方法执行多次结果 与 单次执行的结果不一致
多版本
当一个接口实现,出现不兼容升级时,可以用版本号过度,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<dubbo:service interface="com.akieay.dubbo.gmall.service.UserService" version="1.0.0" />
@DubboService(interfaceClass = UserService.class, version = "1.0.0")
新版本服务提供者配置:
<dubbo:service interface="com.akieay.dubbo.gmall.service.UserService" version="2.0.0" />
@DubboService(interfaceClass = UserService.class, version = "2.0.0")
老版本服务消费者配置:
<dubbo:reference id="userService" interface="com.akieay.dubbo.gmall.service.UserService" version="1.0.0" />
@DubboReference(check = false, timeout = 1000, retries = 0, version = "1.0.0")
新版本服务消费者配置:
<dubbo:reference id="userService" interface="com.akieay.dubbo.gmall.service.UserService" version="2.0.0" />
@DubboReference(check = false, timeout = 1000, retries = 0, version = "2.0.0")
如果不需要区分版本,可以按照以下的方式配置 [1]:
<dubbo:reference id="userService" interface="com.akieay.dubbo.gmall.service.UserService" version="*" />
本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub ,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
配置
<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />
或
@DubboReference(check = false, timeout = 1000, retries = 0, version = "1.0.0", stub = "com.akieay.dubbo.order.service.UserServiceStub")
private UserService userService;
Stub 实现
- Stub 必须有可传入 Proxy 的构造函数
- 在 interface 旁边放一个 Stub 实现,它实现 UserService接口,并有一个传入远程 UserService实例的构造函数。
/**
* @author akieay
*/
public class UserServiceStub implements UserService {
private final UserService userService;
public UserServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceStub 本地存根执行......");
if(StringUtils.isEmpty(userId)){
return null;
}
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return userService.getUserAddressList(userId);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return null;
}
}
}
springboot中dubbo的三种使用方法
- 1)、导入dubbo-starter,在application.yml 配置属性,使用
@DubboService
【暴露服务】使用@DubboReference
【引用服务】- 2)、保留 dubbo xml配置文件;导入 dubbo-starter,使用 @ImportResource 导入 dubbo 的配置文件即可
- 3)、使用注解API 的方式;将每一个组件手动创建到容器中
高可用
1、zookeeper 宕机 与 dubbo 直连
现象:zookeeper 注册中心宕机了,还可以消费 dubbo 暴露的服务。
原因:
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍然能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
高可用:通过设计,减少不能提供服务的时间;
dubbo 直连:
@DubboReference(url = "192.168.0.164:8002") private UserService userService;
若采用dubbo 直连的方式,即便是没有注册中心【宕掉】,服务消费者也可以通过指定的url 访问服务提供者
2、集群下 dubbo 负载均衡配置
在集群负载均衡时,dubbo 提供了多种负载均衡策略,缺省为 random【默认使用基于权重的随机负载均衡机制】。
负载均衡策略:
- Random LoadBalance
随机,按权重设置随机概率。在一个截面上配置的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者的权重。
如图所示,根据权重将服务的调用概率分成了7等份,服务1占2份,服务2占4份,服务3占一份;在一次请求中,服务1被调用的几率是2/7,服务2被调用的几率是4/7,服务3被调用的几率是1/7;但这并不意味着下一次调用的就是机率高的;有可能服务3会被连续调用几次,而服务2没被调用;但是在大量调用时【如上万次或 10万次】,服务被调用的几率大致如图所示。
- RoundRobin LoadBalance
轮询,按公约后的权重设置轮询比率。存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调用到第二台时就卡在那,久而久之,所有的请求都卡在调到第二台上。
如图所示,该策略下服务的调用将先基于轮询,然后考虑权重;如图,若有7次调用服务的请求,其调用顺序为 1、2、3、1、2、2、2;请求首先采用轮询的方式将所有服务提供者都调用一次,当进入第二轮时,3号服务的权重规定了:在每7次请求中,3号服务只被调用一次。所以,第二轮中将只调用1、2号服务;同理,在第三轮中,1号服务的次数也已经用完,将只调用2号服务。
- LeastActive LoadBalance
最小活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少的请求,因为越慢的提供者的调用前后计数差会越大。
如图所示,在服务调用时,会统计上一次调用服务所用的时间;所用时间少的,将优先被调用;若时间相同,则随机调用。【如上图将会调用服务1】
- ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂掉时,原本发往该提供者的请求,基于虚拟机点,平摊到其它提供者,不会引起剧烈变动。算法参见:http://en.wikipedia.org/wiki/Consistent_hashing。缺省只对第一个参数 Hash,如果要修改,请配置 :
<dubbo:parameter key="hash.arguments" value="0,1" />
缺省用 160 份虚拟节点,如果要修改,请配置:
<dubbo:parameter key="hash.nodes" value="320" />
负载均衡策略的使用配置:
服务端服务级别
<dubbo:service interface="..." loadbalance="roundrobin" />
客户端服务级别
<dubbo:reference interface="..." loadbalance="roundrobin" />
@DubboReference(loadbalance = "random", check = false, timeout = 1000, retries = 0, version = "1.0.0") private UserService userService;
服务端方法级别
<dubbo:service interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:service>
客户端方法级别
<dubbo:reference interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:reference>
更多:https://dubbo.apache.org/zh-cn/docs/user/demos/loadbalance.html
服务降级
什么是服务降级?
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
使用:
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
向注册中心写入动态配置覆盖规则:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
其中:
mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。- 还可以改为
mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
可以通过dubbo的监控中心实现:
其中:屏蔽 相当于第一种方法,即不发起远程调用;容错 相当于第二种方法,即在服务调用失败后返回null值。
集群容错
在集群调用失败时,dubbo 提供了多种容错方案,缺省为 failover 重试。
各节点关系:
- 这里的
Invoker
是Provider
的一个可调用Service
的抽象,Invoker
封装了Provider
地址及Service
接口信息Directory
代表多个Invoker
,可以把它看成List<Invoker>
,但与List
不同的是,它的值可能是动态变化的,比如注册中心推送变更Cluster
将Directory
中的多个Invoker
伪装成一个Invoker
,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个Router
负责从多个Invoker
中按路由规则选出子集,比如读写分离,应用隔离等LoadBalance
负责从多个Invoker
中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
集群容错模式
-
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可以通过
retries="2
来设置重试 次数(不含第一次调用)。重试次数配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference> <dubbo:method name="findFoo" retries="2" /> </dubbo:reference>
-
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等的写操作,比如新增记录。
-
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
-
Failback Cluster
失败自动恢复,后台记录失败的请求,定时重发。通常用于消息通知操作。
-
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可以通过
forks="2"
来设置最大并行数。 -
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集群模式配置
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
整合 Hystrix
Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
配置 spring-cloud-starter-netflix-hystrix
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
然后再Application 启动类上添加 @EnableHystrix 注解,开启基于注册的 hystrix
@EnableHystrix
public class UserApplication8002 {
使用案例:
服务提供方
导入依赖,并开启基于注解的 hystrix【上面已给出示例】
在需要启用容错的方法上加上 @HystrixCommand 注解,如下:
public class UserServiceImpl implements UserService {
@HystrixCommand
@Override
public List<UserAddress> getUserAddressList(String userId) {
服务消费方
导入依赖,并开启基于注解的 hystrix【上面已给出示例】
在服务调用方加上 @HystrixCommand 注解,并编写服务降级回调方法
@Service
public class OrderServiceImpl implements OrderService {
@DubboReference(loadbalance = "random", check = false, timeout = 1000, retries = 0, version = "1.0.0")
private UserService userService;
/**
*
* @param userId
* @return
* defaultFallback: 服务降级回调方法
*/
@HystrixCommand(defaultFallback = "initOrderFallBack")
@Override
public List<UserAddress> initOrder(String userId) {
List<UserAddress> addressList = userService.getUserAddressList(userId);
return addressList;
}
/**
* 容错回调方法
* @return
*/
public List<UserAddress> initOrderFallBack(){
return Arrays.asList(new UserAddress(1, "1", "服务容错地址", "服务容错测试", "0736-2208208820"));
}
}
更多使用案例请参考dubbo官方文档:https://dubbo.apache.org/zh-cn/docs/user/quick-start.html
dubbo原理
一、RPC 与 Netty原理
1、RPC 原理
一次完整的 PRC 调用流程(同步调用,异步另说)如下:
- 1)服务消费方(client)以本地调用方式调用服务;
- 2)client stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
- 3)client stub 找到服务地址,并将消息发送到服务端;
- 4)server stub 收到消息后进行解码;
- 5)server stub 根据解码结果调用本地的服务;
- 6)本地服务执行并将结果返回给 server stub;
- 7)server stub 将返回结果打包成消息并发送至消费方;
- 8)client stub 接收到消息,并进行解码;
- 9)服务消费方得到最终结果。
RPC 框架的目标就是要将 2~8 这些步骤都封装起来,这些细节对于用户来说是不可见的。
2、Netty 通信原理
Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它极大地简化了TCP 和 UDP 套接字服务器等网络编程。
NIO 与 BIO
BIO:(Blocking IO),即阻塞式IO,NIO:(Non-Blocking IO),即非阻塞式IO
NIO模型
每一个请求一进来,都需要开一个线程来读取数据,并进行业务逻辑的调用;而且在业务逻辑执行完成之前,线程都是得不到释放的;当大量请求过来时,会有大量的线程在这阻塞等待业务逻辑的完成,所以我们的服务器就不能同时处理大量的请求。
BIO模型
Channel:通道
Selector:一般称为 选择器,也可以翻译为 多路复用器;
Connect(连接就绪)、Accept(接收就绪)、Read(读就绪)、Write(写就绪)
每一个请求过来都可以开启一个通道,Selector 来监听多个通道,当发现某一个通道里面的数据准备好了(比如某一个连接过来了),Selector可以监听状态(Connect、Accept、Read、Write),当某一个状态准备好了,Selector 就可以开一个线程去执行事件对应的处理。
Netty就是基于BIO实现的
Netty的基本原理
如图所示:
首先Netty 服务器启动,并绑定上一个监听端口(比如dubbo的20880),这样所有给这个端口发送的数据Netty 服务器都能够收到了;
1)服务器启动初始化 Channel(通道);
2)将 Channel 注册到 Selector(多路复用器)中;
3)Selector 轮询监听 accept(接收就绪)事件,即通道接收数据已经准备就绪了;生成一个任务队列,处理accept事件;
4)处理 accept,建立连接 channel(NioSocketChannel),即生成与客户端连接的通道;
5)将 NioSocketChannnel 注册到另一个 Selector 中,只不过这个 Selector 监听的是 Read与Write 事件;Read:即请求发来的数据已经接收完了,等待读取;Write:即可以给客户端通道里面写响应了;
6)轮询读写事件;
7)处理读写事件;比如读准备就绪了做一个任务,写准备就绪了做一个任务,这些任务都会抛给任务队列,Netty就把这个任务队列执行完;
boos:用来监听来自于绑定的端口的连接的所有准备就绪事件;worker:准备就绪后要做的工作由worker执行;
二、Dubbo原理-框架设计
整体设计
图例说明:
- 图中左边淡蓝色背景(Consumer)的为:服务消费方使用的接口;右边淡绿色背景(Provider)的为:服务提供方使用的接口,位于中轴的为:双方都用到的接口。
- 图中从下至上分为十层,各层均为单向依赖,右边黑色箭头(Depend)代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI;
- 图中绿色小块(Interface)的为扩展接口,蓝色小块(Class)的为实现类,图中只显示用于关联各层的实现类。
- 图中蓝色虚线(init)为初始化过程,即启动时组装链;红色实线(Call)为方法调用过程,即运行时调用链;紫色三角箭头(Inherit)为继承,可以把子类看作父类的同一个节点,线上文字为调用的方法。
各层说明
- service 层:服务层,主要是用户编写的接口,对于用户编程来说主要关心的是这一层;
- config 配置层:对外配置接口,以
ServiceConfig
,ReferenceCondig
为中心,可以直接初始化配置类,也可以通过 Spring 解析配置生成配置类;- proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务端 Skeleton,以
ServiceProxy
为中心,扩展接口为ProxyFactory
;- registry 注册中心层:封装服务地址的注册于发现,以服务 URL 为中心,扩展接口为
RegistryFacory
,Registry
,RegistryService
;- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以
Invoker
为中心,扩展接口为 ``Cluster,
Directory,
Router,
LoadBalance`;- monitor 监控层:RPC 调用次数和调用事件监控,,以
Statistics
为中心,扩展接口为MonitorFactory
,Monitor
,MonitorService
;- protocol 远程调用层:封装 RPC 调用,以
Invocation
,Result
为中心,扩展接口为Protocol
,Invoker
,Exporter
;- exchange 信息交换层:封装请求响应模式,同步转异步,以
Request
,Response
为中心,扩展接口为Exchanger
,ExchangeChannel
,ExchangeClient
,ExchangeServer
;- transport 网络传输层:抽象 mina 和 netty 为统一接口,以
Message
为中心,扩展接口为Channel
,Transporter
,Client
,Server
,Codec
;- serialize 数据序列化层:可复用的一些工具,扩展接口为
Serialization
,ObjectInput
,ObjectOutput
,ThreadPool
三、Dubbo原理-标签解析
主要参考:DubboBeanDefinitionParser.java
DubboNamespaceHandler.java
四、Dubbo原理-服务暴露流程
五、Dubbo原理-服务引用流程
六、Dubbo原理-服务调用流程
更多:请参考https://dubbo.apache.org/zh-cn/docs/user/quick-start.html
案例代码 gitee 地址:https://gitee.com/ak_learning/springboot-dubbo.git