一、基础知识
1、 RPC
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数, 而不用程序员显式编码这个远程调用的细节。即程序员无论是调用用本地的还是远程的函数,本质上编写的调用代码基本相同。
(1)、首先调用方需要有个RPCclient,被调用方需要有 RCPServer,这两个服务用于RPC通信。
(2)、调用者通过RPCClient调用指定方法,RPCClient则将请求封装后(将方法参数值进行二进制序列化),传递给server
(3)、server收到请求后,反序列化参数,恢复成请求原本的形式,然后找到对应的方法进行本地调用。将方法的返回值通过 RPC返回给client
(4)、client收到结果后,将结果返回给调用者作为返回值。
RPC两个核心模块: 通讯, 序列化
决定RPC的效率: 通信效率, 序列化及反序列化效率
RPC框架:如 dubbo, gRPC, Thrift, HSF
2、hadoop的RPC(六、RPC基本原理_51CTO博客_rpc原理)
1)、hdfs的RPC基本流程
hdfs的rpc流程基本如上,其中的关键就是获得NameNode代理对象。
2)、java中的代理方式
(1)静态代理 先定义一个公共接口,里面包括了可以通过RPC调用的方法列表。而且被代理对象以及对象本身都需要实现该接口
(2)动态代理 先定义一个公共接口,里面包括了可以通过RPC调用的方法列表。被代理对象以及对象本身都不需要实现该接口。而是通过匿名内部类+反射的机制实现。hadoop就是使用这种方式
3)、hadoop rpc框架的例子
例子结构:
Server
MyImpl.java server本地执行的方法的具体实现代码
MyInterface.java 可以rpc执行的方法列表
MyRpcServer.java rpc server端
Client
MyRpcClient.java rpc client端
Server:
/*
MyInterface.java
*/
package Server;
import org.apache.hadoop.ipc.VersionedProtocol;
public interface MyInterface extends VersionedProtocol {
public static long versionID = 1001; //这个是标记RPC的client和server对应的标记
public String helloWorld(String name);
}
/*
MyImpl.java
*/
package Server;
import org.apache.hadoop.ipc.ProtocolSignature;
import java.io.IOException;
public class MyImpl implements MyInterface{
/*这是实际目标*/
//重写我们在上面接口自定义的方法
@Override
public String helloWorld(String name) {
return "hello," + name;
}
//返回版本号
@Override
public long getProtocolVersion(String s, long l) throws IOException {
return MyInterface.versionID;
}
//返回签名信息
@Override
public ProtocolSignature getProtocolSignature(String s, long l, int i) throws IOException {
return new ProtocolSignature(MyInterface.versionID, null);
}
}
/*
MyRpcServer.java
*/
package Server;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.RPC;
import java.io.IOException;
public class MyRpcServer {
public static void main(String[] args) {
//建立rpc通道对象
RPC.Builder builder = new RPC.Builder(new Configuration());
• //设置RPC server参数
• builder.setBindAddress("localhost");
• builder.setPort(7788);
• //部署程序,传入实现server业务代码的接口定义,这里面包括了该rpcserver 可以提供的方法,也就是给client调用的方法列表,通过反射的方式引入类对象
• builder.setProtocol(MyInterface.class);
• //部署接口的实现类对象
• builder.setInstance(new MyImpl());
• //开启server
• try {
• RPC.Server server = builder.build();
• server.start();
• } catch (IOException e) {
• e.printStackTrace();
• }
}
}
client:
/*
MyRpcClient.java
*/
package Client;
import Server.MyInterface;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.RPC;
import java.io.IOException;
import java.net.InetSocketAddress;
public class MyRpcClient {
public static void main(String[] args) {
try {
//获取代理对象,设置接口类对象、RPC通信的versionID,rpcserver地址、configuration对象
MyInterface proxy = RPC.getProxy(
MyInterface.class,
MyInterface.versionID,
new InetSocketAddress("localhost", 7788),
new Configuration());
• //获得代理对象之后,就可以通过proxy调用接口类中的方法,这里就调用上面定义的 helloWorld对象
• System.out.println(proxy.helloWorld("king"));
• } catch (IOException e) {
• e.printStackTrace();
• }
}
}
下面启动server端和client端,执行结果为:
//server:可以看到显示监听端口 7788
[main] INFO org.apache.hadoop.ipc.CallQueueManager - Using callQueue: class java.util.concurrent.LinkedBlockingQueue queueCapacity: 100 scheduler: class org.apache.hadoop.ipc.DefaultRpcScheduler
[Socket Reader #1 for port 7788] INFO org.apache.hadoop.ipc.Server - Starting Socket Reader #1 for port 7788
[IPC Server Responder] INFO org.apache.hadoop.ipc.Server - IPC Server Responder: starting
[IPC Server listener on 7788] INFO org.apache.hadoop.ipc.Server - IPC Server listener on 7788: starting
//client: 我们传入“King”作为参数,能够争取执行
hello,king
-----------------------------------
3、dubbo核心概念
-
简介
Apache dubbo 是一款高性能、 轻量及的开源 java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
官网: Apache Dubbo
-
特性
面向接口代理的高性能RPC调用
服务自动注册与发现
运行期流量调度
智能负载均衡
高度可扩展能力
可视化的服务治理与运维
-
3. dubbo Architecture
总体架构
节点角色说明
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用关系说明
-
服务容器负责启动,加载,运行服务提供者。
-
服务提供者在启动时,向注册中心注册自己提供的服务。
-
服务消费者在启动时,向注册中心订阅自己所需的服务。
-
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
-
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
-
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。
连通性
-
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
-
监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
-
服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
-
服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
-
注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
-
注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
-
注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
-
注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性
-
监控中心宕掉不影响使用,只是丢失部分采样数据
-
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
-
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
-
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
-
服务提供者无状态,任意一台宕掉后,不影响使用
-
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性
-
注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
-
服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
升级性
当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:
节点角色说明
节点 | 角色说明 |
---|---|
Deployer | 自动部署服务的本地代理 |
Repository | 仓库用于存储服务应用发布包 |
Scheduler | 调度中心基于访问压力自动增减服务提供者 |
Admin | 统一管理控制台 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
4.代码架构
整体设计
图例说明:
-
图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
-
图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
-
图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
-
图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调用链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
各层说明
-
Config 配置层:对外配置接口,以
ServiceConfig
,ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类 -
Proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以
ServiceProxy
为中心,扩展接口为ProxyFactory
-
Registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为
RegistryFactory
,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
关系说明
-
在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。
-
图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是 Dubbo 在很多场景下都使用 Provider, Consumer, Registry, Monitor 划分逻辑拓扑节点,保持统一概念。
-
而 Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。
-
Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
-
而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。
-
Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。
模块分包
模块说明:
-
dubbo-common 公共逻辑模块:包括 Util 类和通用模型。
-
dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
-
dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
-
dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
-
dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
-
dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
-
dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节。
-
dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。
整体上按照分层结构进行分包,与分层的不同点在于:
-
Container 为服务容器,用于部署运行服务,没有在层中画出。
-
Protocol 层和 Proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。
-
Transport 层和 Exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。
-
Serialize 层放在 common 模块中,以便更大程度复用。
依赖关系
图例说明:
-
图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。
-
图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。
-
图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。
-
图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。
调用链
展开总设计图的红色调用链,如下:
暴露服务时序
展开总设计图右边服务提供方暴露服务的蓝色初始化链,时序图如下:
引用服务时序
展开总设计图左边服务消费方引用服务的绿色初始化链,时序图如下:
领域模型
在 Dubbo 的核心领域模型中:
-
Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
-
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠拢,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
-
Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。
基本设计原则
-
采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
-
采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
二、dubbo开发基础
1、搭建环境
安装zookeeper注册中心(https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/registry/zookeeper/)
使用docker安装:
1)、编写 docker-compose.yml:
version: "3.1"
services:
zk:
image: daocloud.io/daocloud/zookeeper:latest
restart: always
container_name: zk
ports:
- 2181:2181
2)、docker-compose up -d
3)、docker exec -it 63 bash
安装监控中心(GitHub - apache/dubbo: Apache Dubbo is a high-performance, java based, open source RPC framework.)
GitHub - apache/dubbo-admin: The ops and reference implementation for Apache Dubbo
1、 Run With Kubernetes
-
Get Kubernetes manifests**
$ git clone https://github.com/apache/dubbo-admin.git
All we need from this step is the Admin kubernetes manifests in deploy/k8s
But before you can apply the manifests, override the default value defined in application.properties by adding items in configmap.yaml
.
$ cd /dubbo-admin/deploy/k8s
-
2.Deploy Dubbo Admin
# Change configuration in ./deploy/application.yml before apply if necessary
$ kubectl apply -f ./
-
Visit Admin**
$ kubectl port-forward service dubbo-admin 38080:38080
Open web browser and visit http://localhost:38080
, default username and password are root
2、 Run With Docker
The prebuilt docker image is hosted at: Docker
You can run the image directly by mounting a volume from the host that contains an application.properties
file with the accessible registry and config-center addresses specified.
$ docker run -it --rm -v /the/host/path/containing/properties:/config -p 38080:38080 apache/dubbo-admin
Replace /the/host/path/containing/properties
with the actual host path (must be an absolute path) that points to a directory containing application.properties
.
Open web browser and visit http://localhost:38080
, default username and password are root
3、 Compile From Source
-
Clone source code on
develop
branchgit clone https://github.com/apache/dubbo-admin.git
-
Specify registry address in
dubbo-admin-server/src/main/resources/application.properties
admin.registry.address=zookeeper://192.168.0.252:2181?blockUntilConnectedWait=200000&timeout=200000 admin.config-center=zookeeper://192.168.0.252:2181 admin.metadata-report.address=zookeeper://192.168.0.252:2181 dubbo.registry.timeout=200000 dubbo.metadata-report.timeout=200000 dubbo.metadata-report.address=${admin.metadata-report.address}
-
Build
-
mvn clean package -Dmaven.test.skip=true
-
-
Start
-
mvn --projects dubbo-admin-server spring-boot:run
OR -
cd dubbo-admin-distribution/target
;java -jar dubbo-admin-0.1.jar
-
-
Visit
http://localhost:8080
, default username and password areroot
注意:如果是虚拟机,要关闭防火墙和selinux
#关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
#关才selinux
sed -i 's/enforcing/disabled/' /etc/selinux/config #永久
setenforce 0 #临时
2、dubbo-helloworld
1)、提出需求
某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址;
需要创建两个服务模块进行测试:
模块 | 功能 |
---|---|
订单服务web模块 | 创建订单等 |
用户服务service模块 | 查询用户地址等 |
测试预期结果:
订单服务web模块在A服务器,用户服务模块在B服务器,A可以远行调用B的功能。
创建API包(服务化最佳实践 | Apache Dubbo)
创建bean
public class UserAddress implements Serializable{
private Integer id;
private String userAddress;
private String userId;
private String consignee; //收货人
private String phoneNum; //电放号码
private String isDefault; //是否为默认地址
}
创建公共接口
public interface OrderService {
public void initOrder(String userId)
}
public interface UserService {
public List<UserAddress> getUserAddressList(String userId);
}
如下所示:
2)、服务提供者
1、创建maven工程
2、 引入API公共包:
<dependency>
<groupId>com.study.gmall</groupId>
<artifactId>gmall-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
3、 实现服务提供者的接口
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress userAddress1 = new UserAddress(1, "西安市高新区高新十小", "1", "康老师", "13854524576", "是");
UserAddress userAddress2 = new UserAddress(2, "西安市高新区高新十小", "2", "孙老师", "13973747319", "否");
return Arrays.asList(userAddress1, userAddress2);
}
}
4、引入dubbo
1、将服务提供者注册到注册中心(暴露服务)
(1)、导入dubbo依赖(2.6.2)以及引入操作zookper的客户端(curator)
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
导入zookeeper依赖(2.6版本以上)
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
(2.6版本以下)
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
(2) 配置文件
<?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">
<!-- 1、指定当前服务/应用的名字(同样的服务名字相同, 不要和别的服务同名) -->
<dubbo:application name="user-service-provider" />
<!-- 2、指定注册中心的位置 -->
<dubbo:registry address="zookeeper://192.168.0.252:2181" />
<!--3、指定通信规则(通信规则?通信端口) -->
<dubbo:protocol name="dubbo" port="20881" />
<!--4、暴露服务 ref: 指向服务的真正的实现对象 -->
<dubbo:service interface="com.study.gmall.service.UserService" ref="userServiceImpl" />
<!--5、服务的实现 -->
<bean id="userServiceImpl" class="com.study.gmall.service.impl.UserServiceImpl" />
</beans>
5、测试:
public class App
{
public static void main( String[] args ) throws IOException
{
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("provider.xml");
ioc.start();
System.in.read();
}
}
6、结果:
3)、创建消费者:
1、创建maven工程
2、引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
3、实现:
/**
* 1、将服务提供者注册到注册中心(暴露服务) 1)、 导入dubbo依赖(2.6.2) \操作zookeeper的客户端(curator) 2)、
* 配置服务提供者
*
* 2、让服务消费者去注册中心订阅服务提供都的服务地址
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
public void initOrder(String userId) {
// TODO Auto-generated method stub
System.out.println("用户ID: " + userId);
// 1、查询用户的收货地址
List<UserAddress> addresses = userService.getUserAddressList(userId);
for(UserAddress userAddress : addresses) {
System.out.println(userAddress.getUserAddress());
}
}
}
4、配置:
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<context:component-scan base-package="com.study.gmall.service.impl"></context:component-scan>
<dubbo:application name="order-service-consumer"></dubbo:application>
<dubbo:registry address="zookeeper://192.168.0.252:2181" />
<!-- 声明需要调用的远程服务的接口,生成远程服务代理 -->
<dubbo:reference interface="com.study.gmall.service.UserService" id="userService"></dubbo:reference>
</beans>
5、测试:
public class App
{
public static void main( String[] args ) throws IOException
{
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");;
OrderService orderService = applicationContext.getBean(OrderService.class);
orderService.initOrder("1");
System.out.println("调用结束..........................");
System.in.read();
}
}
6、结果:
3、springboot 整合 dubbo
1)、服务提供者
1、创建springboot工程
2、引入API包,实现服务
<dependency>
<groupId>com.study.dubbo</groupId>
<artifactId>gmall-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
@Component
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress userAddress1 = new UserAddress(1, "北京市海淀区中关村大街132号", "1", "康老师", "13854524576", "是");
UserAddress userAddress2 = new UserAddress(2, "深圳市罗湖区香湖湾101号", "2", "孙老师", "13973747319", "否");
return Arrays.asList(userAddress1, userAddress2);
}
}
3、引入dubbo, 及相关的zookeeper组件
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
4、编写配置文件(application.properties)
dubbo.application.name=boot-user-service-provider
dubbo.registry.address=zookeeper://192.168.0.252:2181?blockUntilConnectedWait=200000&timeout=200000
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo
dubbo.protocol.port=20882
dubbo.monitor.protocol=registry
dubbo.metadata-report.address=zookeeper://192.168.0.252:2181
5、暴露服务
在服务上引入 @org.apache.dubbo.config.annotation.DubboService
低版本引入 @org.apache.dubbo.config.annotation.Service 或
@com.alibaba.dubbo.config.annotation.Service 这个注解将要过期
@org.apache.dubbo.config.annotation.DubboService //暴露服务
@Component
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress userAddress1 = new UserAddress(1, "北京市海淀区中关村大街132号", "1", "康老师", "13854524576", "是");
UserAddress userAddress2 = new UserAddress(2, "深圳市罗湖区香湖湾101号", "2", "孙老师", "13973747319", "否");
return Arrays.asList(userAddress1, userAddress2);
}
}
6、开启dubbo服务
@EnableDubbo //开启基于注解的dubbo功能
@SpringBootApplication
public class BootUserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceProviderApplication.class, args);
}
}
7、启动服务
2)、服务消费者
1、创建springboot服务, 因为是消费端,使用war包,web工程
2、引入API包,实现接口
<dependency>
<groupId>com.study.dubbo</groupId>
<artifactId>gmall-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
public List<UserAddress> initOrder(String userId) {
// TODO Auto-generated method stub
System.out.println("用户ID: " + userId);
// 1、查询用户的收货地址
List<UserAddress> addresses = userService.getUserAddressList(userId);
return addresses;
}
}
3、创建controller,供web访问
@Controller
public class OrderController {
@Autowired
OrderService orderService;
@ResponseBody
@RequestMapping("/initOrder")
public List<UserAddress> initOrder(@RequestParam("uid") String userId) {
return orderService.initOrder(userId);
}
}
4、配置文件
dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://192.168.0.252:2181?blockUntilConnectedWait=200000&timeout=200000
dubbo.monitor.protocol=registry
dubbo.metadata-report.address=zookeeper://192.168.0.252:2181
server.port=8081
5、引入dubbo及zookeeper客户端
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
6、引用dubbo的消费服务
低版本使用@Reference
@Service
public class OrderServiceImpl implements OrderService {
@DubboReference
UserService userService;
public List<UserAddress> initOrder(String userId) {
// TODO Auto-generated method stub
System.out.println("用户ID: " + userId);
// 1、查询用户的收货地址
List<UserAddress> addresses = userService.getUserAddressList(userId);
return addresses;
}
}
7、开启dubbo服务
@EnableDubbo
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
}
}
8、启动服务,测试
4、XML 配置
1)、属性 配置
配置生效
(1)、 精确优先(方法级优先,接口级次之,全局配置再次之)
(2)、消费者设置优先(如果级别一样,则消费方优先。提供方次之)
2)、启动时检查
3)、重试次数配置
幂等(设置重试次数)【查询、删除、修改】、非幂等(不能设置重试次数)【新增】
retries="": 重试次数,不包含第一次调用, 0代表不重试
4)、多版本
5)、Springboot与dubbo整合的三种方式
1)、导入dubbo-starter, 在application.properties配置属性, 使用@service【暴露服务】使用@Reference【引用服务】
老版本可用如下配置,则不需要开启"@EnableDubbo"
dubbo.scan.base-packages=com.study.gmall
2)、保留dubbo xml配置文件(精确到方法级别)
创建provider.xml文件
在SpringBootApplication中配置 @ImportResource(locations="classpath:provider.xml")
3)、Annotation 配置
https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/config/annotation/
以 Annotation、Spring Boot 开发 Dubbo 应用
本文以 Spring Boot + Annotation 模式描述 Dubbo 应用开发,在此查看无 Spring Boot 的 Spring 注解开发模式 完整示例
在 Dubbo Spring Boot 开发中,你只需要增加几个注解,并配置 application.properties
或 application.yml
文件即可完成 Dubbo 服务定义:
-
注解有
@DubboService
、@DubboReference
与EnableDubbo
。其中@DubboService
与@DubboReference
用于标记 Dubbo 服务,EnableDubbo
启动 Dubbo 相关配置并指定 Spring Boot 扫描包路径。 -
配置文件
application.properties
或application.yml
以下内容的完整示例请参考 dubbo-samples
增加 Maven 依赖
使用 Dubbo Spring Boot Starter 首先引入以下 Maven 依赖
<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>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Zookeeper -->
<!-- NOTICE: Dubbo only provides dependency management module for Zookeeper, add Nacos or other product dependency directly if you want to use them. -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
然后在相应的模块的 pom 中增加
<dependencies>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<type>pom</type>
</dependency>
<!-- dubbo starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- spring starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
application.yml 或 application.properties
除 service、reference 之外的组件都可以在 application.yml 文件中设置,如果要扩展 service 或 reference 的注解配置,则需要增加 dubbo.properties
配置文件或使用其他非注解如 Java Config 方式,具体请看下文 扩展注解的配置。
service、reference 组件也可以通过 id
与 application 中的全局组件做关联,以下面配置为例:
dubbo:
application:
name: dubbo-springboot-demo-provider
protocol:
name: dubbo
port: -1
registry:
id: zk-registry
address: zookeeper://127.0.0.1:2181
config-center:
address: zookeeper://127.0.0.1:2181
metadata-report:
address: zookeeper://127.0.0.1:2181
通过注解将 service 关联到上文定义的特定注册中心
@DubboService(registry="zk-registry")
public class DemoServiceImpl implements DemoService {}
通过 Java Config 配置进行关联也是同样道理
@Configuration
public class ProviderConfiguration {
@Bean
public ServiceConfig demoService() {
ServiceConfig service = new ServiceConfig();
service.setRegistry("zk-registry");
return service;
}
}
注解
@DubboService 注解
@Service
注解从 3.0 版本开始就已经废弃,改用@DubboService
,以区别于 Spring 的@Service
注解
定义好 Dubbo 服务接口后,提供服务接口的实现逻辑,并用 @DubboService
注解标记,就可以实现 Dubbo 的服务暴露
@DubboService
public class DemoServiceImpl implements DemoService {}
如果要设置服务参数,@DubboService
也提供了常用参数的设置方式。如果有更复杂的参数设置需求,则可以考虑使用其他设置方式
@DubboService(version = "1.0.0", group = "dev", timeout = 5000)
public class DemoServiceImpl implements DemoService {}
@DubboReference 注解
@Reference
注解从 3.0 版本开始就已经废弃,改用@DubboReference
,以区别于 Spring 的@Reference
注解
@Component
public class DemoClient {
@DubboReference
private DemoService demoService;
}
@DubboReference
注解将自动注入为 Dubbo 服务代理实例,使用 demoService 即可发起远程服务调用
@EnableDubbo 注解
@EnableDubbo
注解必须配置,否则将无法加载 Dubbo 注解定义的服务,@EnableDubbo
可以定义在主类上
@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ProviderApplication.class, args);
}
}
Spring Boot 注解默认只会扫描 main 类所在的 package,如果服务定义在其它 package 中,需要增加配置 EnableDubbo(scanBasePackages = {"org.apache.dubbo.springboot.demo.provider"})
扩展注解配置
虽然可以通过 @DubboService
和 DubboReference
调整配置参数(如下代码片段所示),但总体来说注解提供的配置项还是非常有限。在这种情况下,如果有更复杂的参数设置需求,可以使用 Java Config
或 dubbo.properties
两种方式。
@DubboService(version = "1.0.0", group = "dev", timeout = 5000)
@DubboReference(version = "1.0.0", group = "dev", timeout = 5000)
使用 Java Config 代替注解
注意,Java Config 是 DubboService
或 DubboReference
的替代方式,对于有复杂配置需求的服务建议使用这种方式。
@Configuration
public class ProviderConfiguration {
@Bean
public ServiceConfig demoService() {
ServiceConfig service = new ServiceConfig();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.setGroup("dev");
service.setVersion("1.0.0");
Map<String, String> parameters = new HashMap<>();
service.setParameters(parameters);
return service;
}
}
通过 dubbo.properties 补充配置
对于使用 DubboService
或 DubboReference
的场景,可以使用 dubbo.properties 作为配置补充,具体格式这里有更详细解释。
dubbo.service.org.apache.dubbo.springboot.demo.DemoService.timeout=5000
dubbo.service.org.apache.dubbo.springboot.demo.DemoService.parameters=[{myKey:myValue},{anotherKey:anotherValue}]
dubbo.reference.org.apache.dubbo.springboot.demo.DemoService.timeout=6000
properties 格式配置目前结构性不太强,比如体现在 key 字段冗余较多,后续会考虑提供对于 yaml 格式的支持。
三、高可用
1、zookeeper宕机与dubbo直连
现象: zookeeper注册中心宕机,还可以消费dubbo暴露的服务;
原因: 健状性
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务。
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯。
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供都全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
高可用:通过设计,减少系统不能提供服务的时间
@DubboReference(url="127.0.0.1:20882") //dubbo直连
2、集群下dubbo负载均衡配置
在集群负载均衡时,Dubbo提供了多种均衡策略,缺省为random随机调用.
负载均衡策略
Random LoadBalance
随机,按权重设置随机概率: 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
轮循,按公约后的权重设置轮循比率:存在慢的提供者累积请求的问题,比如:第二台机器慢,但没挂,当请求调到第二台时就卡住,久而久之,所有请求都卡在调到第二台上。
@DubboReference(loadbalance="roundRobin") //配置
LeastActive LoadBalance
最少活跃调用数,相同活跃数据的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
一致性Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。详见: 深度解析dubbo负载均衡之ConsistentHashLoadBalance_$码出未来的博客-优快云博客 及 Dubbo源码学习--ConsistentHashLoadBalance负载均衡(五)_归田的博客-优快云博客
3、服务降级
当服务压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心变易正常动作或高效动作。
可以通过服务降级功能临时屏蔽某个出错的非关键 服务,并定义降级后的返回策略。向注册中心写入动态配置覆盖规则:
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值,不抛异常。用来容忍
4、集群容错
在集群调用失败时,Dubbo提供了多种容错方案,缺省为Failover重试
集群容错模式:详见(Dubbo集群容错_爱coding的李同学的博客-优快云博客)
附:
-
集群容错 集群调用失败时,Dubbo 提供的容错方案。
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
各节点关系:
这里的 Invoker 是 Provider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息 Directory 代表多个 Invoker,可以把它看成 List ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更 Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个 Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等 LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
-
集群容错模式分类 Cluster类图如下:
2.1 Failover Cluster(缺省配置) 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。
重试次数配置如下:
<dubbo:service retries="2" />
<dubbo:reference retries="2" />
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
源码分析:
copyinvokers 变量,候选的 Invoker 集合。 调用父 #checkInvokers(copyinvokers, invocation) 方法,校验候选的 Invoker 集合非空。如果为空,抛出 RpcException 异常。 获得最大可调用次数:最大可重试次数 +1 。默认最大可重试次数Constants.DEFAULT_RETRIES = 2 。 le 变量,保存最后一次调用的异常。 invoked 变量,保存已经调用的 Invoker 集合。 providers 变量,保存已经调用的网络地址集合。 failover 机制核心实现:如果出现调用失败,那么重试其他服务器。 超过最大调用次数,抛出 RpcException 异常。该异常中,带有最后一次调用异常的信息。 2.2 Forking Cluster 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。
源码分析:
count 变量,异常计数器。 ref 变量,阻塞队列。通过它,实现线程池异步执行任务的结果通知,非常亮眼。 循环 selected 集合,提交线程池,发起 RPC 调用。 从 ref 队列中,阻塞等待,直到获得到结果或者超时。至此,ForkingClusterInvoker 实现了并行调用,且只要一个成功即返回。当然,还有一个隐性的,所有都失败才返回。 处理等待的“结果”。
2.3 Failfast Cluster 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
2.4 Failsafe Cluster 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
2.5 Failback Cluster 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
2.6 Broadcast Cluster 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
现在广播调用中,可以通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。
Broadcast Cluster 配置 broadcast.fail.percent。
broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。
@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})
源码分析:
集群模式配置
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster="failsafe" />
<dubbo:reference cluster="failsafe" />
5、整合Hystrix
Hystrix旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
1)、配置spring-cloud-starter-netflix-hystrix
spring boot官方提供了对hystrix的集成,直接在pom.xml里加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
然后在Application类上增加@EnableHystrix来启用hystrix starter
四、dubbo原理
1、RPC原理
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套接字服务器等网络编程。具体模型详见(https://www.cnblogs.com/hlkawa/p/15047212.html).
BIO:(Blocking IO)
NIO:(Non-Blocking IO)
Netty基本原理:(Java开发中Netty线程模型原理解析-JavaEE资讯-博学谷)
Java开发中Netty线程模型原理解析,Netty是Java领域有名的开源网络库具有高性能和高扩展性的特点,很多流行的框架都是基于它来构建。Netty 线程模型不是一成不变的,取决于用户的启动参数配置。通过设置不同的启动参数Netty ,可同时支持 Reactor 单线程模型、多线程模型。
一、线程组
Netty抽象了两组线程池BossGroup和WorkerGroup,其类型都是NioEventLoopGroup,BossGroup用来接受客户端发来的连接WorkerGroup则负责对完成TCP三次握手的连接进行处理。
NioEventLoopGroup里面包含了多个NioEventLoop管理NioEventLoop的生命周期。每个NioEventLoop中包含了一个NIO Selector、一个队列、一个线程;其中线程用来做轮询注册到Selector上的Channel的读写事件和对投递到队列里面的事件进行处理。
Boss NioEventLoop线程的执行步骤:
(1)处理accept事件与client建立连接, 生成NioSocketChannel。
(2)将NioSocketChannel注册到某个worker NIOEventLoop上的selector
(3)处理任务队列的任务 即runAllTasks。
Worker NioEventLoop线程的执行步骤:
(1)轮询注册到自己Selector上的所有NioSocketChannel的read和write事件。
(2)2处理read和write事件在对应NioSocketChannel处理业务。
(3)#runAllTasks处理任务队列TaskQueue的任务,一些耗时的业务处理可以放入TaskQueue中慢慢处理这样不影响数据在pipeline中的流动处理。
Worker NIOEventLoop处理NioSocketChannel业务时,使用了pipeline (管道),管道中维护了handler处理器链表用来处理channel中的数据。
二、ChannelPipeline
Netty将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelHandler的双向链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便的新增和删除ChannelHandler来实现不同的业务逻辑定制不需要对已有的ChannelHandler进行修改能够实现对修改封闭和对扩展的支持。
ChannelPipeline是一系列的ChannelHandler实例,流经一个Channel的入站和出站事件可以被ChannelPipeline 拦截。每当一个新的Channel被创建了,都会建立一个新的ChannelPipeline并绑定到该Channel上,这个关联是永久性的;Channel既不能附上另一个ChannelPipeline也不能分离当前这个。这些都由Netty负责完成,而无需开发人员的特别处理。
根据起源一个事件将由ChannelInboundHandler或ChannelOutboundHandler处理,ChannelHandlerContext实现转发或传播到下一个ChannelHandler。一个ChannelHandler处理程序可以通知ChannelPipeline中的下一个ChannelHandler执行。Read事件(入站事件)和write事件(出站事件)使用相同的pipeline,入站事件会从链表head 往后传递到最后一个入站的handler出站事件会从链表tail往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。
ChannelInboundHandler回调方法:
ChannelOutboundHandler回调方法:
三、异步非阻塞
写操作:通过NioSocketChannel的write方法向连接里面写入数据时候是非阻塞的,马上会返回即使调用写入的线程是我们的业务线程。Netty通过在ChannelPipeline中判断调用NioSocketChannel的write的调用线程是不是其对应的NioEventLoop中的线程,如果发现不是则会把写入请求封装为WriteTask投递到其对应的NioEventLoop中的队列里面,然后等其对应的NioEventLoop中的线程轮询读写事件时候,将其从队列里面取出来执行。
读操作:当从NioSocketChannel中读取数据时候并不是需要业务线程阻塞等待,而是等NioEventLoop中的IO轮询线程发现Selector上有数据就绪时,通过事件通知方式来通知业务数据已就绪,可以来读取并处理了。
每个NioSocketChannel对应的读写事件都是在其对应的NioEventLoop管理的单线程内执行,对同一个NioSocketChannel不存在并发读写,所以无需加锁处理。
使用Netty框架进行网络通信时,当我们发起I/O请求后会马上返回,而不会阻塞我们的业务调用线程;如果想要获取请求的响应结果,也不需要业务调用线程使用阻塞的方式来等待,而是当响应结果出来的时候,使用I/O线程异步通知业务的方式,所以在整个请求 -> 响应过程中业务线程不会由于阻塞等待而不能干其他事情。
3、Dubbo原理
1)、 框架设计
图例说明:
-
图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
-
图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
-
图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
-
图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调用链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
各层说明
-
Config 配置层:对外配置接口,以
ServiceConfig
,ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类 -
Proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以
ServiceProxy
为中心,扩展接口为ProxyFactory
-
Registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为
RegistryFactory
,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
关系说明
-
在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。
-
图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是 Dubbo 在很多场景下都使用 Provider, Consumer, Registry, Monitor 划分逻辑拓扑节点,保持统一概念。
-
而 Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。
-
Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
-
而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。
-
Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。
模块分包
模块说明:
-
dubbo-common 公共逻辑模块:包括 Util 类和通用模型。
-
dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
-
dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
-
dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
-
dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
-
dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
-
dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节。
-
dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。
整体上按照分层结构进行分包,与分层的不同点在于:
-
Container 为服务容器,用于部署运行服务,没有在层中画出。
-
Protocol 层和 Proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。
-
Transport 层和 Exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。
-
Serialize 层放在 common 模块中,以便更大程度复用。
依赖关系
图例说明:
-
图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。
-
图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。
-
图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。
-
图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。
调用链
展开总设计图的红色调用链,如下:
暴露服务时序
展开总设计图右边服务提供方暴露服务的蓝色初始化链,时序图如下:
引用服务时序
展开总设计图左边服务消费方引用服务的绿色初始化链,时序图如下:
领域模型
在 Dubbo 的核心领域模型中:
-
Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
-
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠拢,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
-
Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。
基本设计原则
-
采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
-
采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
2)、服务暴露流程
spring的接口: BeanDefinitionParser (bean定义解析器)
DubboNamespaceHandler 注册标签,在DubboBeanDefinitionParser初始化时,注册。
3)、服务引用流程
ReferenceBean是FactoryBean
4)、服务调用流程
调用链