Dubbo入门
Dubbo架构
Apache Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
async
表示异步,sync
表示同步
节点角色说明
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用关系说明
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo 架构的特点
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。
连通性
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性
注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
升级性
官方文档上有对未来扩展的设想和架构图。
Dubbo 解决的功能需求
当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。
当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清关系。
接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? 为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阈值,记录此时的访问量,再以此访问量乘以机器数反推总容量。
Dubbo 入门构建
构建接口中心 Dubbo-common
根据 Dubbo 服务化最佳实践:
建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。
如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需在 Spring 加载过程中引用此配置即可。配置建议放在模块的包目录下,以免冲突,如:
com/alibaba/china/xxx/dubbo-reference.xml
。
创建一个实体类
import java.io.Serializable;
/**
* 因为需要在网络中传输,需要支持序列化,实现 Serializable 接口
*/
public class Employee implements Serializable {
private static final long serialVersionUID = -8290432070775741887L;
/**
* 雇员ID
*/
private Integer empId;
/**
* 雇员名称
*/
private String empName;
/**
* 雇员工资
*/
private Double salary;
//省略构造方法和 getter 和 setter 方法
}
创建调用服务统一接口
import com.xm.dubbo.entity.Employee;
import java.util.List;
public interface EmployeeRemoteService {
List<Employee> getEmployeeByConditionRemote(Employee employee);
}
这个类的 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xm.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
构建服务提供中心 Dubbo-provider
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xm.dubbo</groupId>
<artifactId>dubbo-provider</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.8</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>com.xm.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
创建接口实现类如下
//这里需要导入接口和实体类
import com.xm.dubbo.api.EmployeeRemoteService;
import com.xm.dubbo.entity.Employee;
import java.util.ArrayList;
import java.util.List;
public class EmployeeServiceImpl implements EmployeeRemoteService {
@Override
public List<Employee> getEmployeeByConditionRemote(Employee employee) {
System.out.println(employee);
List<Employee> empList = new ArrayList<>();
empList.add(new Employee(111, "empName111", 111.11));
empList.add(new Employee(222, "empName222", 222.22));
empList.add(new Employee(333, "empName333", 333.33));
return empList;
}
}
用 Spring 配置声明暴露服务
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!--1. 配置 Dubbo 提供方应用名称-->
<dubbo:application name="xm-dubbo-provider"/>
<!--2. 指定注册中心位置 -->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"/>
<!--3. 用dubbo协议在20880端口暴露服务-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--4. 声明需要暴露的服务接口 -->
<dubbo:service interface="com.xm.dubbo.api.EmployeeRemoteService" ref="employeeServiceImpl"/>
<!--5. 将实现类关联到接口 -->
<bean id="employeeServiceImpl" class="com.xm.dubbo.service.impl.EmployeeServiceImpl"/>
</beans>
加载 Spring 配置,启动服务提供中心
需要注意应该提前启动 Zookeeper,这里 Zookeeper 已集群启动
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class Provider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
System.in.read();
}
}
启动之后可以发现 Zookpper 中多了一个 dubbo
节点。
构建服务消费者 Dubbo-consumer
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xm</groupId>
<artifactId>dubbo-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.xm.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.8</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.3.0</version>
</dependency>
</dependencies>
</project>
通过 Spring 配置引用远程服务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!--Dubbo配置:消费方应用名 -->
<dubbo:application name="xm-dubbo-consumer" />
<!--Dubbo配置:指定Dubbo注册中心 -->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183" />
<!--Dubbo配置:引用接口代表的服务,生成远程服务代理 -->
<dubbo:reference id="employeeRemoteService" interface="com.xm.dubbo.api.EmployeeRemoteService" />
</beans>
加载Spring配置,并调用远程服务
import com.xm.dubbo.api.EmployeeRemoteService;
import com.xm.dubbo.entity.Employee;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class Consumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
context.start();
//获取远程代理服务
EmployeeRemoteService service = (EmployeeRemoteService) context.getBean("employeeRemoteService");
//执行远程调用
List<Employee> results = service.getEmployeeByConditionRemote(new Employee(1, "silverming", 10000.0));
for ( Employee employ:results) {
System.out.println(employ);
}
}
}
运行之后可以看到调用实现类的方法执行。
使用SpringBoot 整合 Dubbo
使用注解的方式
- 导入 dubbo-starter
- 在 application 配置文件中配置属性
- 使用
@EnableDubbo
开启 Dubbo 注解功能,使用@Service
暴露服务,使用@Reference
调用服务
创建统一接口中心
创建实体类
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; // 用户地址
private String userId; // 用户id
private String consignee; // 收货人
private String phoneNum; // 电话号码
private String isDefault; // 是否为默认地址 yes / no
//省略构造方法和getter、setter方法
}
创建两个服务接口
OrderService
:
public interface OrderService {
/**
* 初始化订单
**/
List<UserAddress> initOrder(String userId);
}
UserService
:
public interface UserService {
/**
* 获取用户信息列表
**/
List<UserAddress> getUserAddressList(String userId);
}
pom.xml
依赖如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
创建服务提供者
pom.xml
如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.silverming.dubbo</groupId>
<artifactId>dubbo-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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.silverming.dubbo</groupId>
<artifactId>dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
服务接口实现
//注意导入的是 zookeeper 的 service
import com.alibaba.dubbo.config.annotation.Service;
@Component
@Service
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "河北省衡水市", "1", "carson","12345678911","Y");
UserAddress address2 = new UserAddress(1, "山东省德州市", "2", "eason", "4562144", "Y");
return Arrays.asList(address1,address2);
}
}
服务提供方使用
@Service
注解暴露服务
服务提供者配置文件
dubbo:
application:
name: boot-order-service-provider
registry:
protocol: zookeeper
address: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
server:
port: 8080
创建启动类
@SpringBootApplication
@EnableDubbo //开启 Dubbo 注解功能
public class BootUserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceProviderApplication.class,args);
}
}
创建服务消费方
pom.xml
文件如下:与服务提供方一样
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>com.silverming.dubbo</groupId>
<artifactId>dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
实现订单服务接口
订单服务接口会调用到用户服务接口,代码如下:
@Service //暴露服务
public class OrderServiceImpl implements OrderService {
/**
* @Reference: 远程调用 UserService, 自己会去注册中心去发现
**/
@Reference
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
System.out.println("用户id:"+userId);
// 查询用户的收货地址
List<UserAddress> userAddressList = userService.getUserAddressList(userId);
return userAddressList;
}
}
@Reference
注解用于引用服务
创建控制器
@Controller
public class OrderController {
@Autowired
private OrderService orderService;
@ResponseBody
@RequestMapping("/initOrder")
public List<UserAddress> initOrder(@RequestParam("uid") String userId) {
return orderService.initOrder(userId);
}
}
配置文件
dubbo:
application:
name: boot-order-service-consumer
registry:
protocol: zookeeper
address: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
protocol:
name: dubbo
port: 20881
server:
port: 8081
创建启动类
@EnableDubbo // 开启基于注解的dubbo功能
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
}
}
使用 XML 配置文件的方式
按照上面原始的方式创建 XML 配置文件,然后在启动类使用 @ImportResouorce
注解导入配置文件
使用注解 API 的方式
将每一个组件手动创建到容器中。API 属性与配置项一对一,比如:ApplicationConfig.setName("xxx")
对应 <dubbo:application name="xxx"/>
示例如下,同时还有服务消费端相应的配置。
@Configuration
public class DubboCongig {
@Bean
// 当前应用配置
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
//对应dubbo.application.name
applicationConfig.setName("application.yml");
return applicationConfig;
}
@Bean
// 连接注册中心配置
public RegistryConfig registryConfig() {
RegistryConfig registry = new RegistryConfig();
registry.setProtocol("zookeeper");
registry.setAddress("127.0.0.1:2181");
return registry;
}
@Bean
// 服务提供者暴露服务配置
public ServiceConfig<UserService> serviceConfig(UserService userService) {
ServiceConfig<UserService> service = new ServiceConfig<UserService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
service.setInterface(UserService.class);
service.setRef(userService);
service.setVersion("1.0.0");
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("getUserAddressList");
methodConfig.setTimeout(1000);
//将 Method 设置保存的 Service 配置中
List<MethodConfig> methodConfigList = new ArrayList<>();
methodConfigList.add(methodConfig);
service.setMethods(methodConfigList);
// 暴露及注册服务
service.export();
return service;
}
}
配置完之后,在启动类使用 @DubboComponentScan
或者 @EnableDubbo(scanBasePackages = "com.silverming.config")
注解。
Dubbo 配置
Dubbo 提供了 XML 、属性、API、注解等多种配置方式,可以通过官方文档查看配置属性有哪些。配置的优先级由高到低如下:
既虚拟机启动参数配置、外部化配置、Spring/API 配置、配置文件配置优先级依次降低。
相关常用配置标签
标签 | 用途 | 解释 |
---|---|---|
<dubbo:service> |
服务配置 | 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心 |
<dubbo:reference/> |
引用配置 | 用于创建一个远程服务代理,一个引用可以指向多个注册中心 |
<dubbo:protocol/> |
协议配置 | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受 |
<dubbo:application/> |
应用配置 | 用于配置当前应用信息,不管该应用是提供者还是消费者 |
<dubbo:module/ |
模块配置 | 用于配置当前模块信息,可选 |
<dubbo:registry/> |
注册中心配置 | 用于配置连接注册中心相关信息 |
<dubbo:monitor/> |
监控中心配置 | 用于配置连接监控中心相关信息,可选 |
<dubbo:provider/> |
提供方配置 | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选 |
<dubbo:consumer/> |
消费方配置 | 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选 |
<dubbo:method/> |
方法配置 | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息 |
<dubbo:argument/> |
参数配置 | 用于指定方法参数配置 |
不同粒度配置的覆盖关系
以 timeout
为例,下图显示了配置的查找顺序,其它 retries
, loadbalance
, actives
等类似:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。
建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置)。
理论上 ReferenceConfig 中除了
interface
这一项,其他所有配置项都可以缺省不配置,框架会自动使用ConsumerConfig,ServiceConfig, ProviderConfig 等提供的缺省配置。
配置相关机制
启动时检查
Dubbo 默认会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
。
可以通过 check="false"
关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
另外,如果 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false"
,总是会返回引用,当服务恢复时,能自动连上。
通过 spring 配置文件关闭
关闭某个服务的启动时检查 (没有提供者时报错):
<dubbo:reference interface="com.foo.BarService" check="false" />
关闭所有服务的启动时检查 (没有提供者时报错):
<dubbo:consumer check="false" />
关闭注册中心启动时检查 (注册订阅失败时报错):
<dubbo:registry check="false" />
此外还可以通过 dubbo.properties
,虚拟机 -D
参数关闭。
配置的含义
dubbo.reference.check=false
,强制改变所有 reference
的 check 值,就算配置中有声明,也会被覆盖。
dubbo.consumer.check=false
,是设置 check
的缺省值,如果配置中有显式的声明,如:<dubbo:reference check="true"/>
,不会受影响。
dubbo.registry.check=false
,前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试。
配置的相关参数
timeout 超时时间
远程服务调用超时时间(毫秒),大多数配置默认情况下是 1000 毫秒。
retries 重试次数
可以通过 retries 参数配置重试次数,不包含第一次调用,0 代表不重试。
对于幂等的操作,可以设置重试次数(如查询、删除、修改),对于非幂等的操作,不能设置重试(如增加)。
version 多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
新版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
老版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
新版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
如果不需要区分版本,可以按照以下的方式配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
stub 本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
在 spring 配置文件中按以下方式配置:对应注解 @Service(stub = "com.silverming.service.stub.UserServiceStub")
<dubbo:service interface="com.silverming.service.UserService" stub="true" />
或
<dubbo:service interface="com.silverming.service.UserService" stub="com.silverming.service.stub.UserServiceStub" />
同时,还需要提供相应的 Stub 实现:
package com.silverming.service.stub;
import java.util.List;
public class UserServiceStub implements UserService {
private final UserService userService;
//构造函数传入真正的远程代理对象
public UserServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public List<UserAddress> getUserAddressList(String userId) {
// 此代码在客户端执行, 可以在客户端做 ThreadLocal 本地缓存,或预先验证参数是否合法,等等
System.out.println("本地存根调用");
try {
return userService.getUserAddressList(userId);
} catch (Exception e) {
// 可以容错,可以做任何AOP拦截事项
return null;
}
}
}
Dubbo 高可用配置
Zookeeper 宕机与 Dubbo 直连
主要与 Dubbo 的健壮性挂钩,Zookeeper 注册中心宕机后,服务提供者和服务消费者仍能够通过本地缓存进行通讯。
Dubbo 健壮性包括以下几点:
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
另外,在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
如果是线上需求需要点对点,可在 <dubbo:reference>
中配置 url 指向提供者,将绕过注册中心,多个地址用分号隔开,配置如下 :
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
也可以通过 JVM 参数指定,在 JVM 启动参数中加入 -D
参数映射服务地址:
java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890
负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random
随机调用。
当有多个服务提供者时,默认情况下使用 random
并且权重相等随机调用。
负载均衡策略
Random LoadBalance
- 随机,按权重设置随机概率。
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
- 轮询,按公约后的权重设置轮询比率。
- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
- 一致性 Hash,相同参数的请求总是发到同一提供者。
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
- 缺省只对第一个参数 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" />
服务端方法级别
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
客户端方法级别
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
服务降级
当服务器压力剧增的情况下,根据实际业务流量及情况,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。
可以通过 mock
参数设置服务降级,与 stub 相似,设为true,表示使用缺省 Mock 类名,即:接口名 + Mock 后缀,服务接口调用失败 Mock 实现类,该 Mock 类必须有一个无参构造函数,与 Stub 的区别在于,Stub 总是被执行,而 Mock 只在出现非业务异常(比如超时,网络异常等)时执行,Stub 在远程调用之前执行,Mock 在远程调用后执行。
服务容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。Dubbo 提供了以下几种容错模式:
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 依赖
在 provider 和 consumer 中导入 Hystrix 依赖,注意版本要和 Spring Boot 兼容
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
启动类配置
@EnableHystrix
在两者的启动类上配置
@EnableHystrix
表示开启 Hystrix。对需要容错的方法使用
@HystrixCommand
注解此注解表示此方法是 hystrix 方法,其中 fallbackMethod 定义回退方法的名称。Spring Cloud 会自动将带有该批注的 Spring bean 包装在连接到 Hystrix 断路器的代理中。断路器计算何时断开和闭合电路,以及在发生故障时应采取的措施。
服务提供者修改示例:
@Component @Service(weight = 700) public class UserServiceImpl implements UserService { @Override @HystrixCommand public List<UserAddress> getUserAddressList(String userId) { System.out.println("服务提供者3"); UserAddress address1 = new UserAddress(1, "河北省衡水市", "1", "carson","12345678911","Y"); UserAddress address2 = new UserAddress(1, "山东省德州市", "2", "eason", "4562144", "Y"); //测试异常 if(Math.random() > 0.5) { throw new RuntimeException(); } return Arrays.asList(address1,address2); } }
服务消费者修改示例:
@Controller public class OrderController { @Autowired private OrderService orderService; @ResponseBody @RequestMapping("/initOrder") @HystrixCommand(fallbackMethod = "fallbackMethod") public List<UserAddress> initOrder(@RequestParam("uid") String userId) { return orderService.initOrder(userId); } //出异常时调用的方法 public List<UserAddress> fallbackMethod(@RequestParam("uid") String userId) { return Arrays.asList(new UserAddress(10,"测试地址","1","测试","测试","Y")); } }
参考资料:
Spring cloud系列十 使用@HystrixCommand使用Hystrix组件及@EnableCircuitBreaker原理介绍