Dubbo原理及使用
Dubbo介绍
Dubbo简介
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
其核心部分包含:
- 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
- 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
- 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
主要功能:
- 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
- 软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
- 服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
架构说明
节点角色说明:
-
Provider: 暴露服务的服务提供方。
-
Consumer: 调用远程服务的服务消费方。
-
Registry: 服务注册与发现的注册中心。
-
Monitor: 统计服务的调用次调和调用时间的监控中心。
- Container: 服务运行容器。
调用关系说明:
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
长连接,指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接,一般需要自己做在线维持。 短连接是指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接,一般银行都使用短连接。
基本的工作流程和原理如下:
- client一个线程调用远程接口,生成一个唯一的ID(比如一段随机字符串,UUID等),Dubbo是使用AtomicLong从0开始累计数字的。
- 将打包的方法调用信息(如调用的接口名称,方法名称,参数值列表等),和处理结果的回调对象callback,全部封装在一起,组成一个对象object。
- 向专门存放调用信息的全局ConcurrentHashMap里面put(ID, object)。
- 将ID和打包的方法调用信息封装成一对象connRequest,使用IoSession.write(connRequest)异步发送出去。
- 当前线程再使用callback的get()方法试图获取远程返回的结果,在get()内部,则使用synchronized获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。
- 服务端接收到请求并处理后,将结果(此结果中包含了前面的ID,即回传)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到ID,再从前面的ConcurrentHashMap里面get(ID),从而找到callback,将方法调用结果设置到callback对象里。
- 监听线程接着使用synchronized获取回调对象callback的锁(因为前面调用过wait(),那个线程已释放callback的锁了),再notifyAll(),唤醒前面处于等待状态的线程继续执行(callback的get()方法继续执行就能拿到调用结果了),至此,整个过程结束。
Dubbo使用
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
-
采用注册中心
下载zookeeper注册中心,下载地址:http://www.apache.org/dyn/closer.cgi/zookeeper/ 下载后解压即可,进入D:\apach-zookeeper-3.4.5\bin,
双击zkServer.cmd启动注册中心服务。
- 定义服务接口: (该接口需单独打包,在服务提供方和消费方共享)
public interface DemoService {
String sayHello(String name);
}
- 在服务提供方实现接口
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}
- 用 Spring 配置声明暴露服务provider.xml
spring.application.name=user-service
#如果指定了spring应用名称,可以缺省dubbo的应用名称,这2个至少要配置1个。缺省dubbo的应用名称时默认值是spring的应用名称
#dubbo.application.name=user-service
//使用multicast广播注册中心暴露服务地址 multicast://224.5.6.7:1234
#注册中心地址
dubbo.registry.address=zookeeper://192.168.1.9:2181
#端口号可以写在address中,也可以单独写。实质是从address中获取的port是null,后面设置的port覆盖了null
#dubbo.registry.port=2181
#协议、ip:port可以写在一起
#也可以分开写
#dubbo.registry.protocol=zookeeper
#dubbo.registry.address=192.168.1.9:2181
#如果是集群,逗号分隔
#dubbo.registry.address=192.168.1.9:2181,192.168.1.10:2181
#指定dubbo使用的协议、端口
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#指定注册到zk上超时时间,ms
dubbo.registry.timeout=10000
#指定实现服务(提供服务)的包
dubbo.scan.base-packages=com.chy.user-service.service
- 加载服务配置
// @EnableDubbo //会扫描所有的包,从中找出dubbo的@Service标注的类
// @DubboComponentScan(basePackages = "com.chy.user-service.service") //只扫描指定的包
@ImportResource(value={"classpath:provider.xml"})
@SpringBootApplication
public class BootUserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceProviderApplication.class, args);
}
}
- 服务消费者引入jar
<!-- api接口的jar包 -->
<dependency>
<groupId>com.chy.mall</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- dubbo的依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.6</version>
</dependency>
<!-- zk的依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.6</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
- 消费者配置文件
#应用名称
spring.application.name=order-service
#dubbo.application.name=order-service
#注册中心地址
dubbo.registry.address=zookeeper://192.168.1.9:2181
#dubbo.registry.port=2181
#协议、端口
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#连接zk的超时时间,ms
dubbo.registry.timeout=10000
#启动应用时是否检查注册中心上有没有依赖的服务,默认true
#dubbo.consumer.check=false
- 消费者调用
@Controller
public class OrderController {
@Reference(interfaceClass = ICircleService.class, cluster = "failover", version="1.0.0",retries = 1,parameters = {"addCircle.cluster", "failfast"})) //注入要调用的服务 配置retries参数,设置重试次数为0
private UserService userService;
@RequestMapping("/user/{id}")
@ResponseBody
public User getUser(@PathVariable Integer id){
//调用服务
User user= userService.findUserById(id);
return user;
}
}
附上各种容错模式机制说明: Failover Cluster 失败自动切换,当出现失败,重试其它服务器。(缺省) 通常用于读操作,但重试会带来更长延迟。 可通过retries="2"来设置重试次数(不含第一次)。
Failfast Cluster 快速失败,只发起一次调用,失败立即报错。 通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster 失败安全,出现异常时,直接忽略。 通常用于写入审计日志等操作。
Failback Cluster 失败自动恢复,后台记录失败请求,定时重发。 通常用于消息通知操作。
Forking Cluster 并行调用多个服务器,只要一个成功即返回。 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。 可通过forks="2"来设置最大并行数。
Broadcast Cluster 广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持) 通常用于通知所有提供者更新缓存或日志等本地资源信息。