Dubbo(进阶)——学习笔记
一、启动时检查
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" />
二、超时时间
由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。尽量将超时时间设置在服务端,因为服务端相对来说更清楚自己接口的性能。
[备注:图片来自网络]
【拓展】
- 服务提供方的配置,通过URL经由注册中心传递给消费方。
- 建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置。
2.1 Dubbo 服务端
xml方式配置服务方超时:
配置域 | 命令(单位:毫秒) |
---|---|
方法配置 | <dubbo:method timeout="1000" ... /> |
接口配置 | <dubbo:service timeout="2000" ... /> |
全局配置 | <dubbo:provider timeout="3000" /> |
接口超时配置:(注解配置)
@Service(timeout = 2000) //这里使用的是 dubbo的 @Service 注解
public class MovieServiceImpl implements MovieService {
//省略服务提供者对服务的具体实现代码
}
【备注】
- 如果超时了则logger.warn打印一个warn日志。服务端的超时设置并不会影响实际的调用过程,就算超时也会执行完所有的处理逻辑。
2.2 Dubbo 消费端
xml方式配置消费方超时:
配置域 | 命令(单位:毫秒) |
---|---|
方法配置 | <dubbo:method timeout="1000" ... /> |
接口配置 | <dubbo:reference timeout="2000" ... /> |
全局配置 | <dubbo:consumer timeout="3000" /> |
接口类超时配置:(注解配置)
@Service //这里使用的是 spring的 @Service 注解
public class OrderService {
@Reference(timeout = 1000) //使用dubbo提供的reference注解引用远程服务
private MovieService movieService;
//省略具体代码
}
如果消费端去请求服务端的服务,但是服务端的响应的服务阻塞了(阻塞时间大于消费端设置的timeout)则此时服务端和消费端都会报异常;
- 服务端会报异常
java.nio.channels.ClosedChannelException
;(封闭通道异常) - 消费端也会报错
com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout
。(超时异常)
如果服务提供端设置超时时间的优先级高的话:
- 当请求失败是服务端只会打印一个异常的日志信息:
[WARN ] [New I/O server worker #1-1] - [DUBBO] All clients has discontected from /本机地址:20883.
三、重连机制和多版本支持
3.1 重连机制
Dubbo在调用服务不成功时,默认会重试2次。Dubbo的路由机制,会把超时的请求路由到其他机器上,而不是本机尝试,所以 dubbo的重试机制也能一定程度的保证服务的质量。可通过retries 属性
来设置重试次数(不含第一次,如 retries=2 表示总共三次)。
将服务端的maven项目多换几个暴露服务的端口号重新运行服务;
//使用dubbo提供的reference注解引用远程服务
//timeout : 请求超时最大时间(超过就报错)
//retries : 重新请求次数
@Reference(timeout = 1000,retries = 3)
3.2 多版本服务支持
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<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="*" />
四、SpringBoot 整合 dubbo三种方式
4.1 全局配置文件+注解
引入Dubb起步依赖,在application.properties或application.yml中配置属性,使用@Service注解来暴露服务,使用@Reference来引用服务。
缺点:没有方法级配置(dubbo:method标签对应的配置)
4.2 Dubbo配置文件+springboot(推荐)
保留Dubbo配置文件,使用@ImportResource导入dubbo的配置文件即可。
【注意】
- 用该方式时,提供方实现类中@Service可以用Dubbo提供的@Service,也可以用Spring的@Service,如果用Dubbo的@Service,启动类上要有@EnableDubbo。
4.2.1 公共接口部分(public-api)
public interface UserService {
List<Users> getUsersList(Integer uid);
}
4.2.2 服务提供者部分(server-provider)
//使用 spring的 @Service 注解
@Service
public class UserServiceImpl implements UserService {
//具体实现代码省略
}
@ImportResource({"classpath:server-provider.xml"})
@SpringBootApplication
public class ServerProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServerProviderApplication.class, args);
}
}
<?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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--当前应用的名字 -->
<dubbo:application name="server-provider" />
<!--指定注册中心得地址(zookeeper的地址 2181) -->
<dubbo:registry address="zookeeper://192.168.238.66:2181" />
<!--使用 dubbo 协议,将服务暴露在 20880 端口 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 暴露服务
interface :暴露的服务名称到注册中心
ref :引用提供服务模块的IoC容器中的名为userServiceImpl的bean -->
<dubbo:service interface="ccbx.publicapi.service.UserService" ref="userServiceImpl" />
</beans>
4.2.3 消费端部分(server-consumer)
@RestController
public class UserController {
//从容器中注入依赖对象
@Autowired
private UserService userService;
//REST 风格请求访问
@RequestMapping("getuser/{uid}")
public List<Users> getUser(@PathVariable("uid") String id){
return userService.getUsersList(Integer.parseInt(id));
}
}
<!--当前应用的名字 -->
<dubbo:application name="server-consumer" />
<!--指定注册中心得地址(zookeeper的地址 2181) -->
<dubbo:registry address="zookeeper://192.168.238.66:2181" />
<!-- 远程过程调用注册中心的服务;
interface :要引用的服务名称
id :bean的唯一标识 -->
<dubbo:reference interface="ccbx.publicapi.service.UserService" id="userService" />
@ImportResource("classpath:server-consumer.xml")
@SpringBootApplication
public class ServerConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerConsumerApplication.class, args);
}
}
4.2.4 查看服务和运行效果
4.3 使用全注解配置
不需要在SpringBoot全局配置文件中做dubbo相关配置,也不用Dubbo配置文件,把所有配置定义在配置类里。
这种方式是将所有组件手动注册到容器中。示例如下:
@Configuration
public class MyDubboConfig {
//<dubbo:application name="server-consumer" />
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("boot-user-service-provider");
return applicationConfig;
}
//<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("127.0.0.1:2181");
return registryConfig;
}
//<dubbo:protocol name="dubbo" port="20882"></dubbo:protocol>
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20882);
return protocolConfig;
}
/**
*<dubbo:service interface="com.java.gmall.service.UserService"
ref="userServiceImpl01" timeout="1000" version="1.0.0">
<dubbo:method name="getUserAddressList" timeout="5000"></dubbo:method>
</dubbo:service>
*/
@Bean
public ServiceConfig<UserService> userServiceConfig(UserService userService){
ServiceConfig<UserService> serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(UserService.class);
serviceConfig.setRef(userService);
//serviceConfig.setVersion("1.0.0");
//配置每一个method的信息
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("getUserAddressList");
methodConfig.setTimeout(5000);
//将method的设置关联到service配置中
List<MethodConfig> methods = new ArrayList<>();
methods.add(methodConfig);
serviceConfig.setMethods(methods);
//ProviderConfig也进行相似的配置
return serviceConfig;
}
}
【注意】
- 使用该方式时,提供方实现类中@Service只能用Dubbo提供的@Service。
五、高可用
5.1 zookeeper 宕机和 dubbo 直连
直连是绕过注册中心(比如zookeeper宕机的情况),直接把提供方的url告诉消费方。(还可以消费 dubbo 暴露的服务)
<dubbo:reference interface="com.java.api.service.UserService"
id="userService"
url="127.0.0.1:20880" >
</dubbo:reference>
【健壮性原因】
- 监控中心宕掉不影响使用,只是丢失部分采样数据。
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务。
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台。
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过
本地缓存
通讯。- 服务提供者无状态,任意一台宕掉后,不影响使用。
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并
无限次重连
等待服务提供者恢复。【高可用】通过设计,减少系统不能提供服务的时间。
5.2 dubbo 负载均衡策略
负载均衡(Load Balance), 其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。负载均衡策略主要用于客户端存在多个提供者时进行选择某个提供者。在集群负载均衡时,Dubbo 提供了多种均衡策略(包括随机
、轮询
、最少活跃调用数
、一致性Hash
),默认为random随机调用。
策略 | 说明 |
---|---|
random | 随机算法,是 Dubbo 默认的负载均衡算法。存在服务堆积问题。 |
roundrobin | 轮询算法。按照设定好的权重依次进行调度。 |
leastactive | 最少活跃度调度算法。即被调度的次数越少,其优选级就越高,被调度到的机率就越高。 |
consistenthash | 一致性 hash 算法。对于相同参数的请求,其会被路由到相同的提供者。 |
【拓展】
- 配置负载均衡策略时,策略名称是对应的负载均衡子类中的NAME属性,这些子类都是继承AbstractLoadBalance的。如:
RandomLoadBalance
、RoundRobinLoadBalance
、LeastActiveLoadBalance
、ConsistentHashLoadBalance
。
5.2.1 服务器端指定
<!-- 接口级别 -->
<dubbo:service interface="ccbx.publicapi.service.UserService"
ref="userServiceImpl"
loadbalance="roundrobin" />
<!-- 方法级别也可以指定(写法跟接口级别配置相同) -->
5.2.2 消费端指定
<!-- 接口级别 -->
<dubbo:reference interface="ccbx.publicapi.service.UserService"
id="userService"
loadbalance="roundrobin" />
<!-- 方法级别也可以指定(写法跟接口级别配置相同) -->
5.3 整合 hystrix,服务熔断与降级处理
5.3.1 服务降级
dubbo的服务降级包含两种常见,屏蔽服务
和服务容错
。在dubbo-admin服务信息消费者界面可以看到有屏蔽和容错功能。
- 屏蔽功能 :是将该服务直接进行屏蔽,消费者将不再调用服务提供者工程,接口直接返回null 空对象。比如在一些服务器压力比较大的情况下,可以 选择屏蔽一些非关键服务接口以保证服务提供者工程减少请求压力。
- 容错功能 :如果接口处理时不稳定,没有容错时,会有时候正常,有时候超时报错。当开启容错后,当出现超时时,消费者端将会以 null 作为结果返回,不在出现远程接口超时报错现象,给用户良好的体验。
5.3.2 幂等性
幂等性是指对同一个操作接口的一次或多次调用,结果是完全一致的。
幂等性是分布式环境下常见的问题;幂等性指的是多次操作,结果是一致的。(多次操作数据库数据是一致的。)
5.3.3 hystrix 实现服务容错
dubbo的容错机制主要有6类:
容错机制 | 说明 |
---|---|
Failover Cluster | 【默认】失败自动切换重试,当出现失败,重试其它服务器。 但重试会带来更长延迟。 可通过 retries=“2” 来设置重试次数(不含第一次)。 |
Failfast Cluster | 快速失败,只发起一次调用,失败立即报错。 通常用于非幂等性的写操作,比如新增记录。 |
Failsafe Cluster | 失败安全,出现异常时,直接忽略。 通常用于写入审计日志等操作。 |
Failback Cluster | 失败自动恢复,后台记录失败请求,定时重发。 通常用于消息通知操作。 |
Forking Cluster | 并行调用多个服务器,只要一个成功即返回。 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。 可通过 forks=“2” 来设置最大并行数。 |
Broadcast Cluster | 广播调用所有提供者,逐个调用,任意一台报错则报错 。 通常用于通知所有提供者更新缓存或日志等本地资源信息。 |
集群模式配置:
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
5.3.4 整合 hystrix
Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障
提供更强大的容错能力。Hystrix 具备以下功能:
- 对来自依赖的延迟和故障进行防护和控制——这些依赖通常都是通过网络访问的
- 阻止故障的连锁反应
- 快速失败并迅速恢复
- 回退并优雅降级
- 提供近实时的监控与告警
1 、配置 spring-cloud-starter-netflix-hystrix
<!-- 使用hystrix来提供服务容错能力 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
2、在服务提供方的服务接口上加上@HystrixCommand
@HystrixCommand 注解
是方法级别的,在需要捕获的方法上加上该注解;
@Service(version = "2.0.0")
public class HelloServiceImpl implements HelloService {
@HystrixCommand
@Override
public String sayHello(String str) {
throw new RuntimeException("Exception to show hystrix enabled.");
}
}
3、在服务消费方发起远程调用的接口上也加@HystrixComman
public class TestController{
//使用该注解时,当远程调用服务失败时,会自动执行 fallbackMethod 属性指定的方法
@HystrixCommand(fallbackMethod = "errorMethod")
public String myMethod(String param) throw Exception{
List<User> list = userService.getUserList(Integer uid);
return "userlist";
}
//调用服务异常执行方法
private String errorMethod(String str){
logger.info("调用 getUserList 服务发生异常");
return "在消费端返回异常信息。";
}
}
4、在服务提供者和服务消费者的启动类上加上 @EnableHystrix 注解
@SpringBootApplication
@EnableHystrix //启用 hystrix
public class ProviderApplication {
//省略代码
}